559 lines
14 KiB
C
559 lines
14 KiB
C
/* $NetBSD: kern_verifiedexec.c,v 1.13 2005/04/26 10:45:41 blymn Exp $ */
|
|
|
|
/*-
|
|
* Copyright 2005 Elad Efrat <elad@bsd.org.il>
|
|
* Copyright 2005 Brett Lymn <blymn@netbsd.org>
|
|
*
|
|
* 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
|
|
* 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. Neither the name of The NetBSD Foundation nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* 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>
|
|
__KERNEL_RCSID(0, "$NetBSD: kern_verifiedexec.c,v 1.13 2005/04/26 10:45:41 blymn Exp $");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/namei.h>
|
|
#include <sys/exec.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/syslog.h>
|
|
#include <sys/verified_exec.h>
|
|
#if defined(__FreeBSD__)
|
|
# include <sys/systm.h>
|
|
# include <sys/imgact.h>
|
|
# include <crypto/sha1.h>
|
|
#else
|
|
# include <sys/sha1.h>
|
|
#endif
|
|
#include "crypto/sha2/sha2.h"
|
|
#include "crypto/ripemd160/rmd160.h"
|
|
#include <sys/md5.h>
|
|
|
|
/*int security_veriexec = 0;*/
|
|
int security_veriexec_verbose = 0;
|
|
int security_veriexec_strict = 0;
|
|
|
|
char *veriexec_fp_names;
|
|
int veriexec_name_max;
|
|
|
|
/* prototypes */
|
|
static void
|
|
veriexec_add_fp_name(char *name);
|
|
|
|
/* Veriexecs table of hash types and their associated information. */
|
|
LIST_HEAD(veriexec_ops_head, veriexec_fp_ops) veriexec_ops_list;
|
|
|
|
struct veriexec_fp_ops veriexec_default_ops[] = {
|
|
#ifdef VERIFIED_EXEC_FP_RMD160
|
|
{ "RMD160", RMD160_DIGEST_LENGTH, sizeof(RMD160_CTX),
|
|
(VERIEXEC_INIT_FN) RMD160Init,
|
|
(VERIEXEC_UPDATE_FN) RMD160Update,
|
|
(VERIEXEC_FINAL_FN) RMD160Final, {NULL, NULL}},
|
|
#endif
|
|
|
|
#ifdef VERIFIED_EXEC_FP_SHA256
|
|
{ "SHA256", SHA256_DIGEST_LENGTH, sizeof(SHA256_CTX),
|
|
(VERIEXEC_INIT_FN) SHA256_Init,
|
|
(VERIEXEC_UPDATE_FN) SHA256_Update,
|
|
(VERIEXEC_FINAL_FN) SHA256_Final, {NULL, NULL}},
|
|
#endif
|
|
|
|
#ifdef VERIFIED_EXEC_FP_SHA384
|
|
{ "SHA384", SHA384_DIGEST_LENGTH, sizeof(SHA384_CTX),
|
|
(VERIEXEC_INIT_FN) SHA384_Init,
|
|
(VERIEXEC_UPDATE_FN) SHA384_Update,
|
|
(VERIEXEC_FINAL_FN) SHA384_Final, {NULL, NULL}},
|
|
#endif
|
|
|
|
#ifdef VERIFIED_EXEC_FP_SHA512
|
|
{ "SHA512", SHA512_DIGEST_LENGTH, sizeof(SHA512_CTX),
|
|
(VERIEXEC_INIT_FN) SHA512_Init,
|
|
(VERIEXEC_UPDATE_FN) SHA512_Update,
|
|
(VERIEXEC_FINAL_FN) SHA512_Final, {NULL, NULL}},
|
|
#endif
|
|
|
|
#ifdef VERIFIED_EXEC_FP_SHA1
|
|
{ "SHA1", SHA1_DIGEST_LENGTH, sizeof(SHA1_CTX),
|
|
(VERIEXEC_INIT_FN) SHA1Init,
|
|
(VERIEXEC_UPDATE_FN) SHA1Update, (VERIEXEC_FINAL_FN) SHA1Final,
|
|
{NULL, NULL}},
|
|
#endif
|
|
|
|
#ifdef VERIFIED_EXEC_FP_MD5
|
|
{ "MD5", MD5_DIGEST_LENGTH, sizeof(MD5_CTX),
|
|
(VERIEXEC_INIT_FN) MD5Init,
|
|
(VERIEXEC_UPDATE_FN) MD5Update, (VERIEXEC_FINAL_FN) MD5Final,
|
|
{NULL, NULL}},
|
|
#endif
|
|
};
|
|
|
|
static unsigned default_ops_count =
|
|
sizeof(veriexec_default_ops) / sizeof(struct veriexec_fp_ops);
|
|
|
|
#define VERIEXEC_BUFSIZE PAGE_SIZE
|
|
|
|
/*
|
|
* Add fingerprint names to the global list.
|
|
*/
|
|
static void
|
|
veriexec_add_fp_name(char *name)
|
|
{
|
|
char *newp;
|
|
unsigned new_max;
|
|
|
|
if ((strlen(veriexec_fp_names) + VERIEXEC_TYPE_MAXLEN + 1) >=
|
|
veriexec_name_max) {
|
|
new_max = veriexec_name_max + 4 * (VERIEXEC_TYPE_MAXLEN + 1);
|
|
if ((newp = realloc(veriexec_fp_names, new_max,
|
|
M_TEMP, M_WAITOK)) == NULL) {
|
|
printf("veriexec: cannot grow storage to add new "
|
|
"fingerprint name to name list. Not adding\n");
|
|
return;
|
|
}
|
|
|
|
veriexec_fp_names = newp;
|
|
veriexec_name_max = new_max;
|
|
}
|
|
|
|
strlcat(veriexec_fp_names, name, veriexec_name_max);
|
|
strlcat(veriexec_fp_names, " ", veriexec_name_max);
|
|
}
|
|
|
|
/*
|
|
* Initialise the internal "default" fingerprint ops vector list.
|
|
*/
|
|
void
|
|
veriexec_init_fp_ops(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
veriexec_name_max = default_ops_count * (VERIEXEC_TYPE_MAXLEN + 1) + 1;
|
|
veriexec_fp_names = malloc(veriexec_name_max, M_TEMP, M_WAITOK);
|
|
veriexec_fp_names[0] = '\0';
|
|
|
|
LIST_INIT(&veriexec_ops_list);
|
|
|
|
for (i = 0; i < default_ops_count; i++) {
|
|
LIST_INSERT_HEAD(&veriexec_ops_list, &veriexec_default_ops[i],
|
|
entries);
|
|
veriexec_add_fp_name(veriexec_default_ops[i].type);
|
|
}
|
|
}
|
|
|
|
struct veriexec_fp_ops *
|
|
veriexec_find_ops(u_char *name)
|
|
{
|
|
struct veriexec_fp_ops *ops;
|
|
|
|
name[VERIEXEC_TYPE_MAXLEN] = '\0';
|
|
|
|
LIST_FOREACH(ops, &veriexec_ops_list, entries) {
|
|
if ((strlen(name) == strlen(ops->type)) &&
|
|
(strncasecmp(name, ops->type, sizeof(ops->type) - 1)
|
|
== 0))
|
|
return (ops);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
* Calculate fingerprint. Information on hash length and routines used is
|
|
* extracted from veriexec_hash_list according to the hash type.
|
|
*/
|
|
int
|
|
veriexec_fp_calc(struct proc *p, struct vnode *vp,
|
|
struct veriexec_hash_entry *vhe, uint64_t size, u_char *fp)
|
|
{
|
|
void *ctx = NULL;
|
|
u_char *buf = NULL;
|
|
off_t offset, len;
|
|
size_t resid;
|
|
int error = 0;
|
|
|
|
/* XXX: This should not happen. Print more details? */
|
|
if (vhe->ops == NULL) {
|
|
panic("Veriexec: Operations vector NULL\n");
|
|
}
|
|
|
|
bzero(fp, vhe->ops->hash_len);
|
|
|
|
|
|
ctx = (void *) malloc(vhe->ops->context_size, M_TEMP, M_WAITOK);
|
|
buf = (u_char *) malloc(VERIEXEC_BUFSIZE, M_TEMP, M_WAITOK);
|
|
|
|
(vhe->ops->init)(ctx); /* init the fingerprint context */
|
|
|
|
/*
|
|
* The vnode is locked. sys_execve() does it for us; We have our
|
|
* own locking in vn_open().
|
|
*/
|
|
for (offset = 0; offset < size; offset += VERIEXEC_BUFSIZE) {
|
|
len = ((size - offset) < VERIEXEC_BUFSIZE) ? (size - offset)
|
|
: VERIEXEC_BUFSIZE;
|
|
|
|
error = vn_rdwr(UIO_READ, vp, buf, len, offset,
|
|
UIO_SYSSPACE,
|
|
#ifdef __FreeBSD__
|
|
IO_NODELOCKED,
|
|
#else
|
|
0,
|
|
#endif
|
|
p->p_ucred, &resid, NULL);
|
|
|
|
if (error)
|
|
goto bad;
|
|
|
|
/* calculate fingerprint for each chunk */
|
|
(vhe->ops->update)(ctx, buf, (unsigned int) len);
|
|
}
|
|
|
|
/* finalise the fingerprint calculation */
|
|
(vhe->ops->final)(fp, ctx);
|
|
|
|
bad:
|
|
free(ctx, M_TEMP);
|
|
free(buf, M_TEMP);
|
|
|
|
return (error);
|
|
}
|
|
|
|
/* Compare two fingerprints of the same type. */
|
|
int
|
|
veriexec_fp_cmp(struct veriexec_hash_entry *vhe, u_char *digest)
|
|
{
|
|
#ifdef VERIFIED_EXEC_DEBUG
|
|
int i;
|
|
|
|
if (security_veriexec_verbose > 1) {
|
|
printf("comparing hashes...\n");
|
|
printf("vhe->fp: ");
|
|
for (i = 0; i < vhe->ops->hash_len; i++) {
|
|
printf("%x", vhe->fp[i]);
|
|
}
|
|
printf("\ndigest: ");
|
|
for (i = 0; i < vhe->ops->hash_len; i++) {
|
|
printf("%x", digest[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
|
|
return (memcmp(vhe->fp, digest, vhe->ops->hash_len));
|
|
}
|
|
|
|
/* Get the hash table for the specified device. */
|
|
struct veriexec_hashtbl *
|
|
veriexec_tblfind(dev_t device) {
|
|
struct veriexec_hashtbl *tbl;
|
|
|
|
LIST_FOREACH(tbl, &veriexec_tables, hash_list) {
|
|
if (tbl->hash_dev == device)
|
|
return (tbl);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
/* Perform a lookup on a hash table. */
|
|
struct veriexec_hash_entry *
|
|
veriexec_lookup(dev_t device, ino_t inode)
|
|
{
|
|
struct veriexec_hashtbl *tbl;
|
|
struct veriexec_hashhead *tble;
|
|
struct veriexec_hash_entry *e;
|
|
size_t indx;
|
|
|
|
tbl = veriexec_tblfind(device);
|
|
if (tbl == NULL)
|
|
return (NULL);
|
|
|
|
indx = VERIEXEC_HASH(tbl, inode);
|
|
tble = &(tbl->hash_tbl[indx & VERIEXEC_HASH_MASK(tbl)]);
|
|
|
|
LIST_FOREACH(e, tble, entries) {
|
|
if ((e != NULL) && (e->inode == inode))
|
|
return (e);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Add an entry to a hash table. If a collision is found, handle it.
|
|
* The passed entry is allocated in kernel memory.
|
|
*/
|
|
int
|
|
veriexec_hashadd(struct veriexec_hashtbl *tbl, struct veriexec_hash_entry *e)
|
|
{
|
|
struct veriexec_hashhead *vhh;
|
|
size_t indx;
|
|
|
|
if (tbl == NULL)
|
|
return (EFAULT);
|
|
|
|
indx = VERIEXEC_HASH(tbl, e->inode);
|
|
vhh = &(tbl->hash_tbl[indx]);
|
|
|
|
if (vhh == NULL)
|
|
panic("Veriexec: veriexec_hashadd: vhh is NULL.");
|
|
|
|
LIST_INSERT_HEAD(vhh, e, entries);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Verify the fingerprint of the given file. If we're called directly from
|
|
* sys_execve(), 'flag' will be VERIEXEC_DIRECT. If we're called from
|
|
* exec_script(), 'flag' will be VERIEXEC_INDIRECT. If we are called from
|
|
* vn_open(), 'flag' will be VERIEXEC_FILE.
|
|
*/
|
|
int
|
|
veriexec_verify(struct proc *p, struct vnode *vp, struct vattr *va,
|
|
const u_char *name, int flag)
|
|
{
|
|
struct veriexec_hash_entry *vhe;
|
|
u_char *digest;
|
|
int error = 0;
|
|
|
|
/* Evaluate fingerprint if needed and set the status on the vp. */
|
|
if (vp->fp_status == FINGERPRINT_NOTEVAL) {
|
|
vhe = veriexec_lookup(va->va_fsid, va->va_fileid);
|
|
if (vhe == NULL) {
|
|
vp->fp_status = FINGERPRINT_NOENTRY;
|
|
goto out;
|
|
}
|
|
|
|
veriexec_dprintf(("Veriexec: veriexec_verify: Got entry for "
|
|
"%s. (dev=%d, inode=%u)\n", name,
|
|
va->va_fsid, va->va_fileid));
|
|
|
|
/* Calculate fingerprint for the inode. */
|
|
digest = (u_char *) malloc(vhe->ops->hash_len, M_TEMP,
|
|
M_WAITOK);
|
|
error = veriexec_fp_calc(p, vp, vhe, va->va_size, digest);
|
|
|
|
if (error) {
|
|
veriexec_dprintf(("Veriexec: veriexec_verify: "
|
|
"Calculation error.\n"));
|
|
free(digest, M_TEMP);
|
|
return (error);
|
|
}
|
|
|
|
if (veriexec_fp_cmp(vhe, digest) == 0) {
|
|
if (vhe->type == VERIEXEC_INDIRECT) {
|
|
vp->fp_status = FINGERPRINT_INDIRECT;
|
|
} else {
|
|
vp->fp_status = FINGERPRINT_VALID;
|
|
}
|
|
} else {
|
|
vp->fp_status = FINGERPRINT_NOMATCH;
|
|
}
|
|
free(digest, M_TEMP);
|
|
}
|
|
|
|
out:
|
|
switch (vp->fp_status) {
|
|
case FINGERPRINT_NOTEVAL:
|
|
/* Should not happen. */
|
|
panic("Veriexec: Not-evaluated status post-evaluation. "
|
|
"Inconsistency detected. Report a bug.");
|
|
|
|
case FINGERPRINT_VALID:
|
|
/* Valid fingerprint. */
|
|
if ((securelevel >= 1) && security_veriexec_verbose)
|
|
printf("Veriexec: veriexec_verify: Fingerprint "
|
|
"matches. (file=%s, dev=%ld, inode=%lu)\n",
|
|
name, va->va_fsid, va->va_fileid);
|
|
break;
|
|
|
|
case FINGERPRINT_INDIRECT:
|
|
/* Fingerprint is okay; Make sure it's indirect execution. */
|
|
if (flag == VERIEXEC_DIRECT) {
|
|
printf("Veriexec: Attempt to execute %s "
|
|
"(dev=%ld, inode=%lu) directly by uid=%u "
|
|
"(pid=%u, ppid=%u, gppid=%u)\n", name,
|
|
va->va_fsid, va->va_fileid,
|
|
p->p_ucred->cr_uid, p->p_pid,
|
|
p->p_pptr->p_pid, p->p_pptr->p_pptr->p_pid);
|
|
|
|
error = EPERM;
|
|
}
|
|
|
|
if ((securelevel >= 1) && security_veriexec_verbose)
|
|
printf("Veriexec: veriexec_verify: Fingerprint "
|
|
"matches on indirect. (file=%s, dev=%ld, "
|
|
"inode=%lu)\n",
|
|
name, va->va_fsid, va->va_fileid);
|
|
break;
|
|
|
|
case FINGERPRINT_NOMATCH:
|
|
/* Fingerprint mismatch. Deny execution. */
|
|
printf("Veriexec: Fingerprint mismatch for %s "
|
|
"(dev=%ld, inode=%lu). Execution "
|
|
"attempt by uid=%u, pid=%u.\n", name,
|
|
va->va_fsid, va->va_fileid,
|
|
p->p_ucred->cr_uid, p->p_pid);
|
|
|
|
if (securelevel >= 2)
|
|
error = EPERM;
|
|
break;
|
|
|
|
case FINGERPRINT_NOENTRY:
|
|
/* No entry in the list. */
|
|
if (securelevel >= 1) {
|
|
if (security_veriexec_verbose)
|
|
printf("Veriexec: veriexec_verify: No "
|
|
"fingerprint for %s (dev=%ld, "
|
|
"inode=%lu)\n", name, va->va_fsid,
|
|
va->va_fileid);
|
|
|
|
/*
|
|
* We only really want to reject file opens on
|
|
* non-fingerprinted files when we are doing
|
|
* strict checking.
|
|
*/
|
|
if (((securelevel >= 2) && (flag != VERIEXEC_FILE))
|
|
|| security_veriexec_strict)
|
|
error = EPERM;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
/*
|
|
* Should never happen.
|
|
* XXX: Print vnode/process?
|
|
*/
|
|
panic("Veriexec: Invalid status post-evaluation in "
|
|
"veriexec_verify(). Report a bug. (vnode=%p, pid=%u)",
|
|
vp, p->p_pid);
|
|
}
|
|
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Veriexec remove policy code. If we have an entry for the file in our
|
|
* tables, we disallow removing if the securelevel is high or we're in
|
|
* strict mode.
|
|
*/
|
|
int
|
|
veriexec_removechk(struct proc *p, struct vnode *vp, const char *pathbuf)
|
|
{
|
|
struct veriexec_hashtbl *tbl;
|
|
struct veriexec_hash_entry *vhe = NULL;
|
|
struct vattr va;
|
|
int error;
|
|
|
|
error = VOP_GETATTR(vp, &va, p->p_ucred, p);
|
|
if (error)
|
|
return (error);
|
|
|
|
vhe = veriexec_lookup(va.va_fsid, va.va_fileid);
|
|
|
|
switch (vp->fp_status) {
|
|
case FINGERPRINT_VALID:
|
|
case FINGERPRINT_INDIRECT:
|
|
case FINGERPRINT_NOMATCH:
|
|
if ((securelevel >= 2) || security_veriexec_strict) {
|
|
printf("Veriexec: Denying unlink request for %s "
|
|
"from uid=%u: File in fingerprint tables. "
|
|
"(pid=%u, dev=%ld, inode=%lu)\n", pathbuf,
|
|
p->p_ucred->cr_uid, p->p_pid,
|
|
va.va_fsid, va.va_fileid);
|
|
|
|
error = EPERM;
|
|
} else {
|
|
if (security_veriexec_verbose) {
|
|
printf("Veriexec: veriexec_removechk: Removing"
|
|
" entry from Veriexec table. (file=%s, "
|
|
"dev=%ld, inode=%lu)\n", pathbuf,
|
|
va.va_fsid, va.va_fileid);
|
|
}
|
|
|
|
if (vhe == NULL) {
|
|
panic("Veriexec: tables inconsistent, vnode "
|
|
"has status but no fp entry found");
|
|
}
|
|
|
|
goto veriexec_rm;
|
|
}
|
|
|
|
break;
|
|
|
|
case FINGERPRINT_NOENTRY:
|
|
return(error);
|
|
break;
|
|
|
|
case FINGERPRINT_NOTEVAL:
|
|
/*
|
|
* Could be we don't have an entry for this, but we can't
|
|
* risk an unevaluated file or vnode cache flush.
|
|
*/
|
|
if (vhe == NULL) {
|
|
break;
|
|
}
|
|
|
|
if (securelevel >= 2) {
|
|
printf("Veriexec: Denying unlink request for %s from"
|
|
" uid=%u: File in fingerprint tables. (pid=%u, "
|
|
"dev=%ld, inode=%lu)\n", pathbuf,
|
|
p->p_ucred->cr_uid, p->p_pid,
|
|
va.va_fsid, va.va_fileid);
|
|
|
|
error = EPERM;
|
|
} else {
|
|
goto veriexec_rm;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
panic("Veriexec: inconsistency in verified exec state"
|
|
"data");
|
|
break;
|
|
|
|
}
|
|
|
|
return (error);
|
|
|
|
veriexec_rm:
|
|
tbl = veriexec_tblfind(va.va_fsid);
|
|
if (tbl == NULL) {
|
|
panic("Veriexec: Inconsistency: Could not get table for file"
|
|
" in lists. Report a bug.");
|
|
}
|
|
|
|
LIST_REMOVE(vhe, entries);
|
|
free(vhe->fp, M_TEMP);
|
|
free(vhe, M_TEMP);
|
|
|
|
return (error);
|
|
}
|