4 * Copyright (c) 2011, 2012, 2013, 2015, 2016, 2019, Juniper Networks, Inc.
7 * Originally derived from:
8 * $NetBSD: kern_verifiedexec.c,v 1.7 2003/11/18 13:13:03 martin Exp $
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 #include <sys/cdefs.h>
36 #include <sys/param.h>
37 #include <sys/systm.h>
40 #include <sys/malloc.h>
41 #include <sys/mutex.h>
44 #include <sys/vnode.h>
46 #include "mac_veriexec.h"
47 #include "mac_veriexec_internal.h"
50 * @brief per-device meta-data storage
52 struct veriexec_dev_list {
53 dev_t fsid; /**< file system identifier of the mount point */
54 LIST_HEAD(filehead, mac_veriexec_file_info) file_head;
55 /**< list of per-file meta-data information */
56 LIST_ENTRY(veriexec_dev_list) entries;
57 /**< next entries in the device list */
60 typedef LIST_HEAD(veriexec_devhead, veriexec_dev_list) veriexec_devhead_t;
63 * @brief Mutex to protect the meta-data store lists
68 * @brief Executables meta-data storage
70 * This is used to store the fingerprints for potentially-executable files.
72 veriexec_devhead_t veriexec_dev_head;
75 * @brief Plain file meta-data storage
77 * This is used for files that are not allowed to be executed, but should
78 * have fingerprint validation available.
80 veriexec_devhead_t veriexec_file_dev_head;
84 * @brief Search the @p head meta-data list for the specified file identifier
85 * @p fileid in the file system identified by @p fsid
87 * If meta-data exists for file system identified by @p fsid, it has a
88 * fingerprint list, and @p found_dev is not @c NULL then store true in the
89 * location pointed to by @p found_dev
91 * @param head meta-data list to search
92 * @param fsid file system identifier to look for
93 * @param fileid file to look for
94 * @param gen generation of file
95 * @param found_dev indicator that an entry for the file system was found
97 * @return A pointer to the meta-data inforation if meta-data exists for
98 * the specified file identifier, otherwise @c NULL
100 static struct mac_veriexec_file_info *
101 get_veriexec_file(struct veriexec_devhead *head, dev_t fsid, long fileid,
102 unsigned long gen, int *found_dev)
104 struct veriexec_dev_list *lp;
105 struct mac_veriexec_file_info *ip, *tip;
109 /* Initialize the value found_dev, if non-NULL */
110 if (found_dev != NULL)
113 VERIEXEC_DEBUG(3, ("searching for file %ju.%lu on device %ju,"
114 " files=%d\n", (uintmax_t)fileid, gen, (uintmax_t)fsid,
115 (head == &veriexec_file_dev_head)));
117 /* Get a lock to access the list */
120 /* First, look for the file system */
121 for (lp = LIST_FIRST(head); lp != NULL; lp = LIST_NEXT(lp, entries))
122 if (lp->fsid == fsid)
125 /* We found the file system in the list */
127 VERIEXEC_DEBUG(3, ("found matching dev number %ju\n",
128 (uintmax_t)lp->fsid));
130 /* If found_dev is non-NULL, store true there */
131 if (found_dev != NULL)
134 /* Next, look for the meta-data information for the file */
135 LIST_FOREACH_SAFE(ip, &(lp->file_head), entries, tip) {
136 if (ip->fileid == fileid) {
139 /* we need to garbage collect */
140 LIST_REMOVE(ip, entries);
142 free(ip->label, M_VERIEXEC);
143 free(ip, M_VERIEXEC);
148 /* Release the lock we obtained earlier */
149 mtx_unlock(&ve_mutex);
151 /* Return the meta-data information we found, if anything */
157 * @brief Display the fingerprint for each entry in the device list
159 * @param sbp sbuf to write output to
160 * @param lp pointer to device list
163 mac_veriexec_print_db_dev_list(struct sbuf *sbp, struct veriexec_dev_list *lp)
165 struct mac_veriexec_file_info *ip;
167 #define FPB(i) (ip->fingerprint[i])
168 for (ip = LIST_FIRST(&(lp->file_head)); ip != NULL;
169 ip = LIST_NEXT(ip, entries))
170 sbuf_printf(sbp, " %ld: %u %ld [%02x %02x %02x %02x %02x "
171 "%02x %02x %02x...]\n", ip->fileid, ip->flags, ip->gen,
172 FPB(0), FPB(1), FPB(2), FPB(3), FPB(4), FPB(5), FPB(6),
178 * @brief Display the device list
180 * @param sbp sbuf to write output to
181 * @param head pointer to head of the device list
184 mac_veriexec_print_db_head(struct sbuf *sbp, struct veriexec_devhead *head)
186 struct veriexec_dev_list *lp;
188 for (lp = LIST_FIRST(head); lp != NULL; lp = LIST_NEXT(lp, entries)) {
189 sbuf_printf(sbp, " FS id: %ju\n", (uintmax_t)lp->fsid);
190 mac_veriexec_print_db_dev_list(sbp, lp);
197 * @brief Generate human-readable output for the current fingerprint database
199 * @param sbp sbuf to write output to
202 mac_veriexec_metadata_print_db(struct sbuf *sbp)
205 struct veriexec_devhead *h;
208 { &veriexec_file_dev_head, "regular files" },
209 { &veriexec_dev_head, "executable files" },
214 for (i = 0; i < sizeof(fpdbs)/sizeof(fpdbs[0]); i++) {
215 sbuf_printf(sbp, "%s fingerprint db:\n", fpdbs[i].name);
216 mac_veriexec_print_db_head(sbp, fpdbs[i].h);
218 mtx_unlock(&ve_mutex);
221 * @brief Determine if the meta-data store has an entry for the specified file.
223 * @param fsid file system identifier to look for
224 * @param fileid file to look for
225 * @param gen generation of file
227 * @return 1 if there is an entry in the meta-data store, 0 otherwise.
230 mac_veriexec_metadata_has_file(dev_t fsid, long fileid, unsigned long gen)
233 return (mac_veriexec_metadata_get_file_info(fsid, fileid, gen, NULL,
234 VERIEXEC_FILES_FIRST) != NULL);
238 * @brief Search the list of devices looking for the one given, in order to
239 * release the resources used by it.
241 * If found, free all file entries for it, and remove it from the list.
243 * @note Called with @a ve_mutex held
245 * @param fsid file system identifier to look for
246 * @param head meta-data list to search
248 * @return 0 if the device entry was freed, otherwise an error code
251 free_veriexec_dev(dev_t fsid, struct veriexec_devhead *head)
253 struct veriexec_dev_list *lp;
254 struct mac_veriexec_file_info *ip, *nip;
256 /* Look for the file system */
257 for (lp = LIST_FIRST(head); lp != NULL;
258 lp = LIST_NEXT(lp, entries))
259 if (lp->fsid == fsid) break;
261 /* If lp is NULL, we did not find it */
265 /* Unhook lp, before we free it and its content */
266 LIST_REMOVE(lp, entries);
268 /* Release the lock */
269 mtx_unlock(&ve_mutex);
271 /* Free the file entries in the list */
272 for (ip = LIST_FIRST(&(lp->file_head)); ip != NULL; ip = nip) {
273 nip = LIST_NEXT(ip, entries);
274 LIST_REMOVE(ip, entries);
276 free(ip->label, M_VERIEXEC);
277 free(ip, M_VERIEXEC);
280 /* Free the meta-data entry for the device */
281 free(lp, M_VERIEXEC);
283 /* Re-acquire the lock */
289 * @brief Search the list of devices looking for the one given.
291 * If it is not in the list then add it.
293 * @note Called with @a ve_mutex held
295 * @param fsid file system identifier to look for
296 * @param head meta-data list to search
298 * @return A pointer to the meta-data entry for the device, if found or added,
301 static struct veriexec_dev_list *
302 find_veriexec_dev(dev_t fsid, struct veriexec_devhead *head)
304 struct veriexec_dev_list *lp;
305 struct veriexec_dev_list *np = NULL;
308 /* Look for the file system */
309 for (lp = LIST_FIRST(head); lp != NULL;
310 lp = LIST_NEXT(lp, entries))
311 if (lp->fsid == fsid) break;
316 * If pointer is null then entry not there,
317 * add a new one, first try to malloc while
318 * we hold mutex - should work most of the time.
320 np = malloc(sizeof(struct veriexec_dev_list),
321 M_VERIEXEC, M_NOWAIT);
324 * So much for that plan, dop the mutex
327 mtx_unlock(&ve_mutex);
328 np = malloc(sizeof(struct veriexec_dev_list),
329 M_VERIEXEC, M_WAITOK);
332 * Repeat the seach, in case someone
333 * added this while we slept.
339 /* Add the entry to the list */
341 LIST_INIT(&(lp->file_head));
343 LIST_INSERT_HEAD(head, lp, entries);
347 * Someone else did it while we slept.
349 mtx_unlock(&ve_mutex);
350 free(np, M_VERIEXEC);
359 * @brief Allocate and initialize label record with the provided data.
361 * @param labelp Location to store the initialized label
362 * @param src Pointer to label string to copy
363 * @param srclen Length of label string to copy
365 * @return Length of resulting label
367 * @note Called with ve_mutex locked.
370 mac_veriexec_init_label(char **labelp, size_t labellen, char *src,
376 if (labellen < srclen) {
377 mtx_unlock(&ve_mutex);
379 free(label, M_VERIEXEC);
380 label = malloc(srclen, M_VERIEXEC, M_WAITOK);
385 memcpy(label, src, srclen);
390 * @brief When a device is unmounted, we want to toss the signatures recorded
393 * We are being called from unmount() with the root vnode just before it is
396 * @param fsid file system identifier to look for
397 * @param td calling thread
399 * @return 0 on success, otherwise an error code.
402 mac_veriexec_metadata_unmounted(dev_t fsid, struct thread *td)
407 * The device can have entries on both lists.
410 error = free_veriexec_dev(fsid, &veriexec_dev_head);
411 if (error && error != ENOENT) {
412 mtx_unlock(&ve_mutex);
415 error = free_veriexec_dev(fsid, &veriexec_file_dev_head);
416 mtx_unlock(&ve_mutex);
417 if (error && error != ENOENT) {
424 * @brief Return the flags assigned to the file identified by file system
425 * identifier @p fsid and file identifier @p fileid.
427 * @param fsid file system identifier
428 * @param fileid file identifier within the file system
429 * @param gen generation of file
430 * @param flags pointer to location to store the flags
431 * @param check_files if 1, check the files list first, otherwise check the
432 * exectuables list first
434 * @return 0 on success, otherwise an error code.
437 mac_veriexec_metadata_get_file_flags(dev_t fsid, long fileid, unsigned long gen,
438 int *flags, int check_files)
440 struct mac_veriexec_file_info *ip;
443 ip = mac_veriexec_metadata_get_file_info(fsid, fileid, gen, &found_dev,
453 * @brief get the files for the specified process
455 * @param cred credentials to use
456 * @param p process to get the flags for
457 * @param flags where to store the flags
458 * @param check_files if 1, check the files list first, otherwise check the
459 * exectuables list first
461 * @return 0 if the process has an entry in the meta-data store, otherwise an
465 mac_veriexec_metadata_get_executable_flags(struct ucred *cred, struct proc *p,
466 int *flags, int check_files)
468 struct vnode *proc_vn;
472 /* Get the text vnode for the process */
473 proc_vn = p->p_textvp;
477 /* Get vnode attributes */
478 error = VOP_GETATTR(proc_vn, &vap, cred);
482 error = mac_veriexec_metadata_get_file_flags(vap.va_fsid,
483 vap.va_fileid, vap.va_gen, flags,
484 (check_files == VERIEXEC_FILES_FIRST));
490 * @brief Ensure the fingerprint status for the vnode @p vp is assigned to its
493 * @param vp vnode to check
494 * @param vap vnode attributes to use
495 * @param td calling thread
496 * @param check_files if 1, check the files list first, otherwise check the
497 * exectuables list first
499 * @return 0 on success, otherwise an error code.
502 mac_veriexec_metadata_fetch_fingerprint_status(struct vnode *vp,
503 struct vattr *vap, struct thread *td, int check_files)
505 unsigned char digest[MAXFINGERPRINTLEN];
506 struct mac_veriexec_file_info *ip;
507 int error, found_dev;
508 fingerprint_status_t status;
513 status = mac_veriexec_get_fingerprint_status(vp);
514 if (status == FINGERPRINT_INVALID || status == FINGERPRINT_NODEV) {
516 ip = mac_veriexec_metadata_get_file_info(vap->va_fsid,
517 vap->va_fileid, vap->va_gen, &found_dev, check_files);
519 status = (found_dev) ? FINGERPRINT_NOENTRY :
522 ("fingerprint status is %d for dev %ju, file "
523 "%ju.%lu\n", status, (uintmax_t)vap->va_fsid,
524 (uintmax_t)vap->va_fileid, vap->va_gen));
527 * evaluate and compare fingerprint
529 error = mac_veriexec_fingerprint_check_vnode(vp, ip,
530 td, vap->va_size, digest);
534 if ((ip->flags & VERIEXEC_INDIRECT))
535 status = FINGERPRINT_INDIRECT;
536 else if ((ip->flags & VERIEXEC_FILE))
537 status = FINGERPRINT_FILE;
539 status = FINGERPRINT_VALID;
541 ("%sfingerprint matches for dev %ju, file "
543 (status == FINGERPRINT_INDIRECT) ?
545 (status == FINGERPRINT_FILE) ?
546 "file " : "", (uintmax_t)vap->va_fsid,
547 (uintmax_t)vap->va_fileid, vap->va_gen));
551 #ifdef VERIFIED_EXEC_DEBUG_VERBOSE
553 char have[MAXFINGERPRINTLEN * 2 + 1];
554 char want[MAXFINGERPRINTLEN * 2 + 1];
557 len = ip->ops->digest_len;
558 for (i = 0; i < len; i++) {
559 sprintf(&want[i * 2], "%02x",
561 sprintf(&have[i * 2], "%02x",
564 log(LOG_ERR, MAC_VERIEXEC_FULLNAME
565 ": fingerprint for dev %ju, file "
566 "%ju.%lu %s != %s\n",
567 (uintmax_t)vap->va_fsid,
568 (uintmax_t)vap->va_fileid,
573 status = FINGERPRINT_NOMATCH;
577 ("fingerprint status error %d\n", error));
581 mac_veriexec_set_fingerprint_status(vp, status);
587 * Add a file and its fingerprint to the list of files attached
588 * to the device @p fsid.
590 * Only add the entry if it is not already on the list.
592 * @note Called with @a ve_mutex held
594 * @param file_dev if 1, the entry should be added on the file list,
595 * otherwise it should be added on the executable list
596 * @param fsid file system identifier of device
597 * @param fileid file to add
598 * @param gen generation of file
599 * @param fingerprint fingerprint to add to the store
600 * @param flags flags to set in the store
601 * @param fp_type digest type
602 * @param override if 1, override any values already stored
604 * @return 0 on success, otherwise an error code.
607 mac_veriexec_metadata_add_file(int file_dev, dev_t fsid, long fileid,
608 unsigned long gen, unsigned char fingerprint[MAXFINGERPRINTLEN],
609 char *label, size_t labellen, int flags, const char *fp_type, int override)
611 struct mac_veriexec_fpops *fpops;
612 struct veriexec_dev_list *lp;
613 struct veriexec_devhead *head;
614 struct mac_veriexec_file_info *ip;
615 struct mac_veriexec_file_info *np = NULL;
617 /* Label and labellen must be set if VERIEXEC_LABEL is set */
618 if ((flags & VERIEXEC_LABEL) != 0 && (label == NULL || labellen == 0))
621 /* Look up the device entry */
623 head = &veriexec_file_dev_head;
625 head = &veriexec_dev_head;
626 lp = find_veriexec_dev(fsid, head);
628 /* Look up the fingerprint operations for the digest type */
629 fpops = mac_veriexec_fingerprint_lookup_ops(fp_type);
634 for (ip = LIST_FIRST(&(lp->file_head)); ip != NULL;
635 ip = LIST_NEXT(ip, entries)) {
636 /* check for a dupe file in the list, skip if an entry
637 * exists for this file except for when the flags contains
638 * VERIEXEC_INDIRECT, always set the flags when it is so
639 * we don't get a hole caused by conflicting flags on
640 * hardlinked files. XXX maybe we should validate
641 * fingerprint is same and complain if it is not...
643 if (ip->fileid == fileid && ip->gen == gen) {
646 * for a signed load we allow overrides,
647 * otherwise fingerpints needed for pkg loads
648 * can fail (the files are on temp device).
652 memcpy(ip->fingerprint, fingerprint,
654 if (flags & VERIEXEC_LABEL) {
655 ip->labellen = mac_veriexec_init_label(
656 &ip->label, ip->labellen, label,
658 } else if (ip->labellen > 0) {
659 free(ip->label, M_VERIEXEC);
663 } else if ((flags & (VERIEXEC_INDIRECT|VERIEXEC_FILE)))
667 /* unlikely but... we don't need it now. */
668 mtx_unlock(&ve_mutex);
669 free(np, M_VERIEXEC);
677 * We may have been past here before...
681 * We first try with mutex held and nowait.
683 np = malloc(sizeof(struct mac_veriexec_file_info), M_VERIEXEC,
687 * It was worth a try, now
688 * drop mutex while we malloc.
690 mtx_unlock(&ve_mutex);
691 np = malloc(sizeof(struct mac_veriexec_file_info),
692 M_VERIEXEC, M_WAITOK);
695 * We now have to repeat our search!
701 /* Set up the meta-data entry */
707 memcpy(ip->fingerprint, fingerprint, fpops->digest_len);
708 if (flags & VERIEXEC_LABEL)
709 ip->labellen = mac_veriexec_init_label(&ip->label,
710 ip->labellen, label, labellen);
716 VERIEXEC_DEBUG(3, ("add file %ju.%lu (files=%d)\n",
717 (uintmax_t)ip->fileid,
720 /* Add the entry to the list */
721 LIST_INSERT_HEAD(&(lp->file_head), ip, entries);
722 #ifdef DEBUG_VERIEXEC_FINGERPRINT
726 printf("Stored %s fingerprint:\n", fp_type);
727 for (offset = 0; offset < fpops->digest_len; offset++)
728 printf("%02x", fingerprint[offset]);
736 * @brief Search the meta-data store for information on the specified file.
738 * @param fsid file system identifier to look for
739 * @param fileid file to look for
740 * @param gen generation of file
741 * @param found_dev indicator that an entry for the file system was found
742 * @param check_files if 1, check the files list first, otherwise check the
743 * exectuables list first
745 * @return A pointer to the meta-data inforation if meta-data exists for
746 * the specified file identifier, otherwise @c NULL
748 struct mac_veriexec_file_info *
749 mac_veriexec_metadata_get_file_info(dev_t fsid, long fileid, unsigned long gen,
750 int *found_dev, int check_files)
752 struct veriexec_devhead *search[3];
753 struct mac_veriexec_file_info *ip;
756 /* Determine the order of the lists to search */
758 search[0] = &veriexec_file_dev_head;
759 search[1] = &veriexec_dev_head;
761 search[0] = &veriexec_dev_head;
762 search[1] = &veriexec_file_dev_head;
766 VERIEXEC_DEBUG(3, ("%s: searching for dev %ju, file %lu\n",
767 __func__, (uintmax_t)fsid, fileid));
769 /* Search for the specified file */
770 for (ip = NULL, x = 0; ip == NULL && search[x]; x++)
771 ip = get_veriexec_file(search[x], fsid, fileid, gen, found_dev);
778 * @brief Intialize the meta-data store
781 mac_veriexec_metadata_init(void)
784 mtx_init(&ve_mutex, "veriexec lock", NULL, MTX_DEF);
785 LIST_INIT(&veriexec_dev_head);
786 LIST_INIT(&veriexec_file_dev_head);