#include <sys/cdefs.h>
#include "opt_mac.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/exec.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/sbuf.h>
#ifdef MAC_VERIEXEC_DEBUG
#include <sys/syslog.h>
#endif
#include <sys/vnode.h>
#include "mac_veriexec.h"
#include "mac_veriexec_internal.h"
struct veriexec_dev_list {
dev_t fsid;
LIST_HEAD(filehead, mac_veriexec_file_info) file_head;
LIST_ENTRY(veriexec_dev_list) entries;
};
typedef LIST_HEAD(veriexec_devhead, veriexec_dev_list) veriexec_devhead_t;
struct mtx ve_mutex;
veriexec_devhead_t veriexec_dev_head;
veriexec_devhead_t veriexec_file_dev_head;
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;
if (found_dev != NULL)
*found_dev = 0;
VERIEXEC_DEBUG(3, ("searching for file %ju.%lu on device %ju,"
" files=%d\n", (uintmax_t)fileid, gen, (uintmax_t)fsid,
(head == &veriexec_file_dev_head)));
mtx_lock(&ve_mutex);
for (lp = LIST_FIRST(head); lp != NULL; lp = LIST_NEXT(lp, entries))
if (lp->fsid == fsid)
break;
if (lp != NULL) {
VERIEXEC_DEBUG(3, ("found matching dev number %ju\n",
(uintmax_t)lp->fsid));
if (found_dev != NULL)
*found_dev = 1;
LIST_FOREACH_SAFE(ip, &(lp->file_head), entries, tip) {
if (ip->fileid == fileid) {
if (ip->gen == gen)
break;
LIST_REMOVE(ip, entries);
if (ip->label)
free(ip->label, M_VERIEXEC);
free(ip, M_VERIEXEC);
}
}
}
mtx_unlock(&ve_mutex);
return (ip);
}
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));
}
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: %ju\n", (uintmax_t)lp->fsid);
mac_veriexec_print_db_dev_list(sbp, lp);
}
}
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);
}
int
mac_veriexec_metadata_has_file(dev_t fsid, long fileid, unsigned long gen)
{
return (mac_veriexec_metadata_get_file_info(fsid, fileid, gen, NULL,
NULL, VERIEXEC_FILES_FIRST) == 0);
}
static int
free_veriexec_dev(dev_t fsid, struct veriexec_devhead *head)
{
struct veriexec_dev_list *lp;
struct mac_veriexec_file_info *ip, *nip;
for (lp = LIST_FIRST(head); lp != NULL;
lp = LIST_NEXT(lp, entries))
if (lp->fsid == fsid) break;
if (lp == NULL)
return ENOENT;
LIST_REMOVE(lp, entries);
mtx_unlock(&ve_mutex);
for (ip = LIST_FIRST(&(lp->file_head)); ip != NULL; ip = nip) {
nip = LIST_NEXT(ip, entries);
LIST_REMOVE(ip, entries);
if (ip->label)
free(ip->label, M_VERIEXEC);
free(ip, M_VERIEXEC);
}
free(lp, M_VERIEXEC);
mtx_lock(&ve_mutex);
return 0;
}
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:
for (lp = LIST_FIRST(head); lp != NULL;
lp = LIST_NEXT(lp, entries))
if (lp->fsid == fsid) break;
if (lp == NULL) {
if (np == NULL) {
np = malloc(sizeof(struct veriexec_dev_list),
M_VERIEXEC, M_NOWAIT);
if (np == NULL) {
mtx_unlock(&ve_mutex);
np = malloc(sizeof(struct veriexec_dev_list),
M_VERIEXEC, M_WAITOK);
mtx_lock(&ve_mutex);
goto search;
}
}
if (np) {
lp = np;
LIST_INIT(&(lp->file_head));
lp->fsid = fsid;
LIST_INSERT_HEAD(head, lp, entries);
}
} else if (np) {
mtx_unlock(&ve_mutex);
free(np, M_VERIEXEC);
mtx_lock(&ve_mutex);
}
return (lp);
}
static size_t
mac_veriexec_init_label(char **labelp, size_t labellen, char *src,
size_t srclen)
{
char *label;
label = *labelp;
if (labellen < srclen) {
mtx_unlock(&ve_mutex);
if (label != NULL)
free(label, M_VERIEXEC);
label = malloc(srclen, M_VERIEXEC, M_WAITOK);
mtx_lock(&ve_mutex);
labellen = srclen;
*labelp = label;
}
memcpy(label, src, srclen);
return labellen;
}
int
mac_veriexec_metadata_unmounted(dev_t fsid, struct thread *td)
{
int error;
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;
}
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 error;
error = mac_veriexec_metadata_get_file_info(fsid, fileid, gen, NULL,
&ip, check_files);
if (error != 0)
return (error);
*flags = ip->flags;
return (0);
}
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_vn = p->p_textvp;
if (proc_vn == NULL)
return EINVAL;
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);
}
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;
if (mac_veriexec_metadata_get_file_info(vap->va_fsid,
vap->va_fileid, vap->va_gen, &found_dev, &ip,
check_files) != 0) {
status = (found_dev) ? FINGERPRINT_NOENTRY :
FINGERPRINT_NODEV;
VERIEXEC_DEBUG(3,
("fingerprint status is %d for dev %ju, file "
"%ju.%lu\n", status, (uintmax_t)vap->va_fsid,
(uintmax_t)vap->va_fileid, vap->va_gen));
} else {
error = mac_veriexec_fingerprint_check_vnode(vp, ip,
td, vap->va_size, digest);
switch (error) {
case 0:
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 %ju, file "
"%ju.%lu\n",
(status == FINGERPRINT_INDIRECT) ?
"indirect " :
(status == FINGERPRINT_FILE) ?
"file " : "", (uintmax_t)vap->va_fsid,
(uintmax_t)vap->va_fileid, vap->va_gen));
break;
case EAUTH:
#ifdef MAC_VERIEXEC_DEBUG
{
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 %ju, file "
"%ju.%lu %s != %s\n",
(uintmax_t)vap->va_fsid,
(uintmax_t)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);
}
const char *
mac_veriexec_metadata_get_file_label(dev_t fsid, long fileid,
unsigned long gen, int check_files)
{
struct mac_veriexec_file_info *ip;
int error;
error = mac_veriexec_metadata_get_file_info(fsid, fileid, gen, NULL,
&ip, check_files);
if (error)
return (NULL);
return ((ip->flags & VERIEXEC_LABEL) != 0 ? ip->label : NULL);
}
int
mac_veriexec_metadata_add_file(int file_dev, dev_t fsid, long fileid,
unsigned long gen, unsigned char fingerprint[MAXFINGERPRINTLEN],
char *label, size_t labellen, 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;
if ((flags & VERIEXEC_LABEL) != 0 && (label == NULL || labellen == 0))
return (EINVAL);
if (file_dev)
head = &veriexec_file_dev_head;
else
head = &veriexec_dev_head;
lp = find_veriexec_dev(fsid, head);
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)) {
if (ip->fileid == fileid && ip->gen == gen) {
if (override) {
ip->flags = flags;
ip->ops = fpops;
memcpy(ip->fingerprint, fingerprint,
fpops->digest_len);
if (flags & VERIEXEC_LABEL) {
ip->labellen = mac_veriexec_init_label(
&ip->label, ip->labellen, label,
labellen);
} else if (ip->labellen > 0) {
free(ip->label, M_VERIEXEC);
ip->labellen = 0;
ip->label = NULL;
}
} else if ((flags & (VERIEXEC_INDIRECT|VERIEXEC_FILE)))
ip->flags |= flags;
if (np) {
mtx_unlock(&ve_mutex);
free(np, M_VERIEXEC);
mtx_lock(&ve_mutex);
}
return (0);
}
}
if (np == NULL) {
np = malloc(sizeof(struct mac_veriexec_file_info), M_VERIEXEC,
M_NOWAIT);
if (np == NULL) {
mtx_unlock(&ve_mutex);
np = malloc(sizeof(struct mac_veriexec_file_info),
M_VERIEXEC, M_WAITOK);
mtx_lock(&ve_mutex);
goto search;
}
}
ip = np;
ip->flags = flags;
ip->ops = fpops;
ip->fileid = fileid;
ip->gen = gen;
ip->label = NULL;
ip->labellen = 0;
memcpy(ip->fingerprint, fingerprint, fpops->digest_len);
if (flags & VERIEXEC_LABEL)
ip->labellen = mac_veriexec_init_label(&ip->label,
ip->labellen, label, labellen);
VERIEXEC_DEBUG(3, ("add file %ju.%lu (files=%d)\n",
(uintmax_t)ip->fileid,
ip->gen, file_dev));
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);
}
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)
{
struct veriexec_devhead *search[3];
struct mac_veriexec_file_info *ip;
int x;
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 %#jx, file %lu.%lu\n",
__func__, (uintmax_t)fsid, fileid, gen));
for (ip = NULL, x = 0; ip == NULL && search[x]; x++)
ip = get_veriexec_file(search[x], fsid, fileid, gen, found_dev);
if (ipp != NULL)
*ipp = ip;
if (ip == NULL)
return (ENOENT);
return (0);
}
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);
}