HardenedBSD/sys/security/mac_veriexec/veriexec_fingerprint.c
2023-04-16 21:23:00 -04:00

468 lines
12 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* 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 <sys/cdefs.h>
#include "opt_mac.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/imgact.h>
#include <sys/jail.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/mutex.h>
#include <sys/priv.h>
#include <sys/proc.h>
#include <sys/sbuf.h>
#include <sys/syslog.h>
#include <sys/vnode.h>
#include <security/mac/mac_framework.h>
#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 | CTLFLAG_NEEDGIANT,
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
* @param td calling thread
* @param msg message to display
*
* @return String form of the information stored in @p imgp
*/
static void
identify_error (struct image_params *imgp, struct thread *td, const char *msg)
{
struct proc *parent;
pid_t ppid, gppid;
parent = imgp->proc->p_pptr;
ppid = (parent != NULL) ? parent->p_pid : 0;
gppid = (parent != NULL && parent->p_pptr != NULL) ?
parent->p_pptr->p_pid : 0;
log(LOG_ERR, MAC_VERIEXEC_FULLNAME ": %s (file=%s fsid=%ju fileid=%ju "
"gen=%lu uid=%u pid=%u ppid=%u gppid=%u)", msg,
(imgp->args != NULL) ? imgp->args->fname : "",
(uintmax_t)imgp->attr->va_fsid, (uintmax_t)imgp->attr->va_fileid,
imgp->attr->va_gen, td->td_ucred->cr_ruid, imgp->proc->p_pid,
ppid, gppid);
}
/**
* @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 > 0)
return (ETXTBSY);
if ((vp->v_mount->mnt_flag & MNT_VERIFIED) != 0) {
VERIEXEC_DEBUG(2, ("file %ju.%lu on verified %s mount\n",
(uintmax_t)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 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 */
identify_error(imgp, td, "got unexpected FINGERPRINT_INVALID");
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 &&
mac_priv_grant(td->td_ucred, PRIV_VERIEXEC_DIRECT) != 0) {
identify_error(imgp, td, "attempted direct execution");
if (prison0.pr_securelevel > 1 ||
mac_veriexec_in_state(VERIEXEC_STATE_ENFORCE))
error = EPERM;
}
break;
case FINGERPRINT_NOMATCH: /* does not match - whine about it */
identify_error(imgp, td,
"fingerprint does not match loaded value");
if (prison0.pr_securelevel > 1 ||
mac_veriexec_in_state(VERIEXEC_STATE_ENFORCE))
error = EAUTH;
break;
case FINGERPRINT_NOENTRY: /* no entry in the list, complain */
identify_error(imgp, td, "no fingerprint");
if (prison0.pr_securelevel > 1 ||
mac_veriexec_in_state(VERIEXEC_STATE_ENFORCE))
error = EAUTH;
break;
case FINGERPRINT_NODEV: /* no signatures for the device, complain */
identify_error(imgp, td, "no signatures for device");
if (prison0.pr_securelevel > 1 ||
mac_veriexec_in_state(VERIEXEC_STATE_ENFORCE))
error = EAUTH;
break;
default: /* this should never happen. */
identify_error(imgp, td, "invalid status field for vnode");
error = EPERM;
}
switch (status) {
case FINGERPRINT_NODEV:
case FINGERPRINT_NOENTRY:
/*
* Check if this process has override allowed.
* This will only be true if PRIV_VERIEXEC_DIRECT
* already succeeded.
*/
if (error == EAUTH &&
mac_priv_grant(td->td_ucred, PRIV_VERIEXEC_NOVERIFY) == 0) {
error = 0;
}
break;
default:
break;
}
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);