veriexec: Additional functionality for MAC/veriexec

Ensure veriexec opens the file before doing any read operations.

When the MAC_VERIEXEC_CHECK_PATH_SYSCALL syscall is requested, veriexec
needs to open the file before calling mac_veriexec_check_vp. This is to
ensure any set up is done by the file system. Most file systems do not
explicitly need an open, but some (e.g. virtfs) require initialization
of access tokens (file identifiers, etc.) before doing any read or write
operations.

The evaluate_fingerprint() function needs to ensure it has an open file
for reading in order to evaluate the fingerprint. The ideal solution is
to have a hook after the VOP_OPEN call in vn_open. For now, we open the
file for reading, envaluate the fingerprint, and close the file. While
this leaves a potential hole that could possibly be taken advantage of
by a dedicated aversary, this code path is not typically visited often
in our use cases, as we primarily encounter verified mounts and not
individual files. This should be considered a temporary workaround until
discussions about the post-open hook have concluded and the hook becomes
available.

Add MAC_VERIEXEC_GET_PARAMS_PATH_SYSCALL and
MAC_VERIEXEC_GET_PARAMS_PID_SYSCALL to mac_veriexec_syscall so we can
fetch and check label contents in an unconstrained manner.

Add a check for PRIV_VERIEXEC_CONTROL to do ioctl on /dev/veriexec

Make it clear that trusted process cannot be debugged. Attempts to debug
a trusted process already fail, but the failure path is very obscure.
Add an explicit check for VERIEXEC_TRUSTED in
mac_veriexec_proc_check_debug.

We need mac_veriexec_priv_check to not block PRIV_KMEM_WRITE if
mac_priv_gant() says it is ok.

Reviewed by:	sjg
Obtained from:	Juniper Networks, Inc.
This commit is contained in:
Steve Kiernan 2023-04-02 12:33:10 -07:00 committed by Stephen J. Kiernan
parent 4654ba28fb
commit 8512d82ea0
10 changed files with 343 additions and 39 deletions

View File

@ -8,7 +8,9 @@ INCS= libveriexec.h
WARNS?= 2
SRCS= veriexec_check.c
SRCS= \
veriexec_check.c \
veriexec_get.c
.include <bsd.lib.mk>

View File

@ -29,9 +29,17 @@
#ifndef __LIBVERIEXEC_H__
#define __LIBVERIEXEC_H__
struct mac_veriexec_syscall_params;
int veriexec_check_fd_mode(int, unsigned int);
int veriexec_check_path_mode(const char *, unsigned int);
int veriexec_check_fd(int);
int veriexec_check_path(const char *);
int veriexec_get_pid_params(pid_t, struct mac_veriexec_syscall_params *);
int veriexec_get_path_params(const char *,
struct mac_veriexec_syscall_params *);
int veriexec_check_pid_label(pid_t, const char *);
#define HAVE_VERIEXEC_CHECK_PID_LABEL 1
#endif /* __LIBVERIEXEC_H__ */

View File

