xref: /freebsd/sys/security/mac_veriexec_parser/mac_veriexec_parser.c (revision 6e45b50342d5adadf9dd08e3476fc90f715be1fc)
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 */
47 struct fingerprint_type {
48 	const char	*fp_type;
49 	int		fp_size;
50 };
51 
52 struct fp_flag {
53 	const char	*flag_name;
54 	int		flag;
55 };
56 
57 static 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 
68 static 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 
76 extern struct mtx ve_mutex;
77 
78 static unsigned char	hexchar_to_byte(unsigned char c);
79 static int		hexstring_to_bin(unsigned char *buf);
80 
81 static int	get_flags(const char *entry);
82 static int	get_fp(const char *entry, char **type,
83 		    unsigned char **digest, int *flags);
84 static int	verify_digest(const char *data, size_t len,
85 		    const unsigned char *expected_hash);
86 
87 static int	open_file(const char *path, struct nameidata *nid);
88 static char	*read_manifest(char *path, unsigned char *digest);
89 static int	parse_entry(char *entry, char *prefix);
90 static int	parse_manifest(char *path, unsigned char *hash, char *prefix);
91 
92 static unsigned char
hexchar_to_byte(unsigned char c)93 hexchar_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 
102 static int
hexstring_to_bin(unsigned char * buf)103 hexstring_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 
120 static int
get_flags(const char * entry)121 get_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  */
138 static int
get_fp(const char * entry,char ** type,unsigned char ** digest,int * flags)139 get_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  */
220 static int
verify_digest(const char * data,size_t len,const unsigned char * expected_hash)221 verify_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 
233 static int
open_file(const char * path,struct nameidata * nid)234 open_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 	if (rc != 0)
245 		return (rc);
246 	NDFREE_PNBUF(nid);
247 
248 	return (0);
249 }
250 
251 /*
252  * Read the manifest from location specified in path and verify its digest.
253  */
254 static char*
read_manifest(char * path,unsigned char * digest)255 read_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 
301 fail:
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  */
313 static int
parse_entry(char * entry,char * prefix)314 parse_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 	if (rc != 0)
350 		return (rc);
351 
352 	rc = VOP_GETATTR(nid.ni_vp, &va, curthread->td_ucred);
353 	if (rc != 0)
354 		goto out;
355 
356 	is_exec = (va.va_mode & VEXEC);
357 
358 	mtx_lock(&ve_mutex);
359 	rc = mac_veriexec_metadata_add_file(
360 	    is_exec == 0,
361 	    va.va_fsid, va.va_fileid, va.va_gen,
362 	    digest,
363 	    NULL, 0,
364 	    flags, fp_type, 1);
365 	mtx_unlock(&ve_mutex);
366 
367 out:
368 	VOP_UNLOCK(nid.ni_vp);
369 	vn_close(nid.ni_vp, FREAD, curthread->td_ucred, curthread);
370 	return (rc);
371 }
372 
373 /*
374  * Look for manifest in env that have beed passed by loader.
375  * This routine should be called right after the rootfs is mounted.
376  */
377 static int
parse_manifest(char * path,unsigned char * hash,char * prefix)378 parse_manifest(char *path, unsigned char *hash, char *prefix)
379 {
380 	char	*data;
381 	char	*entry;
382 	char	*next_entry;
383 	int	rc, success_count;
384 
385 	data = NULL;
386 	success_count = 0;
387 	rc = 0;
388 
389 	data = read_manifest(path, hash);
390 	if (data == NULL) {
391 		rc = EIO;
392 		goto out;
393 	}
394 
395 	entry = data;
396 	while (entry != NULL) {
397 		next_entry = strchr(entry, '\n');
398 		if (next_entry != NULL) {
399 			*next_entry = '\0';
400 			next_entry++;
401 		}
402 		if (entry[0] == '\n' || entry[0] == '\0') {
403 			entry = next_entry;
404 			continue;
405 		}
406 		if ((rc = parse_entry(entry, prefix)))
407 			printf("mac_veriexec_parser: Warning: Failed to parse"
408 			       " entry with rc:%d, entry:\"%s\"\n", rc, entry);
409 		else
410 			success_count++;
411 
412 		entry = next_entry;
413 	}
414 	rc = 0;
415 
416 out:
417 	if (data != NULL)
418 		free(data, M_VERIEXEC);
419 
420 	if (success_count == 0)
421 		rc = EINVAL;
422 
423 	return (rc);
424 }
425 
426 static void
parse_manifest_event(void * dummy)427 parse_manifest_event(void *dummy)
428 {
429 	char		*manifest_path;
430 	char		*manifest_prefix;
431 	unsigned char	*manifest_hash;
432 	int		rc;
433 
434 	/* If the envs are not set fail silently */
435 	manifest_path = kern_getenv("veriexec.manifest_path");
436 	if (manifest_path == NULL)
437 		return;
438 
439 	manifest_hash = kern_getenv("veriexec.manifest_hash");
440 	if (manifest_hash == NULL) {
441 		freeenv(manifest_path);
442 		return;
443 	}
444 
445 	manifest_prefix = kern_getenv("veriexec.manifest_prefix");
446 
447 	if (strlen(manifest_hash) != 2 * SHA256_DIGEST_LENGTH)
448 		panic("veriexec.manifest_hash has incorrect size");
449 
450 	rc = hexstring_to_bin(manifest_hash);
451 	if (rc != 0)
452 		panic("mac_veriexec: veriexec.loader.manifest_hash"
453 		    " doesn't contain a hash in hexadecimal form");
454 
455 	rc = parse_manifest(manifest_path, manifest_hash, manifest_prefix);
456 	if (rc != 0)
457 		panic("mac_veriexec: Failed to parse manifest err=%d", rc);
458 
459 	mtx_lock(&ve_mutex);
460 	mac_veriexec_set_state(
461 	    VERIEXEC_STATE_LOADED | VERIEXEC_STATE_ACTIVE |
462 	    VERIEXEC_STATE_LOCKED | VERIEXEC_STATE_ENFORCE);
463 	mtx_unlock(&ve_mutex);
464 
465 	freeenv(manifest_path);
466 	freeenv(manifest_hash);
467 	if (manifest_prefix != NULL)
468 		freeenv(manifest_prefix);
469 }
470 
471 EVENTHANDLER_DEFINE(mountroot, parse_manifest_event, NULL, 0);
472