/*	$NetBSD: kern_verifiedexec.c,v 1.65 2006/07/26 16:34:07 elad Exp $	*/

/*-
 * Copyright 2005 Elad Efrat <elad@NetBSD.org>
 * 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.65 2006/07/26 16:34:07 elad Exp $");

#include "opt_veriexec.h"

#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/sysctl.h>
#include <sys/inttypes.h>
#define VERIEXEC_NEED_NODE
#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>
#include <uvm/uvm_extern.h>
#include <sys/fileassoc.h>
#include <sys/kauth.h>

int veriexec_verbose;
int veriexec_strict;

char *veriexec_fp_names;
size_t veriexec_name_max;

const struct sysctlnode *veriexec_count_node;

int veriexec_hook;

/* Veriexecs table of hash types and their associated information. */
LIST_HEAD(veriexec_ops_head, veriexec_fp_ops) veriexec_ops_list;

/*
 * Add fingerprint names to the global list.
 */
static void
veriexec_add_fp_name(char *name)
{
	char *newp;
	unsigned int new_max;

	if (name == NULL)
		return;

	if (veriexec_fp_names == NULL) {
		veriexec_name_max = (VERIEXEC_TYPE_MAXLEN + 1) * 6;
		veriexec_fp_names = malloc(veriexec_name_max, M_TEMP, M_WAITOK);
		memset(veriexec_fp_names, 0, veriexec_name_max);
	}

	if ((strlen(veriexec_fp_names) + VERIEXEC_TYPE_MAXLEN + 1) >=
	    veriexec_name_max) {
		new_max = veriexec_name_max + 4 * (VERIEXEC_TYPE_MAXLEN + 1);
		newp = realloc(veriexec_fp_names, new_max, M_TEMP, M_WAITOK);
		veriexec_fp_names = newp;
		veriexec_name_max = new_max;
	}

	strlcat(veriexec_fp_names, name, veriexec_name_max);
	strlcat(veriexec_fp_names, " ", veriexec_name_max);
}

/*
 * Add ops to the fignerprint ops vector list.
 */
int veriexec_add_fp_ops(struct veriexec_fp_ops *ops)
{
	if (ops == NULL)
		return (EFAULT);

	if ((ops->init == NULL) ||
	    (ops->update == NULL) ||
	    (ops->final == NULL))
		return (EFAULT);

	ops->type[sizeof(ops->type) - 1] = '\0';

#ifdef DIAGNOSTIC
	if (veriexec_find_ops(ops->type) != NULL)
		return (EEXIST);
#endif /* DIAGNOSTIC */

	LIST_INSERT_HEAD(&veriexec_ops_list, ops, entries);
	veriexec_add_fp_name(ops->type);

	return (0);
}

/*
 * Initialise the internal "default" fingerprint ops vector list.
 */
