1/*-
2 * Copyright (c) 2019 Stormshield.
3 * Copyright (c) 2019 Semihalf.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
18 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
22 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
23 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <sys/param.h>
28#include <sys/ctype.h>
29#include <sys/eventhandler.h>
30#include <sys/fcntl.h>
31#include <sys/lock.h>
32#include <sys/module.h>
33#include <sys/mutex.h>
34#include <sys/namei.h>
35#include <sys/proc.h>
36#include <sys/systm.h>
37#include <sys/vnode.h>
38
39#include <crypto/sha2/sha256.h>
40#include <crypto/sha2/sha384.h>
41#include <crypto/sha2/sha512.h>
42
43#include <security/mac_veriexec/mac_veriexec.h>
44#include <security/mac_veriexec/mac_veriexec_internal.h>
45
46/* The following are based on sbin/veriexec */
47struct fingerprint_type {
48	const char	*fp_type;
49	int		fp_size;
50};
51
52struct fp_flag {
53	const char	*flag_name;
54	int		flag;
55};
56
57static const struct fingerprint_type fp_table[] = {
58	{"sha256=", SHA256_DIGEST_LENGTH},
59#if MAXFINGERPRINTLEN >= SHA384_DIGEST_LENGTH
60	{"sha384=", SHA384_DIGEST_LENGTH},
61#endif
62#if MAXFINGERPRINTLEN >= SHA512_DIGEST_LENGTH
63	{"sha512=", SHA512_DIGEST_LENGTH},
64#endif
65	{NULL, 0}
66};
67
68static const struct fp_flag flags_table[] = {
69	{"indirect",  VERIEXEC_INDIRECT},
70	{"no_ptrace", VERIEXEC_NOTRACE},
71	{"trusted",   VERIEXEC_TRUSTED},
72	{"no_fips",   VERIEXEC_NOFIPS},
73	{NULL, 0}
74};
75
76extern struct mtx ve_mutex;
77
78static unsigned char	hexchar_to_byte(unsigned char c);
79static int		hexstring_to_bin(unsigned char *buf);
80
81static int	get_flags(const char *entry);
82static int	get_fp(const char *entry, char **type,
83		    unsigned char **digest, int *flags);
84static int	verify_digest(const char *data, size_t len,
85		    const unsigned char *expected_hash);
86
87static int	open_file(const char *path, struct nameidata *nid);
88static char	*read_manifest(char *path, unsigned char *digest);
89static int	parse_entry(char *entry, char *prefix);
90static int	parse_manifest(char *path, unsigned char *hash, char *prefix);
91
92static unsigned char
93hexchar_to_byte(unsigned char c)
94{
95
96	if (isdigit(c))
97		return (c - '0');
98
99	return (isupper(c) ? c - 'A' + 10 : c - 'a' + 10);
100}
101
102static int
103hexstring_to_bin(unsigned char *buf)
104{
105	size_t		i, len;
106	unsigned char	byte;
107
108	len = strlen(buf);
109	for (i = 0; i < len / 2; i++) {
110		if (!isxdigit(buf[2 * i]) || !isxdigit(buf[2 * i + 1]))
111			return (EINVAL);
112
113		byte = hexchar_to_byte(buf[2 * i]) << 4;
114		byte += hexchar_to_byte(buf[2 * i + 1]);
115		buf[i] = byte;
116	}
117	return (0);
118}
119
120static int
121get_flags(const char *entry)
122{
123	int	i;
124	int	result = 0;
125
126	for (i = 0; flags_table[i].flag_name != NULL; i++)
127		if (strstr(entry, flags_table[i].flag_name) != NULL)
128			result |= flags_table[i].flag;
129
130	return (result);
131}
132
133/*
134 * Parse a single line of manifest looking for a digest and its type.
135 * We expect it to be in form of "path shaX=hash".
136 * The line will be split into path, hash type and hash value.
137 */
138static int
139get_fp(const char *entry, char **type, unsigned char **digest, int *flags)
140{
141	char	*delimiter;
142	char	*local_digest;
143	char	*fp_type;
144	char	*prev_fp_type;
145	size_t	min_len;
146	int	i;
147
148	delimiter = NULL;
149	fp_type = NULL;
150	prev_fp_type = NULL;
151
152	for (i = 0; fp_table[i].fp_type != NULL; i++) {
153		fp_type = strstr(entry, fp_table[i].fp_type);
154		/* Look for the last "shaX=hash" in line */
155		while (fp_type != NULL) {
156			prev_fp_type = fp_type;
157			fp_type++;
158			fp_type = strstr(fp_type, fp_table[i].fp_type);
159		}
160		fp_type = prev_fp_type;
161		if (fp_type != NULL) {
162			if (fp_type == entry || fp_type[-1] != ' ')
163				return (EINVAL);
164
165			/*
166			 * The entry should contain at least
167			 * fp_type and digest in hexadecimal form.
168			 */
169			min_len = strlen(fp_table[i].fp_type) +
170				2 * fp_table[i].fp_size;
171
172			if (strnlen(fp_type, min_len) < min_len)
173				return (EINVAL);
174
175			local_digest = &fp_type[strlen(fp_table[i].fp_type)];
176			delimiter = &local_digest[2 * fp_table[i].fp_size];
177
178			/*
179			 * Make sure that digest is followed by
180			 * some kind of delimiter.
181			 */
182			if (*delimiter != '\n' &&
183			    *delimiter != '\0' &&
184			    *delimiter != ' ')
185				return (EINVAL);
186
187			/*
188			 * Does the entry contain flags we need to parse?
189			 */
190			if (*delimiter == ' ' && flags != NULL)
191				*flags = get_flags(delimiter);
192
193			/*
194			 * Split entry into three parts:
195			 * path, fp_type and digest.
196			 */
197			local_digest[-1] = '\0';
198			*delimiter = '\0';
199			fp_type[-1] = '\0';
200			break;
201		}
202	}
203
204	if (fp_type == NULL)
205		return (EINVAL);
206
207	if (type != NULL)
208		*type = fp_type;
209
210	if (digest != NULL)
211		*digest = local_digest;
212
213	return (0);
214}
215
216/*
217 * Currently we verify manifest using sha256.
218 * In future another env with hash type could be introduced.
219 */
220static int
221verify_digest(const char *data, size_t len, const unsigned char *expected_hash)
222{
223	SHA256_CTX	ctx;
224	unsigned char	hash[SHA256_DIGEST_LENGTH];
225
226	SHA256_Init(&ctx);
227	SHA256_Update(&ctx, data, len);
228	SHA256_Final(hash, &ctx);
229
230	return (memcmp(expected_hash, hash, SHA256_DIGEST_LENGTH));
231}
232
233static int
234open_file(const char *path, struct nameidata *nid)
235{
236	int flags, rc;
237
238	flags = FREAD;
239
240	pwd_ensure_dirs();
241
242	NDINIT(nid, LOOKUP, 0, UIO_SYSSPACE, path);
243	rc = vn_open(nid, &flags, 0, NULL);
244	NDFREE_PNBUF(nid);
245	if (rc != 0)
246		return (rc);
247
248	return (0);
249}
250
251/*
252 * Read the manifest from location specified in path and verify its digest.
253 */
254static char*
255read_manifest(char *path, unsigned char *digest)
256{
257	struct nameidata	nid;
258	struct vattr		va;
259	char			*data;
260	ssize_t			bytes_read, resid;
261	int			rc;
262
263	data = NULL;
264	bytes_read = 0;
265
266	rc = open_file(path, &nid);
267	if (rc != 0)
268		goto fail;
269
270	rc = VOP_GETATTR(nid.ni_vp, &va, curthread->td_ucred);
271	if (rc != 0)
272		goto fail;
273
274	data = (char *)malloc(va.va_size + 1, M_VERIEXEC, M_WAITOK);
275
276	while (bytes_read < va.va_size) {
277		rc = vn_rdwr(
278		    UIO_READ, nid.ni_vp, data,
279		    va.va_size - bytes_read, bytes_read,
280		    UIO_SYSSPACE, IO_NODELOCKED,
281		    curthread->td_ucred, NOCRED, &resid, curthread);
282		if (rc != 0)
283			goto fail;
284
285		bytes_read = va.va_size - resid;
286	}
287
288	data[bytes_read] = '\0';
289
290	VOP_UNLOCK(nid.ni_vp);
291	(void)vn_close(nid.ni_vp, FREAD, curthread->td_ucred, curthread);
292
293	/*
294	 * If digest is wrong someone might be trying to fool us.
295	 */
296	if (verify_digest(data, va.va_size, digest))
297		panic("Manifest hash doesn't match expected value!");
298
299	return (data);
300
301fail:
302	if (data != NULL)
303		free(data, M_VERIEXEC);
304
305	return (NULL);
306}
307
308/*
309 * Process single line.
310 * First split it into path, digest_type and digest.
311 * Then try to open the file and insert its fingerprint into metadata store.
312 */
313static int
314parse_entry(char *entry, char *prefix)
315{
316	struct nameidata	nid;
317	struct vattr		va;
318	char			path[MAXPATHLEN];
319	char			*fp_type;
320	unsigned char		*digest;
321	int			rc, is_exec, flags;
322
323	fp_type = NULL;
324	digest = NULL;
325	flags = 0;
326
327	rc = get_fp(entry, &fp_type, &digest, &flags);
328	if (rc != 0)
329		return (rc);
330
331	rc = hexstring_to_bin(digest);
332	if (rc != 0)
333		return (rc);
334
335	if (strnlen(entry, MAXPATHLEN) == MAXPATHLEN)
336		return (EINVAL);
337
338	/* If the path is not absolute prepend it with a prefix */
339	if (prefix != NULL && entry[0] != '/') {
340		rc = snprintf(path, MAXPATHLEN, "%s/%s",
341			    prefix, entry);
342		if (rc < 0)
343			return (-rc);
344	} else {
345		strcpy(path, entry);
346	}
347
348	rc = open_file(path, &nid);
349	NDFREE_PNBUF(&nid);
350	if (rc != 0)
351		return (rc);
352
353	rc = VOP_GETATTR(nid.ni_vp, &va, curthread->td_ucred);
354	if (rc != 0)
355		goto out;
356
357	is_exec = (va.va_mode & VEXEC);
358
359	mtx_lock(&ve_mutex);
360	rc = mac_veriexec_metadata_add_file(
361	    is_exec == 0,
362	    va.va_fsid, va.va_fileid, va.va_gen,
363	    digest,
364	    NULL, 0,
365	    flags, fp_type, 1);
366	mtx_unlock(&ve_mutex);
367
368out:
369	VOP_UNLOCK(nid.ni_vp);
370	vn_close(nid.ni_vp, FREAD, curthread->td_ucred, curthread);
371	return (rc);
372}
373
374/*
375 * Look for manifest in env that have beed passed by loader.
376 * This routine should be called right after the rootfs is mounted.
377 */
378static int
379parse_manifest(char *path, unsigned char *hash, char *prefix)
380{
381	char	*data;
382	char	*entry;
383	char	*next_entry;
384	int	rc, success_count;
385
386	data = NULL;
387	success_count = 0;
388	rc = 0;
389
390	data = read_manifest(path, hash);
391	if (data == NULL) {
392		rc = EIO;
393		goto out;
394	}
395
396	entry = data;
397	while (entry != NULL) {
398		next_entry = strchr(entry, '\n');
399		if (next_entry != NULL) {
400			*next_entry = '\0';
401			next_entry++;
402		}
403		if (entry[0] == '\n' || entry[0] == '\0') {
404			entry = next_entry;
405			continue;
406		}
407		if ((rc = parse_entry(entry, prefix)))
408			printf("mac_veriexec_parser: Warning: Failed to parse"
409			       " entry with rc:%d, entry:\"%s\"\n", rc, entry);
410		else
411			success_count++;
412
413		entry = next_entry;
414	}
415	rc = 0;
416
417out:
418	if (data != NULL)
419		free(data, M_VERIEXEC);
420
421	if (success_count == 0)
422		rc = EINVAL;
423
424	return (rc);
425}
426
427static void
428parse_manifest_event(void *dummy)
429{
430	char		*manifest_path;
431	char		*manifest_prefix;
432	unsigned char	*manifest_hash;
433	int		rc;
434
435	/* If the envs are not set fail silently */
436	manifest_path = kern_getenv("veriexec.manifest_path");
437	if (manifest_path == NULL)
438		return;
439
440	manifest_hash = kern_getenv("veriexec.manifest_hash");
441	if (manifest_hash == NULL) {
442		freeenv(manifest_path);
443		return;
444	}
445
446	manifest_prefix = kern_getenv("veriexec.manifest_prefix");
447
448	if (strlen(manifest_hash) != 2 * SHA256_DIGEST_LENGTH)
449		panic("veriexec.manifest_hash has incorrect size");
450
451	rc = hexstring_to_bin(manifest_hash);
452	if (rc != 0)
453		panic("mac_veriexec: veriexec.loader.manifest_hash"
454		    " doesn't contain a hash in hexadecimal form");
455
456	rc = parse_manifest(manifest_path, manifest_hash, manifest_prefix);
457	if (rc != 0)
458		panic("mac_veriexec: Failed to parse manifest err=%d", rc);
459
460	mtx_lock(&ve_mutex);
461	mac_veriexec_set_state(
462	    VERIEXEC_STATE_LOADED | VERIEXEC_STATE_ACTIVE |
463	    VERIEXEC_STATE_LOCKED | VERIEXEC_STATE_ENFORCE);
464	mtx_unlock(&ve_mutex);
465
466	freeenv(manifest_path);
467	freeenv(manifest_hash);
468	if (manifest_prefix != NULL)
469		freeenv(manifest_prefix);
470}
471
472EVENTHANDLER_DEFINE(mountroot, parse_manifest_event, NULL, 0);
473