Index: head/sys/dev/veriexec/veriexec_ioctl.h =================================================================== --- head/sys/dev/veriexec/veriexec_ioctl.h (revision 347941) +++ head/sys/dev/veriexec/veriexec_ioctl.h (revision 347942) @@ -1,61 +1,67 @@ /* * $FreeBSD$ * * Copyright (c) 2011-2013, 2015, 2019, Juniper Networks, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * * Definitions for the Verified Executables kernel function. * */ #ifndef _DEV_VERIEXEC_VERIEXEC_IOCTL_H #define _DEV_VERIEXEC_VERIEXEC_IOCTL_H #include #include #define VERIEXEC_FPTYPELEN 16 struct verified_exec_params { unsigned char flags; char fp_type[VERIEXEC_FPTYPELEN]; /* type of fingerprint */ char file[MAXPATHLEN]; unsigned char fingerprint[MAXFINGERPRINTLEN]; }; +struct verified_exec_label_params { + struct verified_exec_params params; + char label[MAXLABELLEN]; +}; + #define VERIEXEC_LOAD _IOW('S', 0x1, struct verified_exec_params) #define VERIEXEC_ACTIVE _IO('S', 0x2) /* start checking */ #define VERIEXEC_ENFORCE _IO('S', 0x3) /* fail exec */ #define VERIEXEC_LOCK _IO('S', 0x4) /* don't allow new sigs */ #define VERIEXEC_DEBUG_ON _IOWR('S', 0x5, int) /* set/get debug level */ #define VERIEXEC_DEBUG_OFF _IO('S', 0x6) /* reset debug */ #define VERIEXEC_GETSTATE _IOR('S', 0x7, int) /* get state */ #define VERIEXEC_SIGNED_LOAD _IOW('S', 0x8, struct verified_exec_params) #define VERIEXEC_GETVERSION _IOR('S', 0x9, int) /* get version */ +#define VERIEXEC_LABEL_LOAD _IOW('S', 0xa, struct verified_exec_label_params) #define _PATH_DEV_VERIEXEC _PATH_DEV "veriexec" #endif Index: head/sys/dev/veriexec/verified_exec.c =================================================================== --- head/sys/dev/veriexec/verified_exec.c (revision 347941) +++ head/sys/dev/veriexec/verified_exec.c (revision 347942) @@ -1,256 +1,269 @@ /* * $FreeBSD$ * * Copyright (c) 2011-2013, 2015, 2019 Juniper Networks, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "veriexec_ioctl.h" /* * We need a mutex while updating lists etc. */ extern struct mtx ve_mutex; /* * Handle the ioctl for the device */ static int verifiedexecioctl(struct cdev *dev __unused, u_long cmd, caddr_t data, int flags, struct thread *td) { struct nameidata nid; struct vattr vattr; + struct verified_exec_label_params *lparams; struct verified_exec_params *params; int error = 0; /* * These commands are considered safe requests for anyone who has * permission to access to device node. */ switch (cmd) { case VERIEXEC_GETSTATE: { int *ip = (int *)data; if (ip) *ip = mac_veriexec_get_state(); else error = EINVAL; return (error); } break; default: break; } /* * Anything beyond this point is considered dangerous, so we need to * only allow processes that have kmem write privs to do them. * * MAC/veriexec will grant kmem write privs to "trusted" processes. */ error = priv_check(td, PRIV_KMEM_WRITE); if (error) return (error); - params = (struct verified_exec_params *)data; + lparams = (struct verified_exec_label_params *)data; + if (cmd == VERIEXEC_LABEL_LOAD) + params = &lparams->params; + else + params = (struct verified_exec_params *)data; + switch (cmd) { case VERIEXEC_ACTIVE: mtx_lock(&ve_mutex); if (mac_veriexec_in_state(VERIEXEC_STATE_LOADED)) mac_veriexec_set_state(VERIEXEC_STATE_ACTIVE); else error = EINVAL; mtx_unlock(&ve_mutex); break; case VERIEXEC_DEBUG_ON: mtx_lock(&ve_mutex); { int *ip = (int *)data; mac_veriexec_debug++; if (ip) { if (*ip > 0) mac_veriexec_debug = *ip; *ip = mac_veriexec_debug; } } mtx_unlock(&ve_mutex); break; case VERIEXEC_DEBUG_OFF: mac_veriexec_debug = 0; break; case VERIEXEC_ENFORCE: mtx_lock(&ve_mutex); if (mac_veriexec_in_state(VERIEXEC_STATE_LOADED)) mac_veriexec_set_state(VERIEXEC_STATE_ACTIVE | VERIEXEC_STATE_ENFORCE); else error = EINVAL; mtx_unlock(&ve_mutex); break; case VERIEXEC_GETVERSION: { int *ip = (int *)data; if (ip) *ip = MAC_VERIEXEC_VERSION; else error = EINVAL; } break; case VERIEXEC_LOCK: mtx_lock(&ve_mutex); mac_veriexec_set_state(VERIEXEC_STATE_LOCKED); mtx_unlock(&ve_mutex); break; case VERIEXEC_LOAD: if (prison0.pr_securelevel > 0) return (EPERM); /* no updates when secure */ /* FALLTHROUGH */ + case VERIEXEC_LABEL_LOAD: case VERIEXEC_SIGNED_LOAD: /* * If we use a loader that will only use a * digitally signed hash list - which it verifies. * We can load fingerprints provided veriexec is not locked. */ if (prison0.pr_securelevel > 0 && !mac_veriexec_in_state(VERIEXEC_STATE_LOADED)) { /* * If securelevel has been raised and we * do not have any fingerprints loaded, * it would dangerous to do so now. */ return (EPERM); } if (mac_veriexec_in_state(VERIEXEC_STATE_LOCKED)) error = EPERM; else { + size_t labellen = 0; int flags = FREAD; - int override = (cmd == VERIEXEC_SIGNED_LOAD); + int override = (cmd != VERIEXEC_LOAD); /* * Get the attributes for the file name passed * stash the file's device id and inode number * along with it's fingerprint in a list for * exec to use later. */ /* * FreeBSD seems to copy the args to kernel space */ NDINIT(&nid, LOOKUP, FOLLOW, UIO_SYSSPACE, params->file, td); if ((error = vn_open(&nid, &flags, 0, NULL)) != 0) return (error); error = VOP_GETATTR(nid.ni_vp, &vattr, td->td_ucred); if (error != 0) { mac_veriexec_set_fingerprint_status(nid.ni_vp, FINGERPRINT_INVALID); VOP_UNLOCK(nid.ni_vp, 0); (void) vn_close(nid.ni_vp, FREAD, td->td_ucred, td); return (error); } if (override) { /* * If the file is on a "verified" filesystem * someone may be playing games. */ if ((nid.ni_vp->v_mount->mnt_flag & MNT_VERIFIED) != 0) override = 0; } /* * invalidate the node fingerprint status * which will have been set in the vn_open * and would always be FINGERPRINT_NOTFOUND */ mac_veriexec_set_fingerprint_status(nid.ni_vp, FINGERPRINT_INVALID); VOP_UNLOCK(nid.ni_vp, 0); (void) vn_close(nid.ni_vp, FREAD, td->td_ucred, td); + if (params->flags & VERIEXEC_LABEL) + labellen = strnlen(lparams->label, + sizeof(lparams->label) - 1) + 1; mtx_lock(&ve_mutex); error = mac_veriexec_metadata_add_file( ((params->flags & VERIEXEC_FILE) != 0), vattr.va_fsid, vattr.va_fileid, vattr.va_gen, - params->fingerprint, params->flags, - params->fp_type, override); + params->fingerprint, + (params->flags & VERIEXEC_LABEL) ? + lparams->label : NULL, labellen, + params->flags, params->fp_type, override); mac_veriexec_set_state(VERIEXEC_STATE_LOADED); mtx_unlock(&ve_mutex); } break; default: error = ENODEV; } return (error); } struct cdevsw veriexec_cdevsw = { .d_version = D_VERSION, .d_ioctl = verifiedexecioctl, .d_name = "veriexec", }; static void veriexec_drvinit(void *unused __unused) { make_dev(&veriexec_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "veriexec"); } SYSINIT(veriexec, SI_SUB_PSEUDO, SI_ORDER_ANY, veriexec_drvinit, NULL); MODULE_DEPEND(veriexec, mac_veriexec, 1, 1, 1); Index: head/sys/security/mac_veriexec/mac_veriexec.h =================================================================== --- head/sys/security/mac_veriexec/mac_veriexec.h (revision 347941) +++ head/sys/security/mac_veriexec/mac_veriexec.h (revision 347942) @@ -1,161 +1,163 @@ /* * $FreeBSD$ * - * Copyright (c) 2011, 2012, 2013, 2015, 2016, Juniper Networks, Inc. + * Copyright (c) 2011, 2012, 2013, 2015, 2016, 2019, Juniper Networks, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _SECURITY_MAC_VERIEXEC_H #define _SECURITY_MAC_VERIEXEC_H #ifdef _KERNEL #include #include #include #include #endif /** * Name of the MAC module */ #define MAC_VERIEXEC_NAME "mac_veriexec" /* MAC/veriexec syscalls */ #define MAC_VERIEXEC_CHECK_FD_SYSCALL 1 #define MAC_VERIEXEC_CHECK_PATH_SYSCALL 2 /** * Enough room for the largest signature... */ #define MAXFINGERPRINTLEN 64 /* enough room for largest signature */ +#define MAXLABELLEN 128 /* * Types of veriexec inodes we can have */ #define VERIEXEC_INDIRECT (1<<0) /* Only allow indirect execution */ #define VERIEXEC_FILE (1<<1) /* Fingerprint of a plain file */ #define VERIEXEC_NOTRACE (1<<2) /**< PTRACE not allowed */ #define VERIEXEC_TRUSTED (1<<3) /**< Safe to write /dev/mem */ -/* XXX these are currently unimplemented */ #define VERIEXEC_NOFIPS (1<<4) /**< Not allowed in FIPS mode */ +#define VERIEXEC_LABEL (1<<5) /**< We have a label */ #define VERIEXEC_STATE_INACTIVE 0 /**< Ignore */ #define VERIEXEC_STATE_LOADED (1<<0) /**< Sigs have been loaded */ #define VERIEXEC_STATE_ACTIVE (1<<1) /**< Pay attention to it */ #define VERIEXEC_STATE_ENFORCE (1<<2) /**< Fail execs for files that do not match signature */ #define VERIEXEC_STATE_LOCKED (1<<3) /**< Do not allow further changes */ #ifdef _KERNEL /** * Version of the MAC/veriexec module */ -#define MAC_VERIEXEC_VERSION 1 +#define MAC_VERIEXEC_VERSION 2 /* Valid states for the fingerprint flag - if signed exec is being used */ typedef enum fingerprint_status { FINGERPRINT_INVALID, /**< Fingerprint has not been evaluated */ FINGERPRINT_VALID, /**< Fingerprint evaluated and matches list */ FINGERPRINT_INDIRECT, /**< Fingerprint eval'd/matched but only indirect execs allowed */ FINGERPRINT_FILE, /**< Fingerprint evaluated/matched but not executable */ FINGERPRINT_NOMATCH, /**< Fingerprint evaluated but does not match */ FINGERPRINT_NOENTRY, /**< Fingerprint evaluated but no list entry */ FINGERPRINT_NODEV, /**< Fingerprint evaluated but no dev list */ } fingerprint_status_t; typedef void (*mac_veriexec_fpop_init_t)(void *); typedef void (*mac_veriexec_fpop_update_t)(void *, const uint8_t *, size_t); typedef void (*mac_veriexec_fpop_final_t)(uint8_t *, void *); struct mac_veriexec_fpops { const char *type; size_t digest_len; size_t context_size; mac_veriexec_fpop_init_t init; mac_veriexec_fpop_update_t update; mac_veriexec_fpop_final_t final; LIST_ENTRY(mac_veriexec_fpops) entries; }; /** * Verified execution subsystem debugging level */ extern int mac_veriexec_debug; /** * @brief Define a fingerprint module. * * @param _name Name of the fingerprint module * @param _digest_len Length of the digest string, in number of characters * @param _context_size Size of the context structure, in bytes * @param _init Initialization function of type * mac_veriexec_fpop_init_t * @param _update Update function of type mac_veriexec_fpop_update_t * @param _final Finalize function of type mac_veriexec_fpop_final_t * @param _vers Module version */ #define MAC_VERIEXEC_FPMOD(_name, _digest_len, _context_size, _init, \ _update, _final, _vers) \ static struct mac_veriexec_fpops \ mac_veriexec_##_name##_fpops = { \ .type = #_name, \ .digest_len = _digest_len, \ .context_size = _context_size, \ .init = _init, \ .update = _update, \ .final = _final, \ }; \ static moduledata_t mac_veriexec_##_name##_mod = { \ "mac_veriexec/" #_name, \ mac_veriexec_fingerprint_modevent, \ &(mac_veriexec_##_name##_fpops) \ }; \ MODULE_VERSION(mac_veriexec_##_name, _vers); \ DECLARE_MODULE(mac_veriexec_##_name, \ mac_veriexec_##_name##_mod, SI_SUB_MAC_POLICY, \ SI_ORDER_ANY); \ MODULE_DEPEND(mac_veriexec_##_name, mac_veriexec, \ MAC_VERIEXEC_VERSION, MAC_VERIEXEC_VERSION, \ MAC_VERIEXEC_VERSION) /* * The following function should not be called directly. The prototype is * included here to satisfy the compiler when using the macro above. */ int mac_veriexec_fingerprint_modevent(module_t mod, int type, void *data); /* * Public functions */ int mac_veriexec_metadata_add_file(int file_dev, dev_t fsid, long fileid, unsigned long gen, unsigned char fingerprint[MAXFINGERPRINTLEN], - int flags, const char *fp_type, int override); + char *label, size_t labellen, int flags, const char *fp_type, + int override); int mac_veriexec_metadata_has_file(dev_t fsid, long fileid, unsigned long gen); int mac_veriexec_proc_is_trusted(struct ucred *cred, struct proc *p); #endif #endif /* _SECURITY_MAC_VERIEXEC_H */ Index: head/sys/security/mac_veriexec/mac_veriexec_internal.h =================================================================== --- head/sys/security/mac_veriexec/mac_veriexec_internal.h (revision 347941) +++ head/sys/security/mac_veriexec/mac_veriexec_internal.h (revision 347942) @@ -1,102 +1,107 @@ /* * $FreeBSD$ * - * Copyright (c) 2011, 2012, 2013, 2015, 2016, Juniper Networks, Inc. + * Copyright (c) 2011, 2012, 2013, 2015, 2016, 2019, Juniper Networks, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _SECURITY_MAC_VERIEXEC_INTERNAL_H #define _SECURITY_MAC_VERIEXEC_INTERNAL_H #ifndef _KERNEL #error "no user-serviceable parts inside" #endif #include #include #include #define MAC_VERIEXEC_FULLNAME "MAC/veriexec" #define VERIEXEC_FILES_FIRST 1 #if defined(VERIFIED_EXEC_DEBUG) || defined(VERIFIED_EXEC_DEBUG_VERBOSE) # define VERIEXEC_DEBUG(n, x) if (mac_veriexec_debug > (n)) printf x #else # define VERIEXEC_DEBUG(n, x) #endif struct mac_veriexec_file_info { int flags; long fileid; unsigned long gen; struct mac_veriexec_fpops *ops; unsigned char fingerprint[MAXFINGERPRINTLEN]; + char *label; + size_t labellen; LIST_ENTRY(mac_veriexec_file_info) entries; }; MALLOC_DECLARE(M_VERIEXEC); SYSCTL_DECL(_security_mac_veriexec); struct cred; struct image_params; struct proc; struct sbuf; struct thread; struct ucred; struct vattr; struct vnode; int mac_veriexec_metadata_fetch_fingerprint_status(struct vnode *vp, struct vattr *vap, struct thread *td, int check_files); int mac_veriexec_metadata_get_executable_flags(struct ucred *cred, struct proc *p, int *flags, int check_files); int mac_veriexec_metadata_get_file_flags(dev_t fsid, long fileid, unsigned long gen, int *flags, int check_files); +struct mac_veriexec_file_info * + mac_veriexec_metadata_get_file_info(dev_t fsid, long fileid, + unsigned long gen, int *found_dev, int check_files); void mac_veriexec_metadata_init(void); void mac_veriexec_metadata_print_db(struct sbuf *sbp); int mac_veriexec_metadata_unmounted(dev_t fsid, struct thread *td); int mac_veriexec_fingerprint_add_ops(struct mac_veriexec_fpops *fpops); int mac_veriexec_fingerprint_check_image(struct image_params *imgp, int check_files, struct thread *td); int mac_veriexec_fingerprint_check_vnode(struct vnode *vp, struct mac_veriexec_file_info *ip, struct thread *td, off_t file_size, unsigned char *fingerprint); void mac_veriexec_fingerprint_init(void); struct mac_veriexec_fpops * mac_veriexec_fingerprint_lookup_ops(const char *type); fingerprint_status_t mac_veriexec_get_fingerprint_status(struct vnode *vp); int mac_veriexec_get_state(void); int mac_veriexec_in_state(int state); void mac_veriexec_set_fingerprint_status(struct vnode *vp, fingerprint_status_t fp_status); void mac_veriexec_set_state(int state); #endif /* !_SECURITY_MAC_VERIEXEC_INTERNAL_H */ Index: head/sys/security/mac_veriexec/veriexec_metadata.c =================================================================== --- head/sys/security/mac_veriexec/veriexec_metadata.c (revision 347941) +++ head/sys/security/mac_veriexec/veriexec_metadata.c (revision 347942) @@ -1,730 +1,787 @@ /* * $FreeBSD$ * - * Copyright (c) 2011, 2012, 2013, 2015, 2016, Juniper Networks, Inc. + * Copyright (c) 2011, 2012, 2013, 2015, 2016, 2019, Juniper Networks, Inc. * All rights reserved. * * Originally derived from: * $NetBSD: kern_verifiedexec.c,v 1.7 2003/11/18 13:13:03 martin Exp $ * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include "opt_mac.h" #include #include #include #include #include #include #include #include #include #include "mac_veriexec.h" #include "mac_veriexec_internal.h" /** * @brief per-device meta-data storage */ struct veriexec_dev_list { dev_t fsid; /**< file system identifier of the mount point */ LIST_HEAD(filehead, mac_veriexec_file_info) file_head; /**< list of per-file meta-data information */ LIST_ENTRY(veriexec_dev_list) entries; /**< next entries in the device list */ }; typedef LIST_HEAD(veriexec_devhead, veriexec_dev_list) veriexec_devhead_t; /** * @brief Mutex to protect the meta-data store lists */ struct mtx ve_mutex; /** * @brief Executables meta-data storage * * This is used to store the fingerprints for potentially-executable files. */ veriexec_devhead_t veriexec_dev_head; /** * @brief Plain file meta-data storage * * This is used for files that are not allowed to be executed, but should * have fingerprint validation available. */ veriexec_devhead_t veriexec_file_dev_head; /** * @internal * @brief Search the @p head meta-data list for the specified file identifier * @p fileid in the file system identified by @p fsid * * If meta-data exists for file system identified by @p fsid, it has a * fingerprint list, and @p found_dev is not @c NULL then store true in the * location pointed to by @p found_dev * * @param head meta-data list to search * @param fsid file system identifier to look for * @param fileid file to look for * @param gen generation of file * @param found_dev indicator that an entry for the file system was found * * @return A pointer to the meta-data inforation if meta-data exists for * the specified file identifier, otherwise @c NULL */ static struct mac_veriexec_file_info * get_veriexec_file(struct veriexec_devhead *head, dev_t fsid, long fileid, unsigned long gen, int *found_dev) { struct veriexec_dev_list *lp; struct mac_veriexec_file_info *ip, *tip; ip = NULL; /* Initialize the value found_dev, if non-NULL */ if (found_dev != NULL) *found_dev = 0; VERIEXEC_DEBUG(3, ("searching for file %ju.%lu on device %ju," " files=%d\n", (uintmax_t)fileid, gen, (uintmax_t)fsid, (head == &veriexec_file_dev_head))); /* Get a lock to access the list */ mtx_lock(&ve_mutex); /* First, look for the file system */ for (lp = LIST_FIRST(head); lp != NULL; lp = LIST_NEXT(lp, entries)) if (lp->fsid == fsid) break; /* We found the file system in the list */ if (lp != NULL) { VERIEXEC_DEBUG(3, ("found matching dev number %ju\n", (uintmax_t)lp->fsid)); /* If found_dev is non-NULL, store true there */ if (found_dev != NULL) *found_dev = 1; /* Next, look for the meta-data information for the file */ LIST_FOREACH_SAFE(ip, &(lp->file_head), entries, tip) { if (ip->fileid == fileid) { if (ip->gen == gen) break; /* we need to garbage collect */ LIST_REMOVE(ip, entries); + if (ip->label) + free(ip->label, M_VERIEXEC); free(ip, M_VERIEXEC); } } } /* Release the lock we obtained earlier */ mtx_unlock(&ve_mutex); /* Return the meta-data information we found, if anything */ return (ip); } /** * @internal - * @brief Search the meta-data store for information on the specified file. - * - * @param fsid file system identifier to look for - * @param fileid file to look for - * @param gen generation of file - * @param found_dev indicator that an entry for the file system was found - * @param check_files if 1, check the files list first, otherwise check the - * exectuables list first - * - * @return A pointer to the meta-data inforation if meta-data exists for - * the specified file identifier, otherwise @c NULL - */ -static struct mac_veriexec_file_info * -find_veriexec_file(dev_t fsid, long fileid, unsigned long gen, int *found_dev, - int check_files) -{ - struct veriexec_devhead *search[3]; - struct mac_veriexec_file_info *ip; - int x; - - /* Determine the order of the lists to search */ - if (check_files) { - search[0] = &veriexec_file_dev_head; - search[1] = &veriexec_dev_head; - } else { - search[0] = &veriexec_dev_head; - search[1] = &veriexec_file_dev_head; - } - search[2] = NULL; - - VERIEXEC_DEBUG(3, ("%s: searching for dev %ju, file %lu\n", - __func__, (uintmax_t)fsid, fileid)); - - /* Search for the specified file */ - for (ip = NULL, x = 0; ip == NULL && search[x]; x++) - ip = get_veriexec_file(search[x], fsid, fileid, gen, found_dev); - - return (ip); -} - -/** - * @internal * @brief Display the fingerprint for each entry in the device list * * @param sbp sbuf to write output to * @param lp pointer to device list */ static void mac_veriexec_print_db_dev_list(struct sbuf *sbp, struct veriexec_dev_list *lp) { struct mac_veriexec_file_info *ip; #define FPB(i) (ip->fingerprint[i]) for (ip = LIST_FIRST(&(lp->file_head)); ip != NULL; ip = LIST_NEXT(ip, entries)) sbuf_printf(sbp, " %ld: %u %ld [%02x %02x %02x %02x %02x " "%02x %02x %02x...]\n", ip->fileid, ip->flags, ip->gen, FPB(0), FPB(1), FPB(2), FPB(3), FPB(4), FPB(5), FPB(6), FPB(7)); } /** * @internal * @brief Display the device list * * @param sbp sbuf to write output to * @param head pointer to head of the device list */ static void mac_veriexec_print_db_head(struct sbuf *sbp, struct veriexec_devhead *head) { struct veriexec_dev_list *lp; for (lp = LIST_FIRST(head); lp != NULL; lp = LIST_NEXT(lp, entries)) { sbuf_printf(sbp, " FS id: %ju\n", (uintmax_t)lp->fsid); mac_veriexec_print_db_dev_list(sbp, lp); } } /** * @internal * @brief Generate human-readable output for the current fingerprint database * * @param sbp sbuf to write output to */ void mac_veriexec_metadata_print_db(struct sbuf *sbp) { struct { struct veriexec_devhead *h; const char *name; } fpdbs[] = { { &veriexec_file_dev_head, "regular files" }, { &veriexec_dev_head, "executable files" }, }; int i; mtx_lock(&ve_mutex); for (i = 0; i < sizeof(fpdbs)/sizeof(fpdbs[0]); i++) { sbuf_printf(sbp, "%s fingerprint db:\n", fpdbs[i].name); mac_veriexec_print_db_head(sbp, fpdbs[i].h); } mtx_unlock(&ve_mutex); } /** * @brief Determine if the meta-data store has an entry for the specified file. * * @param fsid file system identifier to look for * @param fileid file to look for * @param gen generation of file * * @return 1 if there is an entry in the meta-data store, 0 otherwise. */ int mac_veriexec_metadata_has_file(dev_t fsid, long fileid, unsigned long gen) { - return (find_veriexec_file(fsid, fileid, gen, NULL, + return (mac_veriexec_metadata_get_file_info(fsid, fileid, gen, NULL, VERIEXEC_FILES_FIRST) != NULL); } /** * @brief Search the list of devices looking for the one given, in order to * release the resources used by it. * * If found, free all file entries for it, and remove it from the list. * * @note Called with @a ve_mutex held * * @param fsid file system identifier to look for * @param head meta-data list to search * * @return 0 if the device entry was freed, otherwise an error code */ static int free_veriexec_dev(dev_t fsid, struct veriexec_devhead *head) { struct veriexec_dev_list *lp; struct mac_veriexec_file_info *ip, *nip; /* Look for the file system */ for (lp = LIST_FIRST(head); lp != NULL; lp = LIST_NEXT(lp, entries)) if (lp->fsid == fsid) break; /* If lp is NULL, we did not find it */ if (lp == NULL) return ENOENT; /* Unhook lp, before we free it and its content */ LIST_REMOVE(lp, entries); /* Release the lock */ mtx_unlock(&ve_mutex); /* Free the file entries in the list */ for (ip = LIST_FIRST(&(lp->file_head)); ip != NULL; ip = nip) { nip = LIST_NEXT(ip, entries); LIST_REMOVE(ip, entries); + if (ip->label) + free(ip->label, M_VERIEXEC); free(ip, M_VERIEXEC); } /* Free the meta-data entry for the device */ free(lp, M_VERIEXEC); /* Re-acquire the lock */ mtx_lock(&ve_mutex); return 0; } /** * @brief Search the list of devices looking for the one given. * * If it is not in the list then add it. * * @note Called with @a ve_mutex held * * @param fsid file system identifier to look for * @param head meta-data list to search * * @return A pointer to the meta-data entry for the device, if found or added, * otherwise @c NULL */ static struct veriexec_dev_list * find_veriexec_dev(dev_t fsid, struct veriexec_devhead *head) { struct veriexec_dev_list *lp; struct veriexec_dev_list *np = NULL; search: /* Look for the file system */ for (lp = LIST_FIRST(head); lp != NULL; lp = LIST_NEXT(lp, entries)) if (lp->fsid == fsid) break; if (lp == NULL) { if (np == NULL) { /* * If pointer is null then entry not there, * add a new one, first try to malloc while * we hold mutex - should work most of the time. */ np = malloc(sizeof(struct veriexec_dev_list), M_VERIEXEC, M_NOWAIT); if (np == NULL) { /* * So much for that plan, dop the mutex * and repeat... */ mtx_unlock(&ve_mutex); np = malloc(sizeof(struct veriexec_dev_list), M_VERIEXEC, M_WAITOK); mtx_lock(&ve_mutex); /* * Repeat the seach, in case someone * added this while we slept. */ goto search; } } if (np) { /* Add the entry to the list */ lp = np; LIST_INIT(&(lp->file_head)); lp->fsid = fsid; LIST_INSERT_HEAD(head, lp, entries); } } else if (np) { /* * Someone else did it while we slept. */ mtx_unlock(&ve_mutex); free(np, M_VERIEXEC); mtx_lock(&ve_mutex); } return (lp); } /** + * @internal + * @brief Allocate and initialize label record with the provided data. + * + * @param labelp Location to store the initialized label + * @param src Pointer to label string to copy + * @param srclen Length of label string to copy + * + * @return Length of resulting label + * + * @note Called with ve_mutex locked. + */ +static size_t +mac_veriexec_init_label(char **labelp, size_t labellen, char *src, + size_t srclen) +{ + char *label; + + label = *labelp; + if (labellen < srclen) { + mtx_unlock(&ve_mutex); + if (label != NULL) + free(label, M_VERIEXEC); + label = malloc(srclen, M_VERIEXEC, M_WAITOK); + mtx_lock(&ve_mutex); + labellen = srclen; + *labelp = label; + } + memcpy(label, src, srclen); + return labellen; +} + +/** * @brief When a device is unmounted, we want to toss the signatures recorded * against it. * * We are being called from unmount() with the root vnode just before it is * freed. * * @param fsid file system identifier to look for * @param td calling thread * * @return 0 on success, otherwise an error code. */ int mac_veriexec_metadata_unmounted(dev_t fsid, struct thread *td) { int error; /* * The device can have entries on both lists. */ mtx_lock(&ve_mutex); error = free_veriexec_dev(fsid, &veriexec_dev_head); if (error && error != ENOENT) { mtx_unlock(&ve_mutex); return error; } error = free_veriexec_dev(fsid, &veriexec_file_dev_head); mtx_unlock(&ve_mutex); if (error && error != ENOENT) { return error; } return 0; } /** * @brief Return the flags assigned to the file identified by file system * identifier @p fsid and file identifier @p fileid. * * @param fsid file system identifier * @param fileid file identifier within the file system * @param gen generation of file * @param flags pointer to location to store the flags * @param check_files if 1, check the files list first, otherwise check the * exectuables list first * * @return 0 on success, otherwise an error code. */ int mac_veriexec_metadata_get_file_flags(dev_t fsid, long fileid, unsigned long gen, int *flags, int check_files) { struct mac_veriexec_file_info *ip; int found_dev; - ip = find_veriexec_file(fsid, fileid, gen, &found_dev, check_files); + ip = mac_veriexec_metadata_get_file_info(fsid, fileid, gen, &found_dev, + check_files); if (ip == NULL) return (ENOENT); *flags = ip->flags; return (0); } /** * @brief get the files for the specified process * * @param cred credentials to use * @param p process to get the flags for * @param flags where to store the flags * @param check_files if 1, check the files list first, otherwise check the * exectuables list first * * @return 0 if the process has an entry in the meta-data store, otherwise an * error code */ int mac_veriexec_metadata_get_executable_flags(struct ucred *cred, struct proc *p, int *flags, int check_files) { struct vnode *proc_vn; struct vattr vap; int error; /* Get the text vnode for the process */ proc_vn = p->p_textvp; if (proc_vn == NULL) return EINVAL; /* Get vnode attributes */ error = VOP_GETATTR(proc_vn, &vap, cred); if (error) return error; error = mac_veriexec_metadata_get_file_flags(vap.va_fsid, vap.va_fileid, vap.va_gen, flags, (check_files == VERIEXEC_FILES_FIRST)); return (error); } /** * @brief Ensure the fingerprint status for the vnode @p vp is assigned to its * MAC label. * * @param vp vnode to check * @param vap vnode attributes to use * @param td calling thread * @param check_files if 1, check the files list first, otherwise check the * exectuables list first * * @return 0 on success, otherwise an error code. */ int mac_veriexec_metadata_fetch_fingerprint_status(struct vnode *vp, struct vattr *vap, struct thread *td, int check_files) { unsigned char digest[MAXFINGERPRINTLEN]; struct mac_veriexec_file_info *ip; int error, found_dev; fingerprint_status_t status; error = 0; ip = NULL; status = mac_veriexec_get_fingerprint_status(vp); if (status == FINGERPRINT_INVALID || status == FINGERPRINT_NODEV) { found_dev = 0; - ip = find_veriexec_file(vap->va_fsid, vap->va_fileid, - vap->va_gen, &found_dev, check_files); + ip = mac_veriexec_metadata_get_file_info(vap->va_fsid, + vap->va_fileid, vap->va_gen, &found_dev, check_files); if (ip == NULL) { status = (found_dev) ? FINGERPRINT_NOENTRY : FINGERPRINT_NODEV; VERIEXEC_DEBUG(3, ("fingerprint status is %d for dev %ju, file " "%ju.%lu\n", status, (uintmax_t)vap->va_fsid, (uintmax_t)vap->va_fileid, vap->va_gen)); } else { /* * evaluate and compare fingerprint */ error = mac_veriexec_fingerprint_check_vnode(vp, ip, td, vap->va_size, digest); switch (error) { case 0: /* Process flags */ if ((ip->flags & VERIEXEC_INDIRECT)) status = FINGERPRINT_INDIRECT; else if ((ip->flags & VERIEXEC_FILE)) status = FINGERPRINT_FILE; else status = FINGERPRINT_VALID; VERIEXEC_DEBUG(2, ("%sfingerprint matches for dev %ju, file " "%ju.%lu\n", (status == FINGERPRINT_INDIRECT) ? "indirect " : (status == FINGERPRINT_FILE) ? "file " : "", (uintmax_t)vap->va_fsid, (uintmax_t)vap->va_fileid, vap->va_gen)); break; case EAUTH: #ifdef VERIFIED_EXEC_DEBUG_VERBOSE { char have[MAXFINGERPRINTLEN * 2 + 1]; char want[MAXFINGERPRINTLEN * 2 + 1]; int i, len; len = ip->ops->digest_len; for (i = 0; i < len; i++) { sprintf(&want[i * 2], "%02x", ip->fingerprint[i]); sprintf(&have[i * 2], "%02x", digest[i]); } log(LOG_ERR, MAC_VERIEXEC_FULLNAME ": fingerprint for dev %ju, file " "%ju.%lu %s != %s\n", (uintmax_t)vap->va_fsid, (uintmax_t)vap->va_fileid, vap->va_gen, have, want); } #endif status = FINGERPRINT_NOMATCH; break; default: VERIEXEC_DEBUG(2, ("fingerprint status error %d\n", error)); break; } } mac_veriexec_set_fingerprint_status(vp, status); } return (error); } /** * Add a file and its fingerprint to the list of files attached * to the device @p fsid. * * Only add the entry if it is not already on the list. * * @note Called with @a ve_mutex held * * @param file_dev if 1, the entry should be added on the file list, * otherwise it should be added on the executable list * @param fsid file system identifier of device * @param fileid file to add * @param gen generation of file * @param fingerprint fingerprint to add to the store * @param flags flags to set in the store * @param fp_type digest type * @param override if 1, override any values already stored * * @return 0 on success, otherwise an error code. */ int mac_veriexec_metadata_add_file(int file_dev, dev_t fsid, long fileid, unsigned long gen, unsigned char fingerprint[MAXFINGERPRINTLEN], - int flags, const char *fp_type, int override) + char *label, size_t labellen, int flags, const char *fp_type, int override) { struct mac_veriexec_fpops *fpops; struct veriexec_dev_list *lp; struct veriexec_devhead *head; struct mac_veriexec_file_info *ip; struct mac_veriexec_file_info *np = NULL; + /* Label and labellen must be set if VERIEXEC_LABEL is set */ + if ((flags & VERIEXEC_LABEL) != 0 && (label == NULL || labellen == 0)) + return (EINVAL); + /* Look up the device entry */ if (file_dev) head = &veriexec_file_dev_head; else head = &veriexec_dev_head; lp = find_veriexec_dev(fsid, head); /* Look up the fingerprint operations for the digest type */ fpops = mac_veriexec_fingerprint_lookup_ops(fp_type); if (fpops == NULL) return (EOPNOTSUPP); search: for (ip = LIST_FIRST(&(lp->file_head)); ip != NULL; ip = LIST_NEXT(ip, entries)) { /* check for a dupe file in the list, skip if an entry * exists for this file except for when the flags contains * VERIEXEC_INDIRECT, always set the flags when it is so * we don't get a hole caused by conflicting flags on * hardlinked files. XXX maybe we should validate * fingerprint is same and complain if it is not... */ if (ip->fileid == fileid && ip->gen == gen) { if (override) { /* * for a signed load we allow overrides, * otherwise fingerpints needed for pkg loads * can fail (the files are on temp device). */ ip->flags = flags; ip->ops = fpops; memcpy(ip->fingerprint, fingerprint, fpops->digest_len); + if (flags & VERIEXEC_LABEL) { + ip->labellen = mac_veriexec_init_label( + &ip->label, ip->labellen, label, + labellen); + } else if (ip->labellen > 0) { + free(ip->label, M_VERIEXEC); + ip->labellen = 0; + ip->label = NULL; + } } else if ((flags & (VERIEXEC_INDIRECT|VERIEXEC_FILE))) ip->flags |= flags; if (np) { /* unlikely but... we don't need it now. */ mtx_unlock(&ve_mutex); free(np, M_VERIEXEC); mtx_lock(&ve_mutex); } return (0); } } /* * We may have been past here before... */ if (np == NULL) { /* * We first try with mutex held and nowait. */ np = malloc(sizeof(struct mac_veriexec_file_info), M_VERIEXEC, M_NOWAIT); if (np == NULL) { /* * It was worth a try, now * drop mutex while we malloc. */ mtx_unlock(&ve_mutex); np = malloc(sizeof(struct mac_veriexec_file_info), M_VERIEXEC, M_WAITOK); mtx_lock(&ve_mutex); /* * We now have to repeat our search! */ goto search; } } /* Set up the meta-data entry */ ip = np; ip->flags = flags; ip->ops = fpops; ip->fileid = fileid; ip->gen = gen; memcpy(ip->fingerprint, fingerprint, fpops->digest_len); + if (flags & VERIEXEC_LABEL) + ip->labellen = mac_veriexec_init_label(&ip->label, + ip->labellen, label, labellen); + else { + ip->label = NULL; + ip->labellen = 0; + } VERIEXEC_DEBUG(3, ("add file %ju.%lu (files=%d)\n", (uintmax_t)ip->fileid, ip->gen, file_dev)); /* Add the entry to the list */ LIST_INSERT_HEAD(&(lp->file_head), ip, entries); #ifdef DEBUG_VERIEXEC_FINGERPRINT { off_t offset; printf("Stored %s fingerprint:\n", fp_type); for (offset = 0; offset < fpops->digest_len; offset++) printf("%02x", fingerprint[offset]); printf("\n"); } #endif return (0); } + +/** + * @brief Search the meta-data store for information on the specified file. + * + * @param fsid file system identifier to look for + * @param fileid file to look for + * @param gen generation of file + * @param found_dev indicator that an entry for the file system was found + * @param check_files if 1, check the files list first, otherwise check the + * exectuables list first + * + * @return A pointer to the meta-data inforation if meta-data exists for + * the specified file identifier, otherwise @c NULL + */ +struct mac_veriexec_file_info * +mac_veriexec_metadata_get_file_info(dev_t fsid, long fileid, unsigned long gen, + int *found_dev, int check_files) +{ + struct veriexec_devhead *search[3]; + struct mac_veriexec_file_info *ip; + int x; + + /* Determine the order of the lists to search */ + if (check_files) { + search[0] = &veriexec_file_dev_head; + search[1] = &veriexec_dev_head; + } else { + search[0] = &veriexec_dev_head; + search[1] = &veriexec_file_dev_head; + } + search[2] = NULL; + + VERIEXEC_DEBUG(3, ("%s: searching for dev %ju, file %lu\n", + __func__, (uintmax_t)fsid, fileid)); + + /* Search for the specified file */ + for (ip = NULL, x = 0; ip == NULL && search[x]; x++) + ip = get_veriexec_file(search[x], fsid, fileid, gen, found_dev); + + return (ip); +} + /** * @brief Intialize the meta-data store */ void mac_veriexec_metadata_init(void) { mtx_init(&ve_mutex, "veriexec lock", NULL, MTX_DEF); LIST_INIT(&veriexec_dev_head); LIST_INIT(&veriexec_file_dev_head); }