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