void
veriexec_init_fp_ops(void)
{
	struct veriexec_fp_ops *ops;

	/* Register a fileassoc for Veriexec. */
	veriexec_hook = fileassoc_register("veriexec", veriexec_clear);
#ifdef DIAGNOSTIC
	if (veriexec_hook == FILEASSOC_INVAL)
		panic("Veriexec: Can't register meta-hook");
#endif /* DIAGNOSTIC */

	LIST_INIT(&veriexec_ops_list);
	veriexec_fp_names = NULL;
	veriexec_name_max = 0;

#ifdef VERIFIED_EXEC_FP_RMD160
	ops = (struct veriexec_fp_ops *) malloc(sizeof(*ops), M_TEMP, M_WAITOK);
	VERIEXEC_OPINIT(ops, "RMD160", RMD160_DIGEST_LENGTH,
			sizeof(RMD160_CTX), RMD160Init, RMD160Update,
			RMD160Final);
	(void) veriexec_add_fp_ops(ops);
#endif /* VERIFIED_EXEC_FP_RMD160 */

#ifdef VERIFIED_EXEC_FP_SHA256
	ops = (struct veriexec_fp_ops *) malloc(sizeof(*ops), M_TEMP, M_WAITOK);
	VERIEXEC_OPINIT(ops, "SHA256", SHA256_DIGEST_LENGTH,
			sizeof(SHA256_CTX), SHA256_Init, SHA256_Update,
			SHA256_Final);
	(void) veriexec_add_fp_ops(ops);
#endif /* VERIFIED_EXEC_FP_SHA256 */

#ifdef VERIFIED_EXEC_FP_SHA384
	ops = (struct veriexec_fp_ops *) malloc(sizeof(*ops), M_TEMP, M_WAITOK);
	VERIEXEC_OPINIT(ops, "SHA384", SHA384_DIGEST_LENGTH,
			sizeof(SHA384_CTX), SHA384_Init, SHA384_Update,
			SHA384_Final);
	(void) veriexec_add_fp_ops(ops);
#endif /* VERIFIED_EXEC_FP_SHA384 */

#ifdef VERIFIED_EXEC_FP_SHA512
	ops = (struct veriexec_fp_ops *) malloc(sizeof(*ops), M_TEMP, M_WAITOK);
	VERIEXEC_OPINIT(ops, "SHA512", SHA512_DIGEST_LENGTH,
			sizeof(SHA512_CTX), SHA512_Init, SHA512_Update,
			SHA512_Final);
	(void) veriexec_add_fp_ops(ops);
#endif /* VERIFIED_EXEC_FP_SHA512 */

#ifdef VERIFIED_EXEC_FP_SHA1
	ops = (struct veriexec_fp_ops *) malloc(sizeof(*ops), M_TEMP, M_WAITOK);
	VERIEXEC_OPINIT(ops, "SHA1", SHA1_DIGEST_LENGTH,
			sizeof(SHA1_CTX), SHA1Init, SHA1Update,
			SHA1Final);
	(void) veriexec_add_fp_ops(ops);
#endif /* VERIFIED_EXEC_FP_SHA1 */

#ifdef VERIFIED_EXEC_FP_MD5
	ops = (struct veriexec_fp_ops *) malloc(sizeof(*ops), M_TEMP, M_WAITOK);
	VERIEXEC_OPINIT(ops, "MD5", MD5_DIGEST_LENGTH, sizeof(MD5_CTX),
			MD5Init, MD5Update, MD5Final);
	(void) veriexec_add_fp_ops(ops);
#endif /* VERIFIED_EXEC_FP_MD5 */

}

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 lwp *l, struct vnode *vp,
    struct veriexec_file_entry *vfe, u_char *fp)
{
	struct vattr va;
	void *ctx, *page_ctx;
	u_char *buf, *page_fp;
	off_t offset, len;
	size_t resid, npages;
	int error, do_perpage, pagen;

	if (vfe->ops == NULL)
		panic("Veriexec: Operations vector is NULL");

	error = VOP_GETATTR(vp, &va, l->l_cred, l);
	if (error)
		return (error);

#if 0 /* XXX - for now */
	if ((vfe->type & VERIEXEC_UNTRUSTED) &&
	    (vfe->page_fp_status == PAGE_FP_NONE))
		do_perpage = 1;
	else
#endif
		do_perpage = 0;

	ctx = (void *) malloc(vfe->ops->context_size, M_TEMP, M_WAITOK);
	buf = (u_char *) malloc(PAGE_SIZE, M_TEMP, M_WAITOK);

	page_ctx = NULL;
	page_fp = NULL;
	npages = 0;
	if (do_perpage) {
		npages = (va.va_size >> PAGE_SHIFT) + 1;
		page_fp = (u_char *) malloc(vfe->ops->hash_len * npages,
						 M_TEMP, M_WAITOK|M_ZERO);
		vfe->page_fp = page_fp;
		page_ctx = (void *) malloc(vfe->ops->context_size, M_TEMP,
					   M_WAITOK);
	}

	(vfe->ops->init)(ctx);