@ -0,0 +1,184 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2021-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>
__FBSDID("$FreeBSD$");
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/mac.h>
#include <unistd.h>
#include <string.h>
#include <security/mac_veriexec/mac_veriexec.h>
/**
* @brief get veriexec params for a process
*
* @return
* @li 0 if successful
*/
int
veriexec_get_pid_params(pid_t pid,
struct mac_veriexec_syscall_params *params)
{
struct mac_veriexec_syscall_params_args args;
if (params == NULL)
return EINVAL;
args.u.pid = pid;
args.params = params;
return mac_syscall(MAC_VERIEXEC_NAME,
MAC_VERIEXEC_GET_PARAMS_PID_SYSCALL, &args);
}
/**
* @brief get veriexec params for a process
*
* @return
* @li 0 if successful
*/
int
veriexec_get_path_params(const char *file,
struct mac_veriexec_syscall_params *params)
{
struct mac_veriexec_syscall_params_args args;
if (file == NULL || params == NULL)
return EINVAL;
args.u.filename = file;
args.params = params;
return mac_syscall(MAC_VERIEXEC_NAME,
MAC_VERIEXEC_GET_PARAMS_PATH_SYSCALL, &args);
}
/**
* @brief check if label contains what we want
*
* @return
* @li 0 if no
* @li 1 if yes
*/
int
veriexec_check_pid_label(pid_t pid, const char *want)
{
struct mac_veriexec_syscall_params params;
char *cp;
size_t n;
if (want != NULL &&
veriexec_get_pid_params(pid, &params) == 0) {
/* Does label contain [,]<want>[,] ? */
if (params.labellen > 0 &&
(cp = strstr(params.label, want)) != NULL) {
if (cp == params.label || cp[-1] == ',') {
n = strlen(want);
if (cp[n] == '\0' || cp[n] == ',')
return 1; /* yes */
}
}
}
return 0; /* no */
}
#ifdef UNIT_TEST
#include <stdlib.h>
#include <stdio.h>
#include <err.h>
static char *
hash2hex(char *type, unsigned char *digest)
{
static char buf[2*MAXFINGERPRINTLEN+1];
size_t n;
int i;
if (strcmp(type, "SHA1") == 0) {
n = 20;
} else if (strcmp(type, "SHA256") == 0) {
n = 32;
} else if (strcmp(type, "SHA384") == 0) {
n = 48;
}
for (i = 0; i < n; i++) {
sprintf(&buf[2*i], "%02x", (unsigned)digest[i]);
}
return buf;
}
int
main(int argc, char *argv[])
{
struct mac_veriexec_syscall_params params;
pid_t pid;
char *want = NULL;
int pflag = 0;
int error;
int c;
while ((c = getopt(argc, argv, "pw:")) != -1) {
switch (c) {
case 'p':
pflag = 1;
break;
case 'w':
want = optarg;
break;
default:
break;
}
}
for (; optind < argc; optind++) {
if (pflag) {
pid = atoi(argv[optind]);
if (want) {
error = veriexec_check_pid_label(pid, want);
printf("pid=%d want='%s': %d\n",
pid, want, error);
continue;
}
error = veriexec_get_pid_params(pid, &params);
} else {
error = veriexec_get_path_params(argv[optind], &params);
}
if (error) {
err(2, "%s, error=%d", argv[optind], error);
}
printf("arg=%s, type=%s, flags=%u, label='%s', fingerprint='%s'\n",
argv[optind], params.fp_type, (unsigned)params.flags,
params.label,
hash2hex(params.fp_type, params.fingerprint));
}
return 0;
}
#endif

View File

@ -1,7 +1,7 @@
/*
* $FreeBSD$
*
* Copyright (c) 2011-2013, 2015, 2019, Juniper Networks, Inc.
* Copyright (c) 2011-2023, Juniper Networks, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -34,11 +34,8 @@
#ifndef _DEV_VERIEXEC_VERIEXEC_IOCTL_H
#define _DEV_VERIEXEC_VERIEXEC_IOCTL_H
#include <sys/param.h>
#include <security/mac_veriexec/mac_veriexec.h>
#define VERIEXEC_FPTYPELEN 16
struct verified_exec_params {
unsigned char flags;
char fp_type[VERIEXEC_FPTYPELEN]; /* type of fingerprint */

View File

