version 1.5, 2005/02/27 00:26:58 |
version 1.5.2.14, 2005/08/15 12:38:03 |
|
|
/* $NetBSD$ */ |
/* $NetBSD$ */ |
|
|
/*- |
/*- |
* Copyright (c) 1998-1999 Brett Lymn |
* Copyright 2005 Elad Efrat <elad@bsd.org.il> |
* (blymn@baea.com.au, brett_lymn@yahoo.com.au) |
* Copyright 2005 Brett Lymn <blymn@netbsd.org> |
* All rights reserved. |
|
* |
* |
* This code has been donated to The NetBSD Foundation by the Author. |
* This code is derived from software contributed to The NetBSD Foundation |
|
* by Brett Lymn and Elad Efrat |
* |
* |
* Redistribution and use in source and binary forms, with or without |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions |
* modification, are permitted provided that the following conditions |
* are met: |
* are met: |
* 1. Redistributions of source code must retain the above copyright |
* 1. Redistributions of source code must retain the above copyright |
* notice, this list of conditions and the following disclaimer. |
* notice, this list of conditions and the following disclaimer. |
* 2. The name of the author may not be used to endorse or promote products |
* 2. Neither the name of The NetBSD Foundation nor the names of its |
* derived from this software withough specific prior written permission |
* contributors may be used to endorse or promote products derived |
* |
* from this software without specific prior written permission. |
* 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. |
|
* |
|
* |
* |
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
|
* ``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 FOUNDATION OR CONTRIBUTORS |
|
* 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 <sys/cdefs.h> |
|
#if defined(__NetBSD__) |
__KERNEL_RCSID(0, "$NetBSD$"); |
__KERNEL_RCSID(0, "$NetBSD$"); |
|
#else |
|
__RCSID("$Id$\n$NetBSD$"); |
|
#endif |
|
|
#include <sys/param.h> |
#include <sys/param.h> |
#include <sys/systm.h> |
#include <sys/systm.h> |
#include <sys/proc.h> |
#include <sys/proc.h> |
#include <sys/errno.h> |
#include <sys/errno.h> |
#include <sys/verified_exec.h> |
|
#include <sys/buf.h> |
#include <sys/buf.h> |
#include <sys/malloc.h> |
#include <sys/malloc.h> |
|
|
|
#ifdef __FreeBSD__ |
|
#include <sys/kernel.h> |
|
#include <sys/device_port.h> |
|
#include <sys/ioccom.h> |
|
#else |
#include <sys/ioctl.h> |
#include <sys/ioctl.h> |
#include <sys/device.h> |
#include <sys/device.h> |
|
#define DEVPORT_DEVICE struct device |
|
#endif |
|
|
#include <sys/conf.h> |
#include <sys/conf.h> |
#include <sys/lock.h> |
#include <sys/lock.h> |
#include <sys/queue.h> |
#include <sys/queue.h> |
#include <sys/vnode.h> |
#include <sys/vnode.h> |
#include <sys/fcntl.h> |
#include <sys/fcntl.h> |
#include <sys/namei.h> |
#include <sys/namei.h> |
|
#include <sys/sysctl.h> |
|
#define VERIEXEC_NEED_NODE |
|
#include <sys/verified_exec.h> |
|
|
struct verified_exec_softc { |
/* count of number of times device is open (we really only allow one open) */ |
struct device veriexec_dev; |
static unsigned int veriexec_dev_usage; |
}; |
|
|
|
const struct cdevsw verifiedexec_cdevsw = { |
struct veriexec_softc { |
verifiedexecopen, verifiedexecclose, noread, nowrite, |
DEVPORT_DEVICE veriexec_dev; |
verifiedexecioctl, nostop, notty, nopoll, nommap, nokqfilter, |
|
}; |
}; |
|
|
/* internal structures */ |
#if defined(__FreeBSD__) |
LIST_HEAD(veriexec_devhead, veriexec_dev_list) veriexec_dev_head; |
# define CDEV_MAJOR 216 |
/*LIST_HEAD(veriexec_file_devhead, veriexec_dev_list) veriexec_file_dev_head;*/ |
# define BDEV_MAJOR -1 |
struct veriexec_devhead veriexec_file_dev_head; |
#endif |
|
|
|
const struct cdevsw veriexec_cdevsw = { |
|
veriexecopen, |
|
veriexecclose, |
|
noread, |
|
nowrite, |
|
veriexecioctl, |
|
#ifdef __NetBSD__ |
|
nostop, |
|
notty, |
|
#endif |
|
nopoll, |
|
nommap, |
|
#if defined(__NetBSD__) |
|
nokqfilter, |
|
#elif defined(__FreeBSD__) |
|
nostrategy, |
|
"veriexec", |
|
CDEV_MAJOR, |
|
nodump, |
|
nopsize, |
|
0, /* flags */ |
|
BDEV_MAJOR |
|
#endif |
|
}; |
|
|
/* Autoconfiguration glue */ |
/* Autoconfiguration glue */ |
void verifiedexecattach(struct device *parent, struct device *self, |
void veriexecattach(DEVPORT_DEVICE *parent, DEVPORT_DEVICE *self, |
void *aux); |
void *aux); |
int verifiedexecopen(dev_t dev, int flags, int fmt, struct proc *p); |
int veriexecopen(dev_t dev, int flags, int fmt, struct proc *p); |
int verifiedexecclose(dev_t dev, int flags, int fmt, struct proc *p); |
int veriexecclose(dev_t dev, int flags, int fmt, struct proc *p); |
int verifiedexecioctl(dev_t dev, u_long cmd, caddr_t data, int flags, |
int veriexecioctl(dev_t dev, u_long cmd, caddr_t data, int flags, |
struct proc *p); |
struct proc *p); |
void add_veriexec_inode(struct veriexec_dev_list *list, unsigned long inode, |
|
unsigned char fingerprint[MAXFINGERPRINTLEN], |
|
unsigned char type, unsigned char fp_type); |
|
struct veriexec_dev_list *find_veriexec_dev(unsigned long dev, |
|
struct veriexec_devhead *head); |
|
|
|
/* |
|
* Attach for autoconfig to find. Initialise the lists and return... |
|
*/ |
|
void |
void |
verifiedexecattach(struct device *parent, struct device *self, void *aux) |
veriexecattach(DEVPORT_DEVICE *parent, DEVPORT_DEVICE *self, |
|
void *aux) |
{ |
{ |
LIST_INIT(&veriexec_dev_head); |
veriexec_dev_usage = 0; |
LIST_INIT(&veriexec_file_dev_head); |
|
} |
|
|
|
int |
if (veriexec_verbose >= 2) |
verifiedexecopen(dev_t dev, int flags, int fmt, struct proc *p) |
printf("Veriexec: veriexecattach: Veriexec pseudo-device" |
{ |
"attached.\n"); |
return 0; |
|
} |
} |
|
|
int |
int |
verifiedexecclose(dev_t dev, int flags, int fmt, struct proc *p) |
veriexecopen(dev_t dev __unused, int flags __unused, |
|
int fmt __unused, struct proc *p __unused) |
{ |
{ |
#ifdef VERIFIED_EXEC_DEBUG_VERBOSE |
if (veriexec_verbose >= 2) { |
struct veriexec_dev_list *lp; |
printf("Veriexec: veriexecopen: Veriexec load device " |
struct veriexec_inode_list *ip; |
"open attempt by uid=%u, pid=%u. (dev=%d)\n", |
|
p->p_ucred->cr_uid, p->p_pid, dev); |
printf("Loaded exec fingerprint list is:\n"); |
|
for (lp = LIST_FIRST(&veriexec_dev_head); lp != NULL; |
|
lp = LIST_NEXT(lp, entries)) { |
|
for (ip = LIST_FIRST(&(lp->inode_head)); ip != NULL; |
|
ip = LIST_NEXT(ip, entries)) { |
|
printf("Got loaded fingerprint for dev %lu, inode %lu\n", |
|
lp->id, ip->inode); |
|
} |
|
} |
} |
|
|
printf("\n\nLoaded file fingerprint list is:\n"); |
if (suser(p->p_ucred, &p->p_acflag) != 0) |
for (lp = LIST_FIRST(&veriexec_file_dev_head); lp != NULL; |
return (EPERM); |
lp = LIST_NEXT(lp, entries)) { |
|
for (ip = LIST_FIRST(&(lp->inode_head)); ip != NULL; |
|
ip = LIST_NEXT(ip, entries)) { |
|
printf("Got loaded fingerprint for dev %lu, inode %lu\n", |
|
lp->id, ip->inode); |
|
} |
|
} |
|
#endif |
|
return 0; |
|
} |
|
|
|
/* |
if (veriexec_dev_usage > 0) { |
* Search the list of devices looking for the one given. If it is not |
if (veriexec_verbose >= 2) |
* in the list then add it. |
printf("Veriexec: load device already in use.\n"); |
*/ |
|
struct veriexec_dev_list * |
|
find_veriexec_dev(unsigned long dev, struct veriexec_devhead *head) |
|
{ |
|
struct veriexec_dev_list *lp; |
|
|
|
for (lp = LIST_FIRST(head); lp != NULL; |
return(EBUSY); |
lp = LIST_NEXT(lp, entries)) |
|
if (lp->id == dev) break; |
|
|
|
if (lp == NULL) { |
|
/* if pointer is null then entry not there, add a new one */ |
|
MALLOC(lp, struct veriexec_dev_list *, |
|
sizeof(struct veriexec_dev_list), M_TEMP, M_WAITOK); |
|
LIST_INIT(&(lp->inode_head)); |
|
lp->id = dev; |
|
LIST_INSERT_HEAD(head, lp, entries); |
|
} |
} |
|
|
return lp; |
veriexec_dev_usage++; |
|
return (0); |
} |
} |
|
|
/* |
int |
* Add a file's inode and fingerprint to the list of inodes attached |
veriexecclose(dev_t dev __unused, int flags __unused, |
* to the device id. Only add the entry if it is not already on the |
int fmt __unused, struct proc *p __unused) |
* list. |
|
*/ |
|
void |
|
add_veriexec_inode(struct veriexec_dev_list *list, unsigned long inode, |
|
unsigned char fingerprint[MAXFINGERPRINTLEN], |
|
unsigned char type, unsigned char fp_type) |
|
{ |
{ |
struct veriexec_inode_list *ip; |
if (veriexec_dev_usage > 0) |
|
veriexec_dev_usage--; |
for (ip = LIST_FIRST(&(list->inode_head)); ip != NULL; |
return (0); |
ip = LIST_NEXT(ip, entries)) |
|
/* check for a dupe inode in the list, skip if an entry |
|
* exists for this inode except for when the type is |
|
* VERIEXEC_INDIRECT, always set the type when it is so |
|
* we don't get a hole caused by conflicting types on |
|
* hardlinked files. XXX maybe we should validate |
|
* fingerprint is same and complain if it is not... |
|
*/ |
|
if (ip->inode == inode) { |
|
if (type == VERIEXEC_INDIRECT) |
|
ip->type = type; |
|
return; |
|
} |
|
|
|
|
|
MALLOC(ip, struct veriexec_inode_list *, sizeof(struct veriexec_inode_list), |
|
M_TEMP, M_WAITOK); |
|
ip->type = type; |
|
ip->fp_type = fp_type; |
|
ip->inode = inode; |
|
memcpy(ip->fingerprint, fingerprint, sizeof(ip->fingerprint)); |
|
LIST_INSERT_HEAD(&(list->inode_head), ip, entries); |
|
} |
} |
|
|
/* |
|
* Handle the ioctl for the device |
|
*/ |
|
int |
int |
verifiedexecioctl(dev_t dev, u_long cmd, caddr_t data, int flags, |
veriexecioctl(dev_t dev __unused, u_long cmd, caddr_t data, |
struct proc *p) |
int flags __unused, struct proc *p) |
{ |
{ |
int error = 0; |
struct veriexec_hashtbl *tbl; |
struct verified_exec_params *params = (struct verified_exec_params *)data; |
|
struct nameidata nid; |
struct nameidata nid; |
struct vattr vattr; |
struct vattr va; |
struct veriexec_dev_list *dlp; |
int error = 0; |
|
u_long hashmask; |
|
|
#ifdef VERIFIED_EXEC_DEBUG |
if (veriexec_strict > 0) { |
printf("veriexec_ioctl: got cmd 0x%lx for file %s\n", cmd, params->file); |
printf("Veriexec: veriexecioctl: Strict mode, modifying " |
#endif |
"veriexec tables is not permitted.\n"); |
|
|
|
return (EPERM); |
|
} |
|
|
switch (cmd) { |
switch (cmd) { |
case VERIEXECLOAD: |
case VERIEXEC_TABLESIZE: { |
if (securelevel > 0) { |
struct veriexec_sizing_params *params = |
/* don't allow updates when secure */ |
(struct veriexec_sizing_params *) data; |
error = EPERM; |
u_char node_name[16]; |
} else { |
|
/* |
/* Check for existing table for device. */ |
* Get the attributes for the file name passed |
if (veriexec_tblfind(params->dev) != NULL) |
* stash the file's device id and inode number |
return (EEXIST); |
* along with it's fingerprint in a list for |
|
* exec to use later. |
/* Allocate and initialize a Veriexec hash table. */ |
*/ |
tbl = malloc(sizeof(struct veriexec_hashtbl), M_TEMP, |
NDINIT(&nid, LOOKUP, FOLLOW, UIO_USERSPACE, |
M_WAITOK); |
params->file, p); |
tbl->hash_size = params->hash_size; |
if ((error = vn_open(&nid, FREAD, 0)) != 0) { |
tbl->hash_dev = params->dev; |
return(error); |
tbl->hash_tbl = hashinit(params->hash_size, HASH_LIST, M_TEMP, |
} |
M_WAITOK, &hashmask); |
error = VOP_GETATTR(nid.ni_vp, &vattr, p->p_ucred, p); |
tbl->hash_count = 0; |
if (error) { |
|
nid.ni_vp->fp_status = FINGERPRINT_INVALID; |
LIST_INSERT_HEAD(&veriexec_tables, tbl, hash_list); |
VOP_UNLOCK(nid.ni_vp, 0); |
|
(void) vn_close(nid.ni_vp, FREAD, |
snprintf(node_name, sizeof(node_name), "dev_%u", |
p->p_ucred, p); |
tbl->hash_dev); |
return(error); |
|
} |
sysctl_createv(NULL, 0, &veriexec_count_node, NULL, |
/* invalidate the node fingerprint status |
CTLFLAG_READONLY, CTLTYPE_QUAD, node_name, |
* which will have been set in the vn_open |
NULL, NULL, 0, &tbl->hash_count, 0, |
* and would always be FINGERPRINT_NOTFOUND |
tbl->hash_dev, CTL_EOL); |
|
|
|
break; |
|
} |
|
|
|
case VERIEXEC_LOAD: { |
|
struct veriexec_params *params = |
|
(struct veriexec_params *) data; |
|
struct veriexec_hash_entry *hh; |
|
struct veriexec_hash_entry *e; |
|
|
|
NDINIT(&nid, LOOKUP, FOLLOW, UIO_SYSSPACE, params->file, p); |
|
error = namei(&nid); |
|
if (error) |
|
return (error); |
|
|
|
/* Add only regular files. */ |
|
if (nid.ni_vp->v_type != VREG) { |
|
printf("Veriexec: veriexecioctl: Not adding \"%s\": " |
|
"Not a regular file.\n", params->file); |
|
vrele(nid.ni_vp); |
|
return (EINVAL); |
|
} |
|
|
|
/* Get attributes for device and inode. */ |
|
error = VOP_GETATTR(nid.ni_vp, &va, p->p_ucred, p); |
|
if (error) |
|
return (error); |
|
|
|
/* Release our reference to the vnode. (namei) */ |
|
vrele(nid.ni_vp); |
|
|
|
/* Get table for the device. */ |
|
tbl = veriexec_tblfind(va.va_fsid); |
|
if (tbl == NULL) { |
|
return (EINVAL); |
|
} |
|
|
|
hh = veriexec_lookup(va.va_fsid, va.va_fileid); |
|
if (hh != NULL) { |
|
/* |
|
* Duplicate entry means something is wrong in |
|
* the signature file. Just give collision info |
|
* and return. |
*/ |
*/ |
nid.ni_vp->fp_status = FINGERPRINT_INVALID; |
printf("veriexec: Duplicate entry. [%s, %ld:%lu] " |
VOP_UNLOCK(nid.ni_vp, 0); |
"old[type=0x%02x, algorithm=%s], " |
(void) vn_close(nid.ni_vp, FREAD, p->p_ucred, p); |
"new[type=0x%02x, algorithm=%s] " |
/* vattr.va_fsid = dev, vattr.va_fileid = inode */ |
"(%s fingerprint)\n", |
if (params->type == VERIEXEC_FILE) { |
params->file, va.va_fsid, va.va_fileid, |
dlp = find_veriexec_dev(vattr.va_fsid, |
hh->type, hh->ops->type, |
&veriexec_file_dev_head); |
params->type, params->fp_type, |
} else { |
(((hh->ops->hash_len != params->size) || |
dlp = find_veriexec_dev(vattr.va_fsid, |
(memcmp(hh->fp, params->fingerprint, |
&veriexec_dev_head); |
min(hh->ops->hash_len, params->size)) |
} |
!= 0)) ? "different" : "same")); |
|
|
add_veriexec_inode(dlp, vattr.va_fileid, |
return (0); |
params->fingerprint, params->type, |
} |
params->fp_type); |
|
|
e = malloc(sizeof(*e), M_TEMP, M_WAITOK); |
|
e->inode = va.va_fileid; |
|
e->type = params->type; |
|
e->status = FINGERPRINT_NOTEVAL; |
|
if ((e->ops = veriexec_find_ops(params->fp_type)) == NULL) { |
|
free(e, M_TEMP); |
|
printf("Veriexec: veriexecioctl: Invalid or unknown " |
|
"fingerprint type \"%s\" for file \"%s\" " |
|
"(dev=%ld, inode=%ld)\n", params->fp_type, |
|
params->file, va.va_fsid, va.va_fileid); |
|
return(EINVAL); |
} |
} |
|
|
|
/* |
|
* Just a bit of a sanity check - require the size of |
|
* the fp to be passed in, check this against the expected |
|
* size. Of course userland could lie deliberately, this |
|
* really only protects against the obvious fumble of |
|
* changing the fp type but not updating the fingerprint |
|
* string. |
|
*/ |
|
if (e->ops->hash_len != params->size) { |
|
printf("Veriexec: veriexecioctl: Inconsistent " |
|
"fingerprint size for type \"%s\" for file " |
|
"\"%s\" (dev=%ld, inode=%ld), size was %u " |
|
"was expecting %d\n", params->fp_type, |
|
params->file, va.va_fsid, va.va_fileid, |
|
params->size, e->ops->hash_len); |
|
free(e, M_TEMP); |
|
return(EINVAL); |
|
} |
|
|
|
e->fp = malloc(e->ops->hash_len, M_TEMP, M_WAITOK); |
|
memcpy(e->fp, params->fingerprint, e->ops->hash_len); |
|
|
|
veriexec_report("New entry.", params->file, &va, NULL, |
|
REPORT_VERBOSE_HIGH, REPORT_NOALARM, |
|
REPORT_NOPANIC); |
|
|
|
error = veriexec_hashadd(tbl, e); |
|
|
break; |
break; |
|
} |
|
|
default: |
default: |
|
/* Invalid operation. */ |
error = ENODEV; |
error = ENODEV; |
|
|
|
break; |
} |
} |
|
|
return (error); |
return (error); |
} |
} |
|
|
|
#if defined(__FreeBSD__) |
|
static void |
|
veriexec_drvinit(void *unused __unused) |
|
{ |
|
make_dev(&verifiedexec_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, |
|
"veriexec"); |
|
verifiedexecattach(0, 0, 0); |
|
} |
|
|
|
SYSINIT(veriexec, SI_SUB_PSEUDO, SI_ORDER_ANY, veriexec_drvinit, NULL); |
|
#endif |