	len = 0;
	error = 0;
	pagen = 0;
	for (offset = 0; offset < va.va_size; offset += PAGE_SIZE) {
		len = ((va.va_size - offset) < PAGE_SIZE) ?
		    (va.va_size - offset) : PAGE_SIZE;

		error = vn_rdwr(UIO_READ, vp, buf, len, offset,
				UIO_SYSSPACE,
#ifdef __FreeBSD__
				IO_NODELOCKED,
#else
				0,
#endif
				l->l_cred, &resid, NULL);

		if (error) {
			if (do_perpage) {
				free(vfe->page_fp, M_TEMP);
				vfe->page_fp = NULL;
			}

			goto bad;
		}

		(vfe->ops->update)(ctx, buf, (unsigned int) len);

		if (do_perpage) {
			(vfe->ops->init)(page_ctx);
			(vfe->ops->update)(page_ctx, buf, (unsigned int)len);
			(vfe->ops->final)(page_fp, page_ctx);

			if (veriexec_verbose >= 2) {
				int i;

				printf("hash for page %d: ", pagen);
				for (i = 0; i < vfe->ops->hash_len; i++)
					printf("%02x", page_fp[i]);
				printf("\n");
			}

			page_fp += vfe->ops->hash_len;
			pagen++;
		}

		if (len != PAGE_SIZE)
			break;
	}

	(vfe->ops->final)(fp, ctx);

	if (do_perpage) {
		vfe->last_page_size = len;
		vfe->page_fp_status = PAGE_FP_READY;
		vfe->npages = npages;
	}

bad:
	if (do_perpage)
		free(page_ctx, M_TEMP);
	free(ctx, M_TEMP);
	free(buf, M_TEMP);

	return (error);
}

/* Compare two fingerprints of the same type. */
int
veriexec_fp_cmp(struct veriexec_fp_ops *ops, u_char *fp1, u_char *fp2)
{
	if (veriexec_verbose >= 2) {
		int i;

		printf("comparing hashes...\n");
		printf("fp1: ");
		for (i = 0; i < ops->hash_len; i++) {
			printf("%02x", fp1[i]);
		}
		printf("\nfp2: ");
		for (i = 0; i < ops->hash_len; i++) {
			printf("%02x", fp2[i]);
		}
		printf("\n");
	}

	return (memcmp(fp1, fp2, ops->hash_len));
}

struct veriexec_table_entry *
veriexec_tblfind(struct vnode *vp)
{
	return (fileassoc_tabledata_lookup(vp->v_mount, veriexec_hook));
}

struct veriexec_file_entry *
veriexec_lookup(struct vnode *vp)
{
	return (fileassoc_lookup(vp, veriexec_hook));
}

/*
 * 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 vnode *vp, struct veriexec_file_entry *vfe)
{
	struct veriexec_table_entry *vte;
	int error;

	error = fileassoc_add(vp, veriexec_hook, vfe);
	if (error)
		return (error);

	vte = veriexec_tblfind(vp);
#ifdef DIAGNOSTIC
	if (vte == NULL)
		panic("Fileassoc: Inconsistency with table data");
#endif /* DIAGNOSTIC */

	vte->vte_count++;

	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 lwp *l, struct vnode *vp, const u_char *name, int flag,
    struct veriexec_file_entry **ret)
{
	struct veriexec_file_entry *vfe;
	u_char *digest;
	int error;

	if (vp->v_type != VREG)
		return (0);

	/* Lookup veriexec table entry, save pointer if requested. */
	vfe = veriexec_lookup(vp);
	if (ret != NULL)
		*ret = vfe;
	if (vfe == NULL)
		goto out;

	/* Evaluate fingerprint if needed. */
	error = 0;
	digest = NULL;
	if ((vfe->status == FINGERPRINT_NOTEVAL) ||
	    (vfe->type & VERIEXEC_UNTRUSTED)) {
		/* Calculate fingerprint for on-disk file. */
		digest = (u_char *) malloc(vfe->ops->hash_len, M_TEMP,
					   M_WAITOK);
		error = veriexec_fp_calc(l, vp, vfe, digest);
		if (error) {
			veriexec_report("Fingerprint calculation error.",
			    name, NULL, REPORT_ALWAYS);
			free(digest, M_TEMP);
			return (error);
		}

		/* Compare fingerprint with loaded data. */
		if (veriexec_fp_cmp(vfe->ops, vfe->fp, digest) == 0) {
			vfe->status = FINGERPRINT_VALID;
		} else {
			vfe->status = FINGERPRINT_NOMATCH;
		}

		free(digest, M_TEMP);
	}

