]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/security/mac_veriexec_parser/mac_veriexec_parser.c
Upgrade to version 3.1.5
[FreeBSD/FreeBSD.git] / sys / security / mac_veriexec_parser / mac_veriexec_parser.c
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);