@ -99,7 +99,7 @@ verifiedexecioctl(struct cdev *dev __unused, u_long cmd, caddr_t data,
*
* MAC/veriexec will grant kmem write privs to "trusted" processes.
*/
error = priv_check(td, PRIV_KMEM_WRITE);
error = priv_check(td, PRIV_VERIEXEC_CONTROL);
if (error)
return (error);

View File

@ -1,7 +1,7 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2011, 2012, 2013, 2015, 2016, 2019 Juniper Networks, Inc.
* Copyright (c) 2011-2023 Juniper Networks, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -330,7 +330,10 @@ mac_veriexec_proc_check_debug(struct ucred *cred, struct proc *p)
if (error != 0)
return (0);
return ((flags & VERIEXEC_NOTRACE) ? EACCES : 0);
error = (flags & (VERIEXEC_NOTRACE|VERIEXEC_TRUSTED)) ? EACCES : 0;
MAC_VERIEXEC_DBG(4, "%s flags=%#x error=%d", __func__, flags, error);
return (error);
}
/**
@ -406,6 +409,9 @@ mac_veriexec_kld_check_load(struct ucred *cred, struct vnode *vp,
* - 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
@ -415,20 +421,30 @@ mac_veriexec_kld_check_load(struct ucred *cred, struct vnode *vp,
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:
if (!mac_veriexec_proc_is_trusted(cred, curproc))
return (EPERM);
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 (0);
return (error);
}
/**
@ -812,7 +828,24 @@ mac_veriexec_syscall(struct thread *td, int call, void *arg)
cap_rights_t rights;
struct vattr va;
struct file *fp;
int error;
struct mac_veriexec_syscall_params_args pargs;
struct mac_veriexec_syscall_params result;
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:
error = copyin(arg, &pargs, sizeof(pargs));
if (error)
return error;
break;
}
switch (call) {
case MAC_VERIEXEC_CHECK_FD_SYSCALL:
@ -863,18 +896,69 @@ mac_veriexec_syscall(struct thread *td, int call, void *arg)
NDINIT(&nd, LOOKUP,
FOLLOW | LOCKLEAF | LOCKSHARED | AUDITVNODE1,
UIO_USERSPACE, arg);
error = namei(&nd);
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);
vput(nd.ni_vp);
/* 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;
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);
}

View File

@ -29,6 +29,8 @@
#ifndef _SECURITY_MAC_VERIEXEC_H
#define _SECURITY_MAC_VERIEXEC_H
#include <sys/param.h>
#ifdef _KERNEL
#include <sys/types.h>
#include <sys/kernel.h>
@ -42,8 +44,12 @@
#define MAC_VERIEXEC_NAME "mac_veriexec"
/* MAC/veriexec syscalls */
#define MAC_VERIEXEC_CHECK_FD_SYSCALL 1
#define MAC_VERIEXEC_CHECK_PATH_SYSCALL 2
#define MAC_VERIEXEC_CHECK_FD_SYSCALL 1
#define MAC_VERIEXEC_CHECK_PATH_SYSCALL 2
#define MAC_VERIEXEC_GET_PARAMS_PID_SYSCALL 3
#define MAC_VERIEXEC_GET_PARAMS_PATH_SYSCALL 4
#define VERIEXEC_FPTYPELEN 16 /* hash name */
/**
* Enough room for the largest signature...
@ -68,6 +74,23 @@
match signature */
#define VERIEXEC_STATE_LOCKED (1<<3) /**< Do not allow further changes */
/* for MAC_VERIEXEC_GET_PARAMS_*_SYSCALL */
struct mac_veriexec_syscall_params {
char fp_type[VERIEXEC_FPTYPELEN];
unsigned char fingerprint[MAXFINGERPRINTLEN];
char label[MAXLABELLEN];
size_t labellen;
unsigned char flags;
};
struct mac_veriexec_syscall_params_args {
union {
pid_t pid;
const char *filename;
} u; /* input only */
struct mac_veriexec_syscall_params *params; /* result */
};
#ifdef _KERNEL
/**
* Version of the MAC/veriexec module

View File

@ -78,9 +78,9 @@ int mac_veriexec_metadata_get_executable_flags(struct ucred *cred,
struct proc *p, int *flags, int check_files);
int mac_veriexec_metadata_get_file_flags(dev_t fsid, long fileid,
unsigned long gen, int *flags, int check_files);
struct mac_veriexec_file_info *
mac_veriexec_metadata_get_file_info(dev_t fsid, long fileid,
unsigned long gen, int *found_dev, int check_files);
int mac_veriexec_metadata_get_file_info(dev_t fsid, long fileid,
unsigned long gen, int *found_dev,
struct mac_veriexec_file_info **ipp, int check_files);
void mac_veriexec_metadata_init(void);
void mac_veriexec_metadata_print_db(struct sbuf *sbp);
int mac_veriexec_metadata_unmounted(dev_t fsid, struct thread *td);

View File

@ -231,7 +231,7 @@ mac_veriexec_metadata_has_file(dev_t fsid, long fileid, unsigned long gen)
{
return (mac_veriexec_metadata_get_file_info(fsid, fileid, gen, NULL,
VERIEXEC_FILES_FIRST) != NULL);
NULL, VERIEXEC_FILES_FIRST) == 0);
}
/**
@ -438,12 +438,12 @@ 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;
int error;
ip = mac_veriexec_metadata_get_file_info(fsid, fileid, gen, &found_dev,
check_files);
if (ip == NULL)
return (ENOENT);
error = mac_veriexec_metadata_get_file_info(fsid, fileid, gen, NULL,
&ip, check_files);
if (error != 0)
return (error);
*flags = ip->flags;
return (0);
@ -513,9 +513,9 @@ mac_veriexec_metadata_fetch_fingerprint_status(struct vnode *vp,
status = mac_veriexec_get_fingerprint_status(vp);
if (status == FINGERPRINT_INVALID || status == FINGERPRINT_NODEV) {
found_dev = 0;
ip = mac_veriexec_metadata_get_file_info(vap->va_fsid,
vap->va_fileid, vap->va_gen, &found_dev, check_files);
if (ip == NULL) {
error = mac_veriexec_metadata_get_file_info(vap->va_fsid,
vap->va_fileid, vap->va_gen, &found_dev, &ip, check_files);
if (error != 0) {
status = (found_dev) ? FINGERPRINT_NOENTRY :
FINGERPRINT_NODEV;
VERIEXEC_DEBUG(3,
@ -735,19 +735,20 @@ mac_veriexec_metadata_add_file(int file_dev, dev_t fsid, long fileid,
/**
* @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 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
* @param ipp pointer to location to store the info pointer
* @param check_files if 1, check the files list first, otherwise check the
* exectuables list first
*
* @return A pointer to the meta-data inforation if meta-data exists for
* the specified file identifier, otherwise @c NULL
*/
struct mac_veriexec_file_info *
int
mac_veriexec_metadata_get_file_info(dev_t fsid, long fileid, unsigned long gen,
int *found_dev, int check_files)
int *found_dev, struct mac_veriexec_file_info **ipp, int check_files)
{
struct veriexec_devhead *search[3];
struct mac_veriexec_file_info *ip;
@ -763,14 +764,18 @@ mac_veriexec_metadata_get_file_info(dev_t fsid, long fileid, unsigned long gen,
}
search[2] = NULL;
VERIEXEC_DEBUG(3, ("%s: searching for dev %ju, file %lu\n",
__func__, (uintmax_t)fsid, fileid));
VERIEXEC_DEBUG(3, ("%s: searching for dev %#jx, file %lu.%lu\n",
__func__, (uintmax_t)fsid, fileid, gen));
/* Search for the specified file */
for (ip = NULL, x = 0; ip == NULL && search[x]; x++)
ip = get_veriexec_file(search[x], fsid, fileid, gen, found_dev);
return (ip);
if (ipp != NULL)
*ipp = ip;
if (ip == NULL)
return (ENOENT);
return (0);
}
/**

View File

@ -525,11 +525,12 @@
*/
#define PRIV_VERIEXEC_DIRECT 700 /* Can override 'indirect' */
#define PRIV_VERIEXEC_NOVERIFY 701 /* Can override O_VERIFY */
#define PRIV_VERIEXEC_CONTROL 702 /* Can configure veriexec */
/*
* Track end of privilege list.
*/
#define _PRIV_HIGHEST 702
#define _PRIV_HIGHEST 703
/*
* Validate that a named privilege is known by the privilege system. Invalid