Index: sys/conf/files =================================================================== --- sys/conf/files +++ sys/conf/files @@ -4334,6 +4334,14 @@ security/mac_seeotheruids/mac_seeotheruids.c optional mac_seeotheruids security/mac_stub/mac_stub.c optional mac_stub security/mac_test/mac_test.c optional mac_test +security/mac_veriexec/mac_veriexec.c optional mac_veriexec +security/mac_veriexec/veriexec_fingerprint.c optional mac_veriexec +security/mac_veriexec/veriexec_metadata.c optional mac_veriexec +security/mac_veriexec/mac_veriexec_rmd160.c optional mac_veriexec_rmd160 +security/mac_veriexec/mac_veriexec_sha1.c optional mac_veriexec_sha1 +security/mac_veriexec/mac_veriexec_sha256.c optional mac_veriexec_sha256 +security/mac_veriexec/mac_veriexec_sha384.c optional mac_veriexec_sha384 +security/mac_veriexec/mac_veriexec_sha512.c optional mac_veriexec_sha512 teken/teken.c optional sc | vt ufs/ffs/ffs_alloc.c optional ffs ufs/ffs/ffs_balloc.c optional ffs Index: sys/modules/Makefile =================================================================== --- sys/modules/Makefile +++ sys/modules/Makefile @@ -223,6 +223,12 @@ mac_seeotheruids \ mac_stub \ mac_test \ + mac_veriexec \ + mac_veriexec_rmd160 \ + mac_veriexec_sha1 \ + mac_veriexec_sha256 \ + mac_veriexec_sha384 \ + mac_veriexec_sha512 \ malo \ md \ mdio \ Index: sys/modules/mac_veriexec/Makefile =================================================================== --- /dev/null +++ sys/modules/mac_veriexec/Makefile @@ -0,0 +1,45 @@ +# +# $Id: Makefile 472710 2011-10-13 03:43:06Z stevek $ +# +# Copyright (c) 2011, Juniper Networks, Inc. +# All rights reserved. +# + +.PATH: ${.PARSEDIR:H:H}/security/mac_veriexec + +KMOD = mac_veriexec +SRCS = \ + bus_if.h \ + device_if.h \ + vnode_if.h +SRCS += \ + opt_capsicum.h \ + opt_global.h \ + opt_mac.h \ + opt_veriexec.h +SRCS += \ + mac_veriexec.c \ + veriexec_fingerprint.c \ + veriexec_metadata.c + +EXPORT_SYMS+= ve_mutex \ + mac_veriexec_in_state \ + mac_veriexec_get_executable_flags + +.if defined(KERNBUILDDIR) +MKDEP= -include ${KERNBUILDDIR}/opt_global.h +.else +CFLAGS+= -include opt_global.h +MKDEP= -include opt_global.h +opt_mac.h: + echo "#define MAC_DEBUG 1" >> ${.TARGET} +opt_global.h: + echo "#define MAC 1" > ${.TARGET} +.endif + +.ifndef WITHOUT_VERIEXEC_DEBUG +CFLAGS+= -DVERIFIED_EXEC_DEBUG +.endif + +.include + Index: sys/modules/mac_veriexec_rmd160/Makefile =================================================================== --- /dev/null +++ sys/modules/mac_veriexec_rmd160/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../security/mac_veriexec +.PATH: ${.CURDIR}/../../opencrypto + +KMOD= mac_veriexec_rmd160 +SRCS= mac_veriexec_rmd160.c rmd160.c + +.include + Index: sys/modules/mac_veriexec_sha1/Makefile =================================================================== --- /dev/null +++ sys/modules/mac_veriexec_sha1/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +.PATH: ${.PARSEDIR:H:H}/security/mac_veriexec +.PATH: ${.PARSEDIR:H:H}/crypto + +KMOD= mac_veriexec_sha1 +SRCS= mac_veriexec_sha1.c sha1.c + +.include + Index: sys/modules/mac_veriexec_sha256/Makefile =================================================================== --- /dev/null +++ sys/modules/mac_veriexec_sha256/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../security/mac_veriexec +.PATH: ${.CURDIR}/../../crypto/sha2 + +KMOD= mac_veriexec_sha256 +SRCS= mac_veriexec_sha256.c sha256c.c + +.include + Index: sys/modules/mac_veriexec_sha384/Makefile =================================================================== --- /dev/null +++ sys/modules/mac_veriexec_sha384/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../security/mac_veriexec +.PATH: ${.CURDIR}/../../crypto/sha2 + +KMOD= mac_veriexec_sha384 +SRCS= mac_veriexec_sha384.c sha512c.c + +.include + Index: sys/modules/mac_veriexec_sha512/Makefile =================================================================== --- /dev/null +++ sys/modules/mac_veriexec_sha512/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../security/mac_veriexec +.PATH: ${.CURDIR}/../../crypto/sha2 + +KMOD= mac_veriexec_sha512 +SRCS= mac_veriexec_sha512.c sha512c.c + +.include + Index: sys/security/mac_veriexec/mac_veriexec.h =================================================================== --- /dev/null +++ sys/security/mac_veriexec/mac_veriexec.h @@ -0,0 +1,161 @@ +/* + * $FreeBSD$ + * + * Copyright (c) 2011, 2012, 2013, 2015, 2016, 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 */ + +/* + * 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_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 + +/* 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); +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: sys/security/mac_veriexec/mac_veriexec.c =================================================================== --- /dev/null +++ sys/security/mac_veriexec/mac_veriexec.c @@ -0,0 +1,826 @@ +/* + * $FreeBSD$ + * + * Copyright (c) 2011, 2012, 2013, 2015, 2016, 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 +#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 +#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); + +SYSCTL_DECL(_security_mac); + +SYSCTL_NODE(_security_mac, OID_AUTO, veriexec, CTLFLAG_RW, 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, 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, 0, 0, sysctl_mac_veriexec_db, + "A", "Verified execution fingerprint database"); + +static int mac_veriexec_slot; + +MALLOC_DEFINE(M_VERIEXEC, "veriexec", "Verified execution data"); + +#ifdef KLD_ON_SECURELEVEL +extern int kld_on_securelevel; +#endif + +/** + * @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_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 %u for mount %p", 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 %u, cleaning up mount", 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); + + return ((flags & VERIEXEC_NOTRACE) ? EACCES : 0); +} + +/** + * @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, but only if securelevel + * is not raised + */ + 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 %u, " + "file %lu.%lu\n", status, va.va_fsid, va.va_fileid, + 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) + * + * @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) +{ + + /* If we are not enforcing veriexec, nothing for us to check */ + if ((mac_veriexec_state & VERIEXEC_STATE_ENFORCE) == 0) + return (0); + + switch (priv) { + case PRIV_KMEM_WRITE: + if (!mac_veriexec_proc_is_trusted(cred, curproc)) + return (EPERM); + break; + default: + break; + } + 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 " + "%u, file %lu.%lu\n", va.va_fsid, + va.va_fileid, 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: + /* + * Caller wants open to fail unless there is a valid + * fingerprint registered. + */ + MAC_VERIEXEC_DBG(2, "fingerprint status is %d for dev " + "%u, file %lu.%lu\n", status, va.va_fsid, + va.va_fileid, 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); +} + +/** + * @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 and debug level */ + mac_veriexec_state = VERIEXEC_STATE_INACTIVE; + mac_veriexec_debug = 1; + + /* 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); + +#ifdef KLD_ON_SECURELEVEL + /* + * Tell linker that it's okay to load/unload KLDs when securelevel is + * raised, as we are policing it. + */ + kld_on_securelevel = 1; +#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; + int error; + + 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(&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, 0); +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 | AUDITVNODE1, + UIO_USERSPACE, arg, td); + error = namei(&nd); + if (error != 0) + break; + NDFREE(&nd, NDF_ONLY_PNBUF); + + /* Check the fingerprint status of the vnode */ + error = mac_veriexec_check_vp(td->td_ucred, nd.ni_vp, VVERIFY); + vput(nd.ni_vp); + break; + default: + error = EOPNOTSUPP; + } + return (error); +} + +static struct mac_policy_ops mac_veriexec_ops = +{ + .mpo_init = mac_veriexec_init, + .mpo_syscall = mac_veriexec_syscall, + .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_vnode_check_exec = mac_veriexec_vnode_check_exec, + .mpo_vnode_check_open = mac_veriexec_vnode_check_open, + .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, 1); + +/** + * @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; + + fps = SLOT(vp->v_label); + switch (fps) { + case FINGERPRINT_VALID: + case FINGERPRINT_INDIRECT: + case FINGERPRINT_FILE: + break; + default: + /* we may need to recurse */ + if (strcmp(vp->v_tag, "null") == 0) { + struct vnode *ldvp; + + ldvp = NULLVPTOLOWERVP(vp); + 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) +{ + + /* recurse until we find the real storage */ + if (strcmp(vp->v_tag, "null") == 0) { + struct vnode *ldvp; + + ldvp = NULLVPTOLOWERVP(vp); + 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); +} Index: sys/security/mac_veriexec/mac_veriexec_internal.h =================================================================== --- /dev/null +++ sys/security/mac_veriexec/mac_veriexec_internal.h @@ -0,0 +1,103 @@ +/* + * $FreeBSD$ + * + * Copyright (c) 2011, 2012, 2013, 2015, 2016, 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]; + 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); +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_print_db(struct sbuf *); +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: sys/security/mac_veriexec/mac_veriexec_rmd160.c =================================================================== --- /dev/null +++ sys/security/mac_veriexec/mac_veriexec_rmd160.c @@ -0,0 +1,45 @@ +/* + * $FreeBSD$ + * + * Copyright (c) 2015, 2016, 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 + +#ifndef RMD160_DIGEST_LENGTH +#define RMD160_DIGEST_LENGTH 20 +#endif + +MAC_VERIEXEC_FPMOD(RMD160, RMD160_DIGEST_LENGTH, sizeof(RMD160_CTX), + (mac_veriexec_fpop_init_t) RMD160Init, + (mac_veriexec_fpop_update_t) RMD160Update, + (mac_veriexec_fpop_final_t) RMD160Final, 1); Index: sys/security/mac_veriexec/mac_veriexec_sha1.c =================================================================== --- /dev/null +++ sys/security/mac_veriexec/mac_veriexec_sha1.c @@ -0,0 +1,49 @@ +/* + * $FreeBSD$ + * + * Copyright (c) 2011, 2012, 2013, 2015, 2016, 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 + +#define SHA1_Init (mac_veriexec_fpop_init_t) sha1_init +#define SHA1_Update (mac_veriexec_fpop_update_t) sha1_loop + +static void +SHA1_Final(unsigned char *buf, void *ctx) +{ + + sha1_result((SHA1_CTX *) ctx, (caddr_t) buf); +} + +MAC_VERIEXEC_FPMOD(SHA1, SHA1_RESULTLEN, sizeof(SHA1_CTX), SHA1_Init, + SHA1_Update, SHA1_Final, 1); Index: sys/security/mac_veriexec/mac_veriexec_sha256.c =================================================================== --- /dev/null +++ sys/security/mac_veriexec/mac_veriexec_sha256.c @@ -0,0 +1,41 @@ +/* + * $FreeBSD$ + * + * Copyright (c) 2011, 2012, 2013, 2015, 2016, 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 + +MAC_VERIEXEC_FPMOD(SHA256, SHA256_DIGEST_LENGTH, sizeof(SHA256_CTX), + (mac_veriexec_fpop_init_t) SHA256_Init, + (mac_veriexec_fpop_update_t) SHA256_Update, + (mac_veriexec_fpop_final_t) SHA256_Final, 1); Index: sys/security/mac_veriexec/mac_veriexec_sha384.c =================================================================== --- /dev/null +++ sys/security/mac_veriexec/mac_veriexec_sha384.c @@ -0,0 +1,41 @@ +/* + * $FreeBSD$ + * + * Copyright (c) 2011, 2012, 2013, 2015, 2016, 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 + +MAC_VERIEXEC_FPMOD(SHA384, SHA384_DIGEST_LENGTH, sizeof(SHA384_CTX), + (mac_veriexec_fpop_init_t) SHA384_Init, + (mac_veriexec_fpop_update_t) SHA384_Update, + (mac_veriexec_fpop_final_t) SHA384_Final, 1); Index: sys/security/mac_veriexec/mac_veriexec_sha512.c =================================================================== --- /dev/null +++ sys/security/mac_veriexec/mac_veriexec_sha512.c @@ -0,0 +1,41 @@ +/* + * $FreeBSD$ + * + * Copyright (c) 2011, 2012, 2013, 2015, 2016, 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 + +MAC_VERIEXEC_FPMOD(SHA512, SHA512_DIGEST_LENGTH, sizeof(SHA512_CTX), + (mac_veriexec_fpop_init_t) SHA512_Init, + (mac_veriexec_fpop_update_t) SHA512_Update, + (mac_veriexec_fpop_final_t) SHA512_Final, 1); Index: sys/security/mac_veriexec/veriexec_fingerprint.c =================================================================== --- /dev/null +++ sys/security/mac_veriexec/veriexec_fingerprint.c @@ -0,0 +1,450 @@ +/* + * $FreeBSD$ + * + * Copyright (c) 2011, 2012, 2013, 2015, 2016, 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 +#include +#include +#include + +#include "mac_veriexec.h" +#include "mac_veriexec_internal.h" + +/** + * @var fpops_list + * @internal + * @brief Fingerprint operations list + * + * This is essentially the list of fingerprint modules currently loaded + */ +static LIST_HEAD(fpopshead, mac_veriexec_fpops) fpops_list; + +static int mac_veriexec_late; + +static int sysctl_mac_veriexec_algorithms(SYSCTL_HANDLER_ARGS); + +SYSCTL_PROC(_security_mac_veriexec, OID_AUTO, algorithms, + CTLTYPE_STRING | CTLFLAG_RD, 0, 0, sysctl_mac_veriexec_algorithms, "A", + "Verified execution supported hashing algorithms"); + +static int +sysctl_mac_veriexec_algorithms(SYSCTL_HANDLER_ARGS) +{ + struct sbuf sb; + struct mac_veriexec_fpops *fpops; + int algorithms, error; + + algorithms = 0; + sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND); + LIST_FOREACH(fpops, &fpops_list, entries) { + if (algorithms++) + sbuf_printf(&sb, " "); + sbuf_printf(&sb, "%s", fpops->type); + } + sbuf_finish(&sb); + error = SYSCTL_OUT(req, sbuf_data(&sb), sbuf_len(&sb)); + sbuf_delete(&sb); + return (error); +} + +/** + * @internal + * @brief Consistently identify file encountering errors + * + * @param imgp image params to display + * + * @return String form of the information stored in @p imgp + */ +static char * +identify_error (struct image_params *imgp) +{ + /* + * log() the consumer of this, doesn't sleep, + * so we can use a static buffer. + */ + static char buf[MAXPATHLEN + 128]; + struct proc *p = imgp->proc; + + snprintf(buf, sizeof(buf), + "file='%s' fsid=%u fileid=%lu gen=%lu uid=%u pid=%u", + (imgp->args) ? imgp->args->fname : "", imgp->attr->va_fsid, + imgp->attr->va_fileid, imgp->attr->va_gen, p->p_ucred->cr_ruid, + p->p_pid); + + return (buf); +} + +/** + * @internal + * @brief Check the fingerprint type for the given file and evaluate the + * fingerprint for that file. + * + * It is assumed that @p fingerprint has sufficient storage to hold the + * resulting fingerprint string. + * + * @param vp vnode to check + * @param ip file info from the meta-data store + * @param td calling thread + * @param file_size size of the file to read + * @param fingerprint resulting fingerprint + * + * @return 0 on success, otherwise an error code. + */ +static int +evaluate_fingerprint(struct vnode *vp, struct mac_veriexec_file_info *ip, + struct thread *td, off_t file_size, unsigned char *fingerprint) +{ + uint8_t *filebuf; + void *ctx; + off_t offset; + size_t count, nread, resid; + int error = EINVAL; + + filebuf = malloc(PAGE_SIZE, M_VERIEXEC, M_WAITOK); + ctx = malloc(ip->ops->context_size, M_VERIEXEC, M_WAITOK); + + (ip->ops->init)(ctx); + for (offset = 0; offset < file_size; offset += nread) { + if ((offset + PAGE_SIZE) > file_size) + count = file_size - offset; + else + count = PAGE_SIZE; + + error = vn_rdwr_inchunks(UIO_READ, vp, filebuf, count, offset, + UIO_SYSSPACE, IO_NODELOCKED, td->td_ucred, NOCRED, &resid, + td); + if (error) + goto failed; + + nread = count - resid; + (ip->ops->update)(ctx, filebuf, nread); + } + (ip->ops->final)(fingerprint, ctx); + +#ifdef DEBUG_VERIEXEC_FINGERPRINT + for (offset = 0; offset < ip->ops->digest_len; offset++) + printf("%02x", fingerprint[offset]); + printf("\n"); +#endif + +failed: + free(ctx, M_VERIEXEC); + free(filebuf, M_VERIEXEC); + return (error); +} + +/** + * @internal + * @brief Compare the two given fingerprints to see if they are the same. + * + * Differing fingerprint methods may have differing lengths which + * is handled by this routine. + * + * @param ip file info from the meta-data store + * @param digest digest to compare + * + * @return 0 if the fingerprints match and non-zero if they do not. + */ +static int +fingerprintcmp(struct mac_veriexec_file_info *ip, unsigned char *digest) +{ + + return memcmp(ip->fingerprint, digest, ip->ops->digest_len); +} + +/** + * @brief Check if @p fingerprint matches the one associated with the vnode + * @p vp + * + * @param vp vnode to check + * @param ip file info from the meta-data store + * @param td calling thread + * @param file_size size of the file to read + * @param fingerprint fingerprint to compare + * + * @return 0 if they match, otherwise an error code. + */ +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) +{ + int error; + + /* reject fingerprint if writers are active */ + if (vp->v_writecount) + return (ETXTBSY); + + if ((vp->v_mount->mnt_flag & MNT_VERIFIED) != 0) { + VERIEXEC_DEBUG(2, ("file %lu.%lu on verified %s mount\n", + ip->fileid, ip->gen, vp->v_mount->mnt_vfc->vfc_name)); + + /* + * The VFS is backed by a file which has been verified. + * No need to waste time here. + */ + return (0); + } + + error = evaluate_fingerprint(vp, ip, td, file_size, fingerprint); + if (error) + return (error); + + if (fingerprintcmp(ip, fingerprint) != 0) + return (EAUTH); + + return (0); +} + +/** + * @brief Check a file signature and validate it. + * + * @param imgp parameters for the image to check + * @param check_files if 1, check the files list first, otherwise check the + * exectuables list first + * @param td calling thread + * + * @note Called with imgp->vp locked. + * + * @return 0 if the signature is valid, otherwise an error code. + */ +int +mac_veriexec_fingerprint_check_image(struct image_params *imgp, + int check_files, struct thread *td) +{ + struct proc *p = imgp->proc; + struct vnode *vp = imgp->vp; + int error; + fingerprint_status_t status; + + if (!mac_veriexec_in_state(VERIEXEC_STATE_ACTIVE)) + return 0; + + error = mac_veriexec_metadata_fetch_fingerprint_status(vp, imgp->attr, + td, check_files); + if (error && error != EAUTH) + return (error); + + /* + * By now status is set. + */ + status = mac_veriexec_get_fingerprint_status(vp); + switch (status) { + case FINGERPRINT_INVALID: /* should not happen */ + log(LOG_ERR, MAC_VERIEXEC_FULLNAME ": got unexpected " + "FINGERPRINT_INVALID: %s\n", identify_error(imgp)); + error = EPERM; + break; + + case FINGERPRINT_FILE: + if (!check_files) { + if (prison0.pr_securelevel > 1 || + mac_veriexec_in_state(VERIEXEC_STATE_ENFORCE)) + error = EPERM; + } + break; + + case FINGERPRINT_VALID: /* is ok - report so if debug is on */ + VERIEXEC_DEBUG(4, ("Fingerprint matches\n")); + break; + + case FINGERPRINT_INDIRECT: /* fingerprint ok but need to check + for direct execution */ + if (!imgp->interpreted) { + log(LOG_ERR, MAC_VERIEXEC_FULLNAME ": attempted " + "direct execution of %s ppid=%u gppid=%u\n", + identify_error(imgp), + p->p_pptr->p_pid, p->p_pptr->p_pptr->p_pid); + if (prison0.pr_securelevel > 1 || + mac_veriexec_in_state(VERIEXEC_STATE_ENFORCE)) + error = EPERM; + } + break; + + case FINGERPRINT_NOMATCH: /* does not match - whine about it */ + log(LOG_ERR, "veriexec: fingerprint for %s does not " + "match loaded value\n", identify_error(imgp)); + if (prison0.pr_securelevel > 1 || + mac_veriexec_in_state(VERIEXEC_STATE_ENFORCE)) + error = EAUTH; + break; + + case FINGERPRINT_NOENTRY: /* no entry in the list, complain */ + log(LOG_ERR, "veriexec: no fingerprint for %s\n", + identify_error(imgp)); + if (prison0.pr_securelevel > 1 || + mac_veriexec_in_state(VERIEXEC_STATE_ENFORCE)) + error = EAUTH; + break; + + case FINGERPRINT_NODEV: /* no signatures for the device, complain */ + log(LOG_ERR, "veriexec: no signatures for device. %s\n", + identify_error(imgp)); + if (prison0.pr_securelevel > 1 || + mac_veriexec_in_state(VERIEXEC_STATE_ENFORCE)) + error = EAUTH; + break; + + default: /* this should never happen. */ + log(LOG_ERR, "veriexec: invalid status field for vnode %p\n", + vp); + error = EPERM; + } + return error; +} + +/** + * @brief Look up the fingerprint operations for a specific digest type + * + * @return A pointer to fingerprint operations, if found, or else @c NULL. + */ +struct mac_veriexec_fpops * +mac_veriexec_fingerprint_lookup_ops(const char *type) +{ + struct mac_veriexec_fpops *fpops; + + if (type == NULL) + return (NULL); + + LIST_FOREACH(fpops, &fpops_list, entries) { + if (!strcasecmp(type, fpops->type)) + break; + } + return (fpops); +} + +/** + * @brief Add fingerprint operations for a specific digest type + * + * Any attempts to add a duplicate digest type results in an error. + * + * @return 0 if the ops were added successfully, otherwise an error code. + */ +int +mac_veriexec_fingerprint_add_ops(struct mac_veriexec_fpops *fpops) +{ + + /* Sanity check the ops */ + if (fpops->type == NULL || fpops->digest_len == 0 || + fpops->context_size == 0 || fpops->init == NULL || + fpops->update == NULL || fpops->final == NULL) + return (EINVAL); + + /* Make sure we do not already have ops for this digest type */ + if (mac_veriexec_fingerprint_lookup_ops(fpops->type)) + return (EEXIST); + + /* Add the ops to the list */ + LIST_INSERT_HEAD(&fpops_list, fpops, entries); + + printf("MAC/veriexec fingerprint module loaded: %s\n", fpops->type); + + return (0); +} + +/** + * @brief Initialize the fingerprint operations list + */ +void +mac_veriexec_fingerprint_init(void) +{ + + LIST_INIT(&fpops_list); +} + +/** + * @brief Handle fingerprint module events + * + * This function is called by the @c MAC_VERIEXEC_FPMOD macro. + * + * @param mod module information + * @param type event type + * @param data event-specific data + * + * @return On @c MOD_LOAD, 0 if the fingerprint ops were added successfully, + * otherwise an error code. All other event types result in an error code. + */ +int +mac_veriexec_fingerprint_modevent(module_t mod, int type, void *data) +{ + struct mac_veriexec_fpops *fpops; + int error; + + error = 0; + fpops = (struct mac_veriexec_fpops *) data; + + switch (type) { + case MOD_LOAD: + /* We do not allow late loading of fingerprint modules */ + if (mac_veriexec_late) { + printf("%s: can't load %s fingerprint module after " + "booting\n", __func__, fpops->type); + error = EBUSY; + break; + } + error = mac_veriexec_fingerprint_add_ops(fpops); + break; + case MOD_UNLOAD: + error = EBUSY; + break; + default: + error = EOPNOTSUPP; + break; + } + + return (error); +} + +/** + * @internal + * @brief Mark veriexec late initialization flag + */ +static void +mac_veriexec_late_init(void) +{ + + mac_veriexec_late = 1; +} + +SYSINIT(mac_veriexec_late, SI_SUB_MAC_LATE, SI_ORDER_ANY, + mac_veriexec_late_init, NULL); Index: sys/security/mac_veriexec/veriexec_metadata.c =================================================================== --- /dev/null +++ sys/security/mac_veriexec/veriexec_metadata.c @@ -0,0 +1,729 @@ +/* + * $FreeBSD$ + * + * Copyright (c) 2011, 2012, 2013, 2015, 2016, 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 %lu.%lu on device %lu," + " files=%d\n", fileid, gen, (unsigned long)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 %u\n", + 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); + FREE(ip, M_TEMP); + } + } + } + + /* 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 %u, file %lu\n", + __func__, 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: %u\n", 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, + 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); + FREE(ip, M_TEMP); + } + + /* Free the meta-data entry for the device */ + FREE(lp, M_TEMP); + + /* 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_TEMP); + mtx_lock(&ve_mutex); + } + + return (lp); +} + +/** + * @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); + 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; + + PROC_LOCK_ASSERT(p, MA_OWNED); + + /* 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); + if (ip == NULL) { + status = (found_dev) ? FINGERPRINT_NOENTRY : + FINGERPRINT_NODEV; + VERIEXEC_DEBUG(3, + ("fingerprint status is %d for dev %u, file " + "%lu.%lu\n", status, vap->va_fsid, 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 %u, file " + "%lu.%lu\n", + (status == FINGERPRINT_INDIRECT) ? + "indirect " : + (status == FINGERPRINT_FILE) ? + "file " : "", vap->va_fsid, + 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 %u, file " + "%lu.%lu %s != %s\n", vap->va_fsid, + 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) +{ + 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; + + /* 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); + } 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_TEMP); + 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); + + VERIEXEC_DEBUG(3, ("add file %lu.%lu (files=%d)\n", 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 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); +}