mirror of
https://git.hardenedbsd.org/hardenedbsd/HardenedBSD.git
synced 2024-11-22 21:51:04 +01:00
bb8d4411e0
The loader tunable 'security.mac.veriexec.block_unlink' has been already flagged with CTLFLAG_RDTUN, no need to re-fetch it with TUNABLE_INT_FETCH. While here move the definition of sysctl knob out of function body, which is more common in FreeBSD. No functional change intended. Reviewed by: stevek MFC after: 1 week Differential Revision: https://reviews.freebsd.org/D42132
1187 lines
31 KiB
C
1187 lines
31 KiB
C
/*-
|
|
* 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 <sys/cdefs.h>
|
|
|
|
#include "opt_capsicum.h"
|
|
#include "opt_mac.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/capsicum.h>
|
|
#include <sys/eventhandler.h>
|
|
#include <sys/fcntl.h>
|
|
#include <sys/file.h>
|
|
#include <sys/filedesc.h>
|
|
#include <sys/imgact.h>
|
|
#include <sys/jail.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/mac.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/namei.h>
|
|
#include <sys/priv.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/sbuf.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/vnode.h>
|
|
#ifdef COMPAT_FREEBSD32
|
|
#include <sys/sysent.h>
|
|
#include <sys/stdint.h>
|
|
#include <sys/abi_compat.h>
|
|
#endif
|
|
#include <fs/nullfs/null.h>
|
|
#include <security/mac/mac_framework.h>
|
|
#include <security/mac/mac_policy.h>
|
|
|
|
#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_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;
|
|
SYSCTL_INT(_security_mac_veriexec, OID_AUTO, block_unlink, CTLFLAG_RDTUN,
|
|
&mac_veriexec_block_unlink, 0, "Veriexec unlink protection");
|
|
|
|
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);
|
|
MAC_VERIEXEC_DBG(3, "set fsid to %ju for mount %p",
|
|
(uintmax_t)va.va_fsid, mp);
|
|
}
|
|
|
|
/**
|
|
* @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);
|
|
|
|
/* 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);
|
|
}
|