	if (!(vfe->type & flag)) {
		veriexec_report("Incorrect access type.", name, l,
		    REPORT_ALWAYS|REPORT_ALARM);

		/* IPS mode: Enforce access type. */
		if (veriexec_strict >= VERIEXEC_IPS)
			return (EPERM);
	}

 out:
	/* No entry in the veriexec tables. */
	if (vfe == NULL) {
		veriexec_report("veriexec_verify: No entry.", name,
		    l, REPORT_VERBOSE);

		/*
		 * Lockdown mode: Deny access to non-monitored files.
		 * IPS mode: Deny execution of non-monitored files.
		 */
		if ((veriexec_strict >= VERIEXEC_LOCKDOWN) ||
		    ((veriexec_strict >= VERIEXEC_IPS) &&
		     (flag != VERIEXEC_FILE)))
			return (EPERM);

		return (0);
	}

        switch (vfe->status) {
	case FINGERPRINT_NOTEVAL:
		/* Should not happen. */
		veriexec_report("veriexec_verify: Not-evaluated status "
		    "post evaluation; inconsistency detected.", name,
		    NULL, REPORT_ALWAYS|REPORT_PANIC);

	case FINGERPRINT_VALID:
		/* Valid fingerprint. */
		veriexec_report("veriexec_verify: Match.", name, NULL,
		    REPORT_VERBOSE);

		break;

	case FINGERPRINT_NOMATCH:
		/* Fingerprint mismatch. */
		veriexec_report("veriexec_verify: Mismatch.", name,
		    NULL, REPORT_ALWAYS|REPORT_ALARM);

		/* IDS mode: Deny access on fingerprint mismatch. */
		if (veriexec_strict >= VERIEXEC_IDS)
			error = EPERM;

		break;

	default:
		/* Should never happen. */
		veriexec_report("veriexec_verify: Invalid status "
		    "post evaluation.", name, NULL, REPORT_ALWAYS|REPORT_PANIC);
        }

	return (error);
}

/*
 * Evaluate per-page fingerprints.
 */
int
veriexec_page_verify(struct veriexec_file_entry *vfe, struct vm_page *pg,
    size_t idx, struct lwp *l)
{
	void *ctx;
	u_char *fp;
	u_char *page_fp;
	int error;
	vaddr_t kva;

	if (vfe->page_fp_status == PAGE_FP_NONE)
		return (0);

	if (vfe->page_fp_status == PAGE_FP_FAIL)
		return (EPERM);

	if (idx >= vfe->npages)
		return (0);

	ctx = malloc(vfe->ops->context_size, M_TEMP, M_WAITOK);
	fp = malloc(vfe->ops->hash_len, M_TEMP, M_WAITOK);
	kva = uvm_km_alloc(kernel_map, PAGE_SIZE, 0, UVM_KMF_VAONLY | UVM_KMF_WAITVA);
	pmap_kenter_pa(kva, VM_PAGE_TO_PHYS(pg), VM_PROT_READ);

	page_fp = (u_char *) vfe->page_fp + (vfe->ops->hash_len * idx);
	(vfe->ops->init)(ctx);
	(vfe->ops->update)(ctx, (void *) kva,
			   ((vfe->npages - 1) == idx) ? vfe->last_page_size
						      : PAGE_SIZE);
	(vfe->ops->final)(fp, ctx);

	pmap_kremove(kva, PAGE_SIZE);
	uvm_km_free(kernel_map, kva, PAGE_SIZE, UVM_KMF_VAONLY);

	error = veriexec_fp_cmp(vfe->ops, page_fp, fp);
	if (error) {
		const char *msg;

		if (veriexec_strict > VERIEXEC_LEARNING) {
			msg = "Pages modified: Killing process.";
		} else {
			msg = "Pages modified.";
			error = 0;
		}

		veriexec_report(msg, "[page_in]", l, REPORT_ALWAYS|REPORT_ALARM);

		if (error) {
			ksiginfo_t ksi;

			KSI_INIT(&ksi);
			ksi.ksi_signo = SIGKILL;
			ksi.ksi_code = SI_NOINFO;
			ksi.ksi_pid = l->l_proc->p_pid;
			ksi.ksi_uid = 0;

			kpsignal(l->l_proc, &ksi, NULL);
		}
	}

	free(ctx, M_TEMP);
	free(fp, M_TEMP);

	return (error);
}

/*
 * Veriexec remove policy code.
 */
int
veriexec_removechk(struct vnode *vp, const char *pathbuf, struct lwp *l)
{
	struct veriexec_file_entry *vfe;
	struct veriexec_table_entry *vte;

	vfe = veriexec_lookup(vp);
	if (vfe == NULL) {
		/* Lockdown mode: Deny access to non-monitored files. */
		if (veriexec_strict >= VERIEXEC_LOCKDOWN)
			return (EPERM);

		return (0);
	}

	veriexec_report("Remove request.", pathbuf, l, REPORT_ALWAYS|REPORT_ALARM);

	/* IDS mode: Deny removal of monitored files. */
	if (veriexec_strict >= VERIEXEC_IDS)
		return (EPERM);

	fileassoc_clear(vp, veriexec_hook);

	vte = veriexec_tblfind(vp);
#ifdef DIAGNOSTIC
	if (vte == NULL)
		panic("Fileassoc: Inconsistency during entry removel");
#endif /* DIAGNOSTIC */

	vte->vte_count--;

	return (0);
}

/*
 * Veriexe rename policy.
 */
int
veriexec_renamechk(struct vnode *fromvp, const char *fromname,
    struct vnode *tovp, const char *toname, struct lwp *l)
{
	struct veriexec_file_entry *vfe, *tvfe;

	if (veriexec_strict >= VERIEXEC_LOCKDOWN) {
		log(LOG_ALERT, "Veriexec: Preventing rename of `%s' to "
		    "`%s', uid=%u, pid=%u: Lockdown mode.\n", fromname, toname,
		    kauth_cred_geteuid(l->l_cred), l->l_proc->p_pid);
		return (EPERM);
	}

	vfe = veriexec_lookup(fromvp);
	tvfe = NULL;
	if (tovp != NULL)
		tvfe = veriexec_lookup(tovp);

	if ((vfe != NULL) || (tvfe != NULL)) {
		if (veriexec_strict >= VERIEXEC_IPS) {
			log(LOG_ALERT, "Veriexec: Preventing rename of `%s' "
			    "to `%s', uid=%u, pid=%u: IPS mode, file "
			    "monitored.\n", fromname, toname,
			    kauth_cred_geteuid(l->l_cred),
			    l->l_proc->p_pid);
			return (EPERM);
		}

		log(LOG_NOTICE, "Veriexec: Monitored file `%s' renamed to "
		    "`%s', uid=%u, pid=%u.\n", fromname, toname,
		    kauth_cred_geteuid(l->l_cred), l->l_proc->p_pid);
	}

	return (0);
}

/*
 * Routine for maintaining mostly consistent message formats in Verified
 * Exec.
 */
void
veriexec_report(const u_char *msg, const u_char *filename, struct lwp *l, int f)
{
	if (msg == NULL || filename == NULL)
		return;

	if (((f & REPORT_LOGMASK) >> 1) <= veriexec_verbose) {
		if (!(f & REPORT_ALARM) || (l == NULL))
			log(LOG_NOTICE, "Veriexec: %s [%s]\n", msg,
			    filename);
		else
			log(LOG_ALERT, "Veriexec: %s [%s, pid=%u, uid=%u, "
			    "gid=%u]\n", msg, filename, l->l_proc->p_pid,
			    kauth_cred_getuid(l->l_cred),
			    kauth_cred_getgid(l->l_cred));
	}

	if (f & REPORT_PANIC)
		panic("Veriexec: Unrecoverable error.");
}

void
veriexec_clear(void *data, int file_specific)
{
	if (file_specific) {
		struct veriexec_file_entry *vfe = data;

		if (vfe != NULL) {
			if (vfe->fp != NULL)
				free(vfe->fp, M_TEMP);
			if (vfe->page_fp != NULL)
				free(vfe->page_fp, M_TEMP);
			free(vfe, M_TEMP);
		}
	} else {
		struct veriexec_table_entry *vte = data;

		if (vte != NULL)
			free(vte, M_TEMP);
	}
}
