#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"
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);
}
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);
}
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);
}
static int
fingerprintcmp(struct mac_veriexec_file_info *ip, unsigned char *digest)
{
return memcmp(ip->fingerprint, digest, ip->ops->digest_len);
}
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;
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));
return (0);
}
error = evaluate_fingerprint(vp, ip, td, file_size, fingerprint);
if (error)
return (error);
if (fingerprintcmp(ip, fingerprint) != 0)
return (EAUTH);
return (0);
}
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);
status = mac_veriexec_get_fingerprint_status(vp);
switch (status) {
case FINGERPRINT_INVALID:
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:
VERIEXEC_DEBUG(4, ("Fingerprint matches\n"));
break;
case FINGERPRINT_INDIRECT:
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:
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:
identify_error(imgp, td, "no fingerprint");
if (prison0.pr_securelevel > 1 ||
mac_veriexec_in_state(VERIEXEC_STATE_ENFORCE))
error = EAUTH;
break;
case FINGERPRINT_NODEV:
identify_error(imgp, td, "no signatures for device");
if (prison0.pr_securelevel > 1 ||
mac_veriexec_in_state(VERIEXEC_STATE_ENFORCE))
error = EAUTH;
break;
default:
identify_error(imgp, td, "invalid status field for vnode");
error = EPERM;
}
switch (status) {
case FINGERPRINT_NODEV:
case FINGERPRINT_NOENTRY:
if (error == EAUTH &&
mac_priv_grant(td->td_ucred, PRIV_VERIEXEC_NOVERIFY) == 0) {
error = 0;
}
break;
default:
break;
}
return error;
}
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);
}
int
mac_veriexec_fingerprint_add_ops(struct mac_veriexec_fpops *fpops)
{
if (fpops->type == NULL || fpops->digest_len == 0 ||
fpops->context_size == 0 || fpops->init == NULL ||
fpops->update == NULL || fpops->final == NULL)
return (EINVAL);
if (mac_veriexec_fingerprint_lookup_ops(fpops->type))
return (EEXIST);
LIST_INSERT_HEAD(&fpops_list, fpops, entries);
printf("MAC/veriexec fingerprint module loaded: %s\n", fpops->type);
return (0);
}
void
mac_veriexec_fingerprint_init(void)
{
LIST_INIT(&fpops_list);
}
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:
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);
}
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);