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