diff --git a/sys/security/mac_veriexec/mac_veriexec.c b/sys/security/mac_veriexec/mac_veriexec.c index d61943479ad6..57f3b6c307fa 100644 --- a/sys/security/mac_veriexec/mac_veriexec.c +++ b/sys/security/mac_veriexec/mac_veriexec.c @@ -1,1192 +1,1190 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011-2023 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 "opt_capsicum.h" #include "opt_mac.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef COMPAT_FREEBSD32 #include #include #include #endif #include #include #include #include "mac_veriexec.h" #include "mac_veriexec_internal.h" #define SLOT(l) \ mac_label_get((l), mac_veriexec_slot) #define SLOT_SET(l, v) \ mac_label_set((l), mac_veriexec_slot, (v)) -#ifdef MAC_DEBUG +#ifdef MAC_VERIEXEC_DEBUG #define MAC_VERIEXEC_DBG(_lvl, _fmt, ...) \ do { \ VERIEXEC_DEBUG((_lvl), (MAC_VERIEXEC_FULLNAME ": " _fmt \ "\n", ##__VA_ARGS__)); \ } while(0) #else #define MAC_VERIEXEC_DBG(_lvl, _fmt, ...) #endif static int sysctl_mac_veriexec_state(SYSCTL_HANDLER_ARGS); static int sysctl_mac_veriexec_db(SYSCTL_HANDLER_ARGS); static struct mac_policy_ops mac_veriexec_ops; SYSCTL_DECL(_security_mac); SYSCTL_NODE(_security_mac, OID_AUTO, veriexec, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "MAC/veriexec policy controls"); int mac_veriexec_debug; SYSCTL_INT(_security_mac_veriexec, OID_AUTO, debug, CTLFLAG_RW, &mac_veriexec_debug, 0, "Debug level"); static int mac_veriexec_state; SYSCTL_PROC(_security_mac_veriexec, OID_AUTO, state, CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_NEEDGIANT, 0, 0, sysctl_mac_veriexec_state, "A", "Verified execution subsystem state"); SYSCTL_PROC(_security_mac_veriexec, OID_AUTO, db, CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_SKIP | CTLFLAG_NEEDGIANT, 0, 0, sysctl_mac_veriexec_db, "A", "Verified execution fingerprint database"); static int mac_veriexec_slot; static int mac_veriexec_block_unlink; MALLOC_DEFINE(M_VERIEXEC, "veriexec", "Verified execution data"); /** * @internal * @brief Handler for security.mac.veriexec.db sysctl * * Display a human-readable form of the current fingerprint database. */ static int sysctl_mac_veriexec_db(SYSCTL_HANDLER_ARGS) { struct sbuf sb; int error; error = sysctl_wire_old_buffer(req, 0); if (error != 0) return (error); sbuf_new_for_sysctl(&sb, NULL, 1024, req); mac_veriexec_metadata_print_db(&sb); error = sbuf_finish(&sb); sbuf_delete(&sb); return (error); } /** * @internal * @brief Generate human-readable output about the current verified execution * state. * * @param sbp sbuf to write output to */ static void mac_veriexec_print_state(struct sbuf *sbp) { if (mac_veriexec_state & VERIEXEC_STATE_INACTIVE) sbuf_printf(sbp, "inactive "); if (mac_veriexec_state & VERIEXEC_STATE_LOADED) sbuf_printf(sbp, "loaded "); if (mac_veriexec_state & VERIEXEC_STATE_ACTIVE) sbuf_printf(sbp, "active "); if (mac_veriexec_state & VERIEXEC_STATE_ENFORCE) sbuf_printf(sbp, "enforce "); if (mac_veriexec_state & VERIEXEC_STATE_LOCKED) sbuf_printf(sbp, "locked "); if (mac_veriexec_state != 0) sbuf_trim(sbp); } /** * @internal * @brief Handler for security.mac.veriexec.state sysctl * * Display a human-readable form of the current verified execution subsystem * state. */ static int sysctl_mac_veriexec_state(SYSCTL_HANDLER_ARGS) { struct sbuf sb; int error; sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND); mac_veriexec_print_state(&sb); sbuf_finish(&sb); error = SYSCTL_OUT(req, sbuf_data(&sb), sbuf_len(&sb)); sbuf_delete(&sb); return (error); } /** * @internal * @brief Event handler called when a virtual file system is mounted. * * We need to record the file system identifier in the MAC per-policy slot * assigned to veriexec, so we have a key to use in order to reference the * mount point in the meta-data store. * * @param arg unused argument * @param mp mount point that is being mounted * @param fsrootvp vnode of the file system root * @param td calling thread */ static void mac_veriexec_vfs_mounted(void *arg __unused, struct mount *mp, struct vnode *fsrootvp, struct thread *td) { struct vattr va; int error; error = VOP_GETATTR(fsrootvp, &va, td->td_ucred); if (error) return; SLOT_SET(mp->mnt_label, va.va_fsid); -#ifdef MAC_DEBUG MAC_VERIEXEC_DBG(3, "set fsid to %ju for mount %p", (uintmax_t)va.va_fsid, mp); -#endif } /** * @internal * @brief Event handler called when a virtual file system is unmounted. * * If we recorded a file system identifier in the MAC per-policy slot assigned * to veriexec, then we need to tell the meta-data store to clean up. * * @param arg unused argument * @param mp mount point that is being unmounted * @param td calling thread */ static void mac_veriexec_vfs_unmounted(void *arg __unused, struct mount *mp, struct thread *td) { dev_t fsid; fsid = SLOT(mp->mnt_label); if (fsid) { MAC_VERIEXEC_DBG(3, "fsid %ju, cleaning up mount", (uintmax_t)fsid); mac_veriexec_metadata_unmounted(fsid, td); } } /** * @internal * @brief The mount point is being initialized, set the value in the MAC * per-policy slot for veriexec to zero. * * @note A value of zero in this slot indicates no file system identifier * is assigned. * * @param label the label that is being initialized */ static void mac_veriexec_mount_init_label(struct label *label) { SLOT_SET(label, 0); } /** * @internal * @brief The mount-point is being destroyed, reset the value in the MAC * per-policy slot for veriexec back to zero. * * @note A value of zero in this slot indicates no file system identifier * is assigned. * * @param label the label that is being destroyed */ static void mac_veriexec_mount_destroy_label(struct label *label) { SLOT_SET(label, 0); } /** * @internal * @brief The vnode label is being initialized, set the value in the MAC * per-policy slot for veriexec to @c FINGERPRINT_INVALID * * @note @c FINGERPRINT_INVALID indicates the fingerprint is invalid. * * @param label the label that is being initialized */ static void mac_veriexec_vnode_init_label(struct label *label) { SLOT_SET(label, FINGERPRINT_INVALID); } /** * @internal * @brief The vnode label is being destroyed, reset the value in the MAC * per-policy slot for veriexec back to @c FINGERPRINT_INVALID * * @note @c FINGERPRINT_INVALID indicates the fingerprint is invalid. * * @param label the label that is being destroyed */ static void mac_veriexec_vnode_destroy_label(struct label *label) { SLOT_SET(label, FINGERPRINT_INVALID); } /** * @internal * @brief Copy the value in the MAC per-policy slot assigned to veriexec from * the @p src label to the @p dest label */ static void mac_veriexec_copy_label(struct label *src, struct label *dest) { SLOT_SET(dest, SLOT(src)); } /** * @internal * @brief Check if the requested process can be debugged * * @param cred credentials to use * @param p process to debug * * @return 0 if debugging is allowed, otherwise an error code. */ static int mac_veriexec_proc_check_debug(struct ucred *cred, struct proc *p) { int error, flags; /* If we are not enforcing veriexec, nothing for us to check */ if ((mac_veriexec_state & VERIEXEC_STATE_ENFORCE) == 0) return (0); error = mac_veriexec_metadata_get_executable_flags(cred, p, &flags, 0); if (error != 0) return (0); error = (flags & (VERIEXEC_NOTRACE|VERIEXEC_TRUSTED)) ? EACCES : 0; MAC_VERIEXEC_DBG(4, "%s flags=%#x error=%d", __func__, flags, error); return (error); } /** * @internal * @brief A KLD load has been requested and needs to be validated. * * @param cred credentials to use * @param vp vnode of the KLD that has been requested * @param vlabel vnode label assigned to the vnode * * @return 0 if the KLD load is allowed, otherwise an error code. */ static int mac_veriexec_kld_check_load(struct ucred *cred, struct vnode *vp, struct label *vlabel) { struct vattr va; struct thread *td = curthread; fingerprint_status_t status; int error; /* * If we are not actively enforcing, allow it */ if ((mac_veriexec_state & VERIEXEC_STATE_ENFORCE) == 0) return (0); /* Get vnode attributes */ error = VOP_GETATTR(vp, &va, cred); if (error) return (error); /* * Fetch the fingerprint status for the vnode * (starting with files first) */ error = mac_veriexec_metadata_fetch_fingerprint_status(vp, &va, td, VERIEXEC_FILES_FIRST); if (error && error != EAUTH) return (error); /* * By now we should have status... */ status = mac_veriexec_get_fingerprint_status(vp); switch (status) { case FINGERPRINT_FILE: case FINGERPRINT_VALID: case FINGERPRINT_INDIRECT: if (error) return (error); break; default: /* * kldload should fail unless there is a valid fingerprint * registered. */ MAC_VERIEXEC_DBG(2, "fingerprint status is %d for dev %ju, " "file %ju.%ju\n", status, (uintmax_t)va.va_fsid, (uintmax_t)va.va_fileid, (uintmax_t)va.va_gen); return (EAUTH); } /* Everything is good, allow the KLD to be loaded */ return (0); } /** * @internal * @brief Check privileges that veriexec needs to be concerned about. * * The following privileges are checked by this function: * - PRIV_KMEM_WRITE\n * Check if writes to /dev/mem and /dev/kmem are allowed\n * (Only trusted processes are allowed) * - PRIV_VERIEXEC_CONTROL\n * Check if manipulating veriexec is allowed\n * (only trusted processes are allowed) * * @param cred credentials to use * @param priv privilege to check * * @return 0 if the privilege is allowed, error code otherwise. */ static int mac_veriexec_priv_check(struct ucred *cred, int priv) { int error; /* If we are not enforcing veriexec, nothing for us to check */ if ((mac_veriexec_state & VERIEXEC_STATE_ENFORCE) == 0) return (0); error = 0; switch (priv) { case PRIV_KMEM_WRITE: case PRIV_VERIEXEC_CONTROL: /* * Do not allow writing to memory or manipulating veriexec, * unless trusted */ if (mac_veriexec_proc_is_trusted(cred, curproc) == 0 && mac_priv_grant(cred, priv) != 0) error = EPERM; MAC_VERIEXEC_DBG(4, "%s priv=%d error=%d", __func__, priv, error); break; default: break; } return (error); } /** * @internal * @brief Check if the requested sysctl should be allowed * * @param cred credentials to use * @param oidp sysctl OID * @param arg1 first sysctl argument * @param arg2 second sysctl argument * @param req sysctl request information * * @return 0 if the sysctl should be allowed, otherwise an error code. */ static int mac_veriexec_sysctl_check(struct ucred *cred, struct sysctl_oid *oidp, void *arg1, int arg2, struct sysctl_req *req) { struct sysctl_oid *oid; /* If we are not enforcing veriexec, nothing for us to check */ if ((mac_veriexec_state & VERIEXEC_STATE_ENFORCE) == 0) return (0); oid = oidp; if (req->newptr && (oid->oid_kind & CTLFLAG_SECURE)) { return (EPERM); /* XXX call mac_veriexec_priv_check? */ } return 0; } /** * @internal * @brief A program is being executed and needs to be validated. * * @param cred credentials to use * @param vp vnode of the program that is being executed * @param label vnode label assigned to the vnode * @param imgp parameters for the image to be executed * @param execlabel optional exec label * * @return 0 if the program should be allowed to execute, otherwise an error * code. */ static int mac_veriexec_vnode_check_exec(struct ucred *cred __unused, struct vnode *vp __unused, struct label *label __unused, struct image_params *imgp, struct label *execlabel __unused) { struct thread *td = curthread; int error; error = mac_veriexec_fingerprint_check_image(imgp, 0, td); return (error); } /** * @brief Check fingerprint for the specified vnode and validate it * * @param cred credentials to use * @param vp vnode of the file * @param accmode access mode to check (read, write, append, create, * verify, etc.) * * @return 0 if the file validated, otherwise an error code. */ static int mac_veriexec_check_vp(struct ucred *cred, struct vnode *vp, accmode_t accmode) { struct vattr va; struct thread *td = curthread; fingerprint_status_t status; int error; /* Get vnode attributes */ error = VOP_GETATTR(vp, &va, cred); if (error) return (error); /* Get the fingerprint status for the file */ error = mac_veriexec_metadata_fetch_fingerprint_status(vp, &va, td, VERIEXEC_FILES_FIRST); if (error && error != EAUTH) return (error); /* * By now we should have status... */ status = mac_veriexec_get_fingerprint_status(vp); if (accmode & VWRITE) { /* * If file has a fingerprint then deny the write request, * otherwise invalidate the status so we don't keep checking * for the file having a fingerprint. */ switch (status) { case FINGERPRINT_FILE: case FINGERPRINT_VALID: case FINGERPRINT_INDIRECT: MAC_VERIEXEC_DBG(2, "attempted write to fingerprinted file for dev " "%ju, file %ju.%ju\n", (uintmax_t)va.va_fsid, (uintmax_t)va.va_fileid, (uintmax_t)va.va_gen); return (EPERM); default: break; } } if (accmode & VVERIFY) { switch (status) { case FINGERPRINT_FILE: case FINGERPRINT_VALID: case FINGERPRINT_INDIRECT: if (error) return (error); break; default: /* Allow for overriding verification requirement */ if (mac_priv_grant(cred, PRIV_VERIEXEC_NOVERIFY) == 0) return (0); /* * Caller wants open to fail unless there is a valid * fingerprint registered. */ MAC_VERIEXEC_DBG(2, "fingerprint status is %d for dev " "%ju, file %ju.%ju\n", status, (uintmax_t)va.va_fsid, (uintmax_t)va.va_fileid, (uintmax_t)va.va_gen); return (EAUTH); } } return (0); } /** * @brief Opening a file has been requested and may need to be validated. * * @param cred credentials to use * @param vp vnode of the file to open * @param label vnode label assigned to the vnode * @param accmode access mode to use for opening the file (read, write, * append, create, verify, etc.) * * @return 0 if opening the file should be allowed, otherwise an error code. */ static int mac_veriexec_vnode_check_open(struct ucred *cred, struct vnode *vp, struct label *label __unused, accmode_t accmode) { int error; /* * Look for the file on the fingerprint lists iff it has not been seen * before. */ if ((mac_veriexec_state & VERIEXEC_STATE_ENFORCE) == 0) return (0); error = mac_veriexec_check_vp(cred, vp, accmode); return (error); } /** * @brief Unlink on a file has been requested and may need to be validated. * * @param cred credentials to use * @param dvp parent directory for file vnode vp * @param dlabel vnode label assigned to the directory vnode * @param vp vnode of the file to unlink * @param label vnode label assigned to the vnode * @param cnp component name for vp * * * @return 0 if opening the file should be allowed, otherwise an error code. */ static int mac_veriexec_vnode_check_unlink(struct ucred *cred, struct vnode *dvp __unused, struct label *dvplabel __unused, struct vnode *vp, struct label *label __unused, struct componentname *cnp __unused) { int error; /* * Look for the file on the fingerprint lists iff it has not been seen * before. */ if ((mac_veriexec_state & VERIEXEC_STATE_ENFORCE) == 0) return (0); error = mac_veriexec_check_vp(cred, vp, VVERIFY); if (error == 0) { /* * The target is verified, so disallow replacement. */ MAC_VERIEXEC_DBG(2, "(UNLINK) attempted to unlink a protected file (euid: %u)", cred->cr_uid); return (EAUTH); } return (0); } /** * @brief Rename the file has been requested and may need to be validated. * * @param cred credentials to use * @param dvp parent directory for file vnode vp * @param dlabel vnode label assigned to the directory vnode * @param vp vnode of the file to rename * @param label vnode label assigned to the vnode * @param cnp component name for vp * * * @return 0 if opening the file should be allowed, otherwise an error code. */ static int mac_veriexec_vnode_check_rename_from(struct ucred *cred, struct vnode *dvp __unused, struct label *dvplabel __unused, struct vnode *vp, struct label *label __unused, struct componentname *cnp __unused) { int error; /* * Look for the file on the fingerprint lists iff it has not been seen * before. */ if ((mac_veriexec_state & VERIEXEC_STATE_ENFORCE) == 0) return (0); error = mac_veriexec_check_vp(cred, vp, VVERIFY); if (error == 0) { /* * The target is verified, so disallow replacement. */ MAC_VERIEXEC_DBG(2, "(RENAME_FROM) attempted to rename a protected file (euid: %u)", cred->cr_uid); return (EAUTH); } return (0); } /** * @brief Rename to file into the directory (overwrite the file name) has been * requested and may need to be validated. * * @param cred credentials to use * @param dvp parent directory for file vnode vp * @param dlabel vnode label assigned to the directory vnode * @param vp vnode of the overwritten file * @param label vnode label assigned to the vnode * @param samedir 1 if the source and destination directories are the same * @param cnp component name for vp * * * @return 0 if opening the file should be allowed, otherwise an error code. */ static int mac_veriexec_vnode_check_rename_to(struct ucred *cred, struct vnode *dvp __unused, struct label *dvplabel __unused, struct vnode *vp, struct label *label __unused, int samedir __unused, struct componentname *cnp __unused) { int error; /* * If there is no existing file to overwrite, vp and label will be * NULL. */ if (vp == NULL) return (0); /* * Look for the file on the fingerprint lists iff it has not been seen * before. */ if ((mac_veriexec_state & VERIEXEC_STATE_ENFORCE) == 0) return (0); error = mac_veriexec_check_vp(cred, vp, VVERIFY); if (error == 0) { /* * The target is verified, so disallow replacement. */ MAC_VERIEXEC_DBG(2, "(RENAME_TO) attempted to overwrite a protected file (euid: %u)", cred->cr_uid); return (EAUTH); } return (0); } /** * @brief Check mode changes on file to ensure they should be allowed. * * We cannot allow chmod of SUID or SGID on verified files. * * @param cred credentials to use * @param vp vnode of the file to open * @param label vnode label assigned to the vnode * @param mode mode flags to set * * @return 0 if the mode change should be allowed, EAUTH otherwise. */ static int mac_veriexec_vnode_check_setmode(struct ucred *cred, struct vnode *vp, struct label *label __unused, mode_t mode) { int error; if ((mac_veriexec_state & VERIEXEC_STATE_ENFORCE) == 0) return (0); /* * Prohibit chmod of verified set-[gu]id file. */ error = mac_veriexec_check_vp(cred, vp, VVERIFY); if (error == EAUTH) /* target not verified */ return (0); if (error == 0 && (mode & (S_ISUID|S_ISGID)) != 0) return (EAUTH); return (0); } /** * @internal * @brief Initialize the mac_veriexec MAC policy * * @param mpc MAC policy configuration */ static void mac_veriexec_init(struct mac_policy_conf *mpc __unused) { /* Initialize state */ mac_veriexec_state = VERIEXEC_STATE_INACTIVE; /* Initialize meta-data storage */ mac_veriexec_metadata_init(); /* Initialize fingerprint ops */ mac_veriexec_fingerprint_init(); /* Register event handlers */ EVENTHANDLER_REGISTER(vfs_mounted, mac_veriexec_vfs_mounted, NULL, EVENTHANDLER_PRI_FIRST); EVENTHANDLER_REGISTER(vfs_unmounted, mac_veriexec_vfs_unmounted, NULL, EVENTHANDLER_PRI_LAST); /* Fetch tunable value in kernel env and define a corresponding read-only sysctl */ mac_veriexec_block_unlink = 0; TUNABLE_INT_FETCH("security.mac.veriexec.block_unlink", &mac_veriexec_block_unlink); SYSCTL_INT(_security_mac_veriexec, OID_AUTO, block_unlink, CTLFLAG_RDTUN, &mac_veriexec_block_unlink, 0, "Veriexec unlink protection"); /* Check if unlink control is activated via tunable value */ if (!mac_veriexec_block_unlink) mac_veriexec_ops.mpo_vnode_check_unlink = NULL; } #ifdef COMPAT_FREEBSD32 struct mac_veriexec_syscall_params32 { char fp_type[VERIEXEC_FPTYPELEN]; unsigned char fingerprint[MAXFINGERPRINTLEN]; char label[MAXLABELLEN]; uint32_t labellen; unsigned char flags; }; struct mac_veriexec_syscall_params_args32 { union { pid_t pid; uint32_t filename; } u; /* input only */ uint32_t params; /* result */ }; #endif /** * @internal * @brief MAC policy-specific syscall for mac_veriexec * * The following syscalls are implemented: * - @c MAC_VERIEXEC_CHECK_SYSCALL * Check if the file referenced by a file descriptor has a fingerprint * registered in the meta-data store. * * @param td calling thread * @param call system call number * @param arg arugments to the syscall * * @return 0 on success, otherwise an error code. */ static int mac_veriexec_syscall(struct thread *td, int call, void *arg) { struct image_params img; struct nameidata nd; cap_rights_t rights; struct vattr va; struct file *fp; struct mac_veriexec_syscall_params_args pargs; struct mac_veriexec_syscall_params result; #ifdef COMPAT_FREEBSD32 struct mac_veriexec_syscall_params_args32 pargs32; struct mac_veriexec_syscall_params32 result32; #endif struct mac_veriexec_file_info *ip; struct proc *proc; struct vnode *textvp; int error, flags, proc_locked; nd.ni_vp = NULL; proc_locked = 0; textvp = NULL; switch (call) { case MAC_VERIEXEC_GET_PARAMS_PID_SYSCALL: case MAC_VERIEXEC_GET_PARAMS_PATH_SYSCALL: #ifdef COMPAT_FREEBSD32 if (SV_PROC_FLAG(td->td_proc, SV_ILP32)) { error = copyin(arg, &pargs32, sizeof(pargs32)); if (error) return error; bzero(&pargs, sizeof(pargs)); switch (call) { case MAC_VERIEXEC_GET_PARAMS_PID_SYSCALL: CP(pargs32, pargs, u.pid); break; case MAC_VERIEXEC_GET_PARAMS_PATH_SYSCALL: PTRIN_CP(pargs32, pargs, u.filename); break; } PTRIN_CP(pargs32, pargs, params); } else #endif error = copyin(arg, &pargs, sizeof(pargs)); if (error) return error; break; } switch (call) { case MAC_VERIEXEC_CHECK_FD_SYSCALL: /* Get the vnode associated with the file descriptor passed */ error = getvnode(td, (uintptr_t) arg, cap_rights_init_one(&rights, CAP_READ), &fp); if (error) return (error); if (fp->f_type != DTYPE_VNODE) { MAC_VERIEXEC_DBG(3, "MAC_VERIEXEC_CHECK_SYSCALL: " "file is not vnode type (type=0x%x)", fp->f_type); error = EINVAL; goto cleanup_file; } /* * setup the bits of image_params that are used by * mac_veriexec_check_fingerprint(). */ bzero(&img, sizeof(img)); img.proc = td->td_proc; img.vp = fp->f_vnode; img.attr = &va; /* * Get vnode attributes * (need to obtain a lock on the vnode first) */ vn_lock(img.vp, LK_EXCLUSIVE | LK_RETRY); error = VOP_GETATTR(fp->f_vnode, &va, td->td_ucred); if (error) goto check_done; MAC_VERIEXEC_DBG(2, "mac_veriexec_fingerprint_check_image: " "va_mode=%o, check_files=%d\n", va.va_mode, ((va.va_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0)); error = mac_veriexec_fingerprint_check_image(&img, ((va.va_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0), td); check_done: /* Release the lock we obtained earlier */ VOP_UNLOCK(img.vp); cleanup_file: fdrop(fp, td); break; case MAC_VERIEXEC_CHECK_PATH_SYSCALL: /* Look up the path to get the vnode */ NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF | LOCKSHARED | AUDITVNODE1, UIO_USERSPACE, arg); flags = FREAD; error = vn_open(&nd, &flags, 0, NULL); if (error != 0) break; NDFREE_PNBUF(&nd); /* Check the fingerprint status of the vnode */ error = mac_veriexec_check_vp(td->td_ucred, nd.ni_vp, VVERIFY); /* nd.ni_vp cleaned up below */ break; case MAC_VERIEXEC_GET_PARAMS_PID_SYSCALL: if (pargs.u.pid == 0 || pargs.u.pid == curproc->p_pid) { proc = curproc; } else { proc = pfind(pargs.u.pid); if (proc == NULL) return (EINVAL); proc_locked = 1; } textvp = proc->p_textvp; /* FALLTHROUGH */ case MAC_VERIEXEC_GET_PARAMS_PATH_SYSCALL: if (textvp == NULL) { /* Look up the path to get the vnode */ NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF | AUDITVNODE1, UIO_USERSPACE, pargs.u.filename); flags = FREAD; error = vn_open(&nd, &flags, 0, NULL); if (error != 0) break; NDFREE_PNBUF(&nd); textvp = nd.ni_vp; } error = VOP_GETATTR(textvp, &va, curproc->p_ucred); if (proc_locked) PROC_UNLOCK(proc); if (error != 0) break; error = mac_veriexec_metadata_get_file_info(va.va_fsid, va.va_fileid, va.va_gen, NULL, &ip, FALSE); if (error != 0) break; #ifdef COMPAT_FREEBSD32 if (SV_PROC_FLAG(td->td_proc, SV_ILP32)) { bzero(&result32, sizeof(result32)); result32.flags = ip->flags; strlcpy(result32.fp_type, ip->ops->type, sizeof(result32.fp_type)); result.labellen = ip->labellen; CP(result, result32, labellen); if (ip->labellen > 0) strlcpy(result32.label, ip->label, sizeof(result32.label)); result32.label[result.labellen] = '\0'; memcpy(result32.fingerprint, ip->fingerprint, ip->ops->digest_len); error = copyout(&result32, pargs.params, sizeof(result32)); break; /* yes */ } #endif bzero(&result, sizeof(result)); result.flags = ip->flags; strlcpy(result.fp_type, ip->ops->type, sizeof(result.fp_type)); result.labellen = ip->labellen; if (ip->labellen > 0) strlcpy(result.label, ip->label, sizeof(result.label)); result.label[result.labellen] = '\0'; memcpy(result.fingerprint, ip->fingerprint, ip->ops->digest_len); error = copyout(&result, pargs.params, sizeof(result)); break; default: error = EOPNOTSUPP; } if (nd.ni_vp != NULL) { VOP_UNLOCK(nd.ni_vp); vn_close(nd.ni_vp, FREAD, td->td_ucred, td); } return (error); } static struct mac_policy_ops mac_veriexec_ops = { .mpo_init = mac_veriexec_init, .mpo_kld_check_load = mac_veriexec_kld_check_load, .mpo_mount_destroy_label = mac_veriexec_mount_destroy_label, .mpo_mount_init_label = mac_veriexec_mount_init_label, .mpo_priv_check = mac_veriexec_priv_check, .mpo_proc_check_debug = mac_veriexec_proc_check_debug, .mpo_syscall = mac_veriexec_syscall, .mpo_system_check_sysctl = mac_veriexec_sysctl_check, .mpo_vnode_check_exec = mac_veriexec_vnode_check_exec, .mpo_vnode_check_open = mac_veriexec_vnode_check_open, .mpo_vnode_check_unlink = mac_veriexec_vnode_check_unlink, .mpo_vnode_check_rename_to = mac_veriexec_vnode_check_rename_to, .mpo_vnode_check_rename_from = mac_veriexec_vnode_check_rename_from, .mpo_vnode_check_setmode = mac_veriexec_vnode_check_setmode, .mpo_vnode_copy_label = mac_veriexec_copy_label, .mpo_vnode_destroy_label = mac_veriexec_vnode_destroy_label, .mpo_vnode_init_label = mac_veriexec_vnode_init_label, }; MAC_POLICY_SET(&mac_veriexec_ops, mac_veriexec, MAC_VERIEXEC_FULLNAME, MPC_LOADTIME_FLAG_NOTLATE, &mac_veriexec_slot); MODULE_VERSION(mac_veriexec, MAC_VERIEXEC_VERSION); static struct vnode * mac_veriexec_bottom_vnode(struct vnode *vp) { struct vnode *ldvp = NULL; /* * XXX This code is bogus. nullfs is not the only stacking * filesystem. Less bogus code would add a VOP to reach bottom * vnode and would not make assumptions how to get there. */ if (vp->v_mount != NULL && strcmp(vp->v_mount->mnt_vfc->vfc_name, "nullfs") == 0) ldvp = NULLVPTOLOWERVP(vp); return (ldvp); } /** * @brief Get the fingerprint status set on a vnode. * * @param vp vnode to obtain fingerprint status from * * @return Fingerprint status assigned to the vnode. */ fingerprint_status_t mac_veriexec_get_fingerprint_status(struct vnode *vp) { fingerprint_status_t fps; struct vnode *ldvp; fps = SLOT(vp->v_label); switch (fps) { case FINGERPRINT_VALID: case FINGERPRINT_INDIRECT: case FINGERPRINT_FILE: break; default: /* we may need to recurse */ ldvp = mac_veriexec_bottom_vnode(vp); if (ldvp != NULL) return mac_veriexec_get_fingerprint_status(ldvp); break; } return fps; } /** * @brief Get the current verified execution subsystem state. * * @return Current set of verified execution subsystem state flags. */ int mac_veriexec_get_state(void) { return (mac_veriexec_state); } /** * @brief Determine if the verified execution subsystem state has specific * flags set. * * @param state mask of flags to check * * @return State flags set within the masked bits */ int mac_veriexec_in_state(int state) { return (mac_veriexec_state & state); } /** * @brief Set the fingerprint status for a vnode * * Fingerprint status is stored in the MAC per-policy slot assigned to * mac_veriexec. * * @param vp vnode to store the fingerprint status on * @param fp_status fingerprint status to store */ void mac_veriexec_set_fingerprint_status(struct vnode *vp, fingerprint_status_t fp_status) { struct vnode *ldvp; /* recurse until we find the real storage */ ldvp = mac_veriexec_bottom_vnode(vp); if (ldvp != NULL) { mac_veriexec_set_fingerprint_status(ldvp, fp_status); return; } SLOT_SET(vp->v_label, fp_status); } /** * @brief Set verified execution subsystem state flags * * @note Flags can only be added to the current state, not removed. * * @param state state flags to add to the current state */ void mac_veriexec_set_state(int state) { mac_veriexec_state |= state; } /** * @brief Determine if the process is trusted * * @param cred credentials to use * @param p the process in question * * @return 1 if the process is trusted, otherwise 0. */ int mac_veriexec_proc_is_trusted(struct ucred *cred, struct proc *p) { int already_locked, error, flags; /* Make sure we lock the process if we do not already have the lock */ already_locked = PROC_LOCKED(p); if (!already_locked) PROC_LOCK(p); error = mac_veriexec_metadata_get_executable_flags(cred, p, &flags, 0); /* Unlock the process if we locked it previously */ if (!already_locked) PROC_UNLOCK(p); /* Any errors, deny access */ if (error != 0) return (0); /* Check that the trusted flag is set */ return ((flags & VERIEXEC_TRUSTED) == VERIEXEC_TRUSTED); } diff --git a/sys/security/mac_veriexec/mac_veriexec_internal.h b/sys/security/mac_veriexec/mac_veriexec_internal.h index e69f34df892e..f618ac155a83 100644 --- a/sys/security/mac_veriexec/mac_veriexec_internal.h +++ b/sys/security/mac_veriexec/mac_veriexec_internal.h @@ -1,107 +1,107 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * 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) +#ifdef MAC_VERIEXEC_DEBUG # 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); int mac_veriexec_metadata_get_file_info(dev_t fsid, long fileid, unsigned long gen, int *found_dev, struct mac_veriexec_file_info **ipp, 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 */ diff --git a/sys/security/mac_veriexec/veriexec_metadata.c b/sys/security/mac_veriexec/veriexec_metadata.c index 9e99f51e7e65..4b9cc9b3052f 100644 --- a/sys/security/mac_veriexec/veriexec_metadata.c +++ b/sys/security/mac_veriexec/veriexec_metadata.c @@ -1,791 +1,794 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * 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 +#ifdef MAC_VERIEXEC_DEBUG +#include +#endif #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 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 (mac_veriexec_metadata_get_file_info(fsid, fileid, gen, NULL, NULL, VERIEXEC_FILES_FIRST) == 0); } /** * @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 search, 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 error; error = mac_veriexec_metadata_get_file_info(fsid, fileid, gen, NULL, &ip, check_files); if (error != 0) return (error); *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; error = mac_veriexec_metadata_get_file_info(vap->va_fsid, vap->va_fileid, vap->va_gen, &found_dev, &ip, check_files); if (error != 0) { 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 +#ifdef MAC_VERIEXEC_DEBUG { 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], 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 ipp pointer to location to store the info pointer * @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 */ int mac_veriexec_metadata_get_file_info(dev_t fsid, long fileid, unsigned long gen, int *found_dev, struct mac_veriexec_file_info **ipp, 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 %#jx, file %lu.%lu\n", __func__, (uintmax_t)fsid, fileid, gen)); /* 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); if (ipp != NULL) *ipp = ip; if (ip == NULL) return (ENOENT); return (0); } /** * @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); }