/* $NetBSD: kern_verifiedexec.c,v 1.45 2005/10/12 14:26:47 elad Exp $ */ /*- * Copyright 2005 Elad Efrat * Copyright 2005 Brett Lymn * * 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 __KERNEL_RCSID(0, "$NetBSD: kern_verifiedexec.c,v 1.45 2005/10/12 14:26:47 elad Exp $"); #include "opt_verified_exec.h" #include #include #include #include #include #include #include #include #include #include #define VERIEXEC_NEED_NODE #include #if defined(__FreeBSD__) # include # include # include #else # include #endif #include #include #include #include int veriexec_verbose; int veriexec_strict; char *veriexec_fp_names; size_t veriexec_name_max; const struct sysctlnode *veriexec_count_node; /* 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; 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 proc *p, struct vnode *vp, struct veriexec_hash_entry *vhe, uint64_t size, u_char *fp) { void *ctx, *page_ctx; u_char *buf, *page_fp; off_t offset, len; size_t resid, npages; int error, do_perpage, pagen; if (vhe->ops == NULL) { panic("veriexec: Operations vector is NULL"); } #if 0 /* XXX - for now */ if ((vhe->type & VERIEXEC_UNTRUSTED) && (vhe->page_fp_status == PAGE_FP_NONE)) do_perpage = 1; else #endif do_perpage = 0; ctx = (void *) malloc(vhe->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 = (size >> PAGE_SHIFT) + 1; page_fp = (u_char *) malloc(vhe->ops->hash_len * npages, M_TEMP, M_WAITOK|M_ZERO); vhe->page_fp = page_fp; page_ctx = (void *) malloc(vhe->ops->context_size, M_TEMP, M_WAITOK); } (vhe->ops->init)(ctx); len = 0; error = 0; pagen = 0; for (offset = 0; offset < size; offset += PAGE_SIZE) { len = ((size - offset) < PAGE_SIZE) ? (size - offset) : PAGE_SIZE; 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) { if (do_perpage) { free(vhe->page_fp, M_TEMP); vhe->page_fp = NULL; } goto bad; } (vhe->ops->update)(ctx, buf, (unsigned int) len); if (do_perpage) { (vhe->ops->init)(page_ctx); (vhe->ops->update)(page_ctx, buf, (unsigned int)len); (vhe->ops->final)(page_fp, page_ctx); if (veriexec_verbose >= 2) { int i; printf("hash for page %d: ", pagen); for (i = 0; i < vhe->ops->hash_len; i++) printf("%02x", page_fp[i]); printf("\n"); } page_fp += vhe->ops->hash_len; pagen++; } if (len != PAGE_SIZE) break; } (vhe->ops->final)(fp, ctx); if (do_perpage) { vhe->last_page_size = len; vhe->page_fp_status = PAGE_FP_READY; vhe->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)); } /* 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); tbl->hash_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 proc *p, struct vnode *vp, struct vattr *va, const u_char *name, int flag, struct veriexec_hash_entry **ret) { struct veriexec_hash_entry *vhe; u_char *digest; int error; if (vp->v_type != VREG) return (0); /* Lookup veriexec table entry, save pointer if requested. */ /* XXX: va_fsid is long, dev_t is uint32_t. */ vhe = veriexec_lookup((dev_t)va->va_fsid, (ino_t)va->va_fileid); if (ret != NULL) *ret = vhe; if (vhe == NULL) goto out; /* Evaluate fingerprint if needed. */ error = 0; digest = NULL; if ((vhe->status == FINGERPRINT_NOTEVAL) || (vhe->type & VERIEXEC_UNTRUSTED)) { /* Calculate fingerprint for on-disk file. */ 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_report("Fingerprint calculation error.", name, va, NULL, REPORT_NOVERBOSE, REPORT_NOALARM, REPORT_NOPANIC); free(digest, M_TEMP); return (error); } /* Compare fingerprint with loaded data. */ if (veriexec_fp_cmp(vhe->ops, vhe->fp, digest) == 0) { vhe->status = FINGERPRINT_VALID; } else { vhe->status = FINGERPRINT_NOMATCH; } free(digest, M_TEMP); } if (!(vhe->type & flag)) { veriexec_report("Incorrect access type.", name, va, p, REPORT_NOVERBOSE, REPORT_ALARM, REPORT_NOPANIC); /* IPS mode: Enforce access type. */ if (veriexec_strict >= 2) return (EPERM); } out: /* No entry in the veriexec tables. */ if (vhe == NULL) { veriexec_report("veriexec_verify: No entry.", name, va, p, REPORT_VERBOSE, REPORT_NOALARM, REPORT_NOPANIC); /* Lockdown mode: Deny access to non-monitored files. */ if (veriexec_strict >= 3) return (EPERM); return (0); } switch (vhe->status) { case FINGERPRINT_NOTEVAL: /* Should not happen. */ veriexec_report("veriexec_verify: Not-evaluated status " "post evaluation; inconsistency detected.", name, va, NULL, REPORT_NOVERBOSE, REPORT_NOALARM, REPORT_PANIC); case FINGERPRINT_VALID: /* Valid fingerprint. */ veriexec_report("veriexec_verify: Match.", name, va, NULL, REPORT_VERBOSE, REPORT_NOALARM, REPORT_NOPANIC); break; case FINGERPRINT_NOMATCH: /* Fingerprint mismatch. */ veriexec_report("veriexec_verify: Mismatch.", name, va, NULL, REPORT_NOVERBOSE, REPORT_ALARM, REPORT_NOPANIC); /* IDS mode: Deny access on fingerprint mismatch. */ if (veriexec_strict >= 1) error = EPERM; break; default: /* * Should never happen. * XXX: Print vnode/process? */ veriexec_report("veriexec_verify: Invalid status " "post evaluation.", name, va, NULL, REPORT_NOVERBOSE, REPORT_NOALARM, REPORT_PANIC); } return (error); } /* * Evaluate per-page fingerprints. */ int veriexec_page_verify(struct veriexec_hash_entry *vhe, struct vattr *va, struct vm_page *pg, size_t idx) { void *ctx; u_char *fp; u_char *page_fp; int error; vaddr_t kva; if (vhe->page_fp_status == PAGE_FP_NONE) return (0); if (vhe->page_fp_status == PAGE_FP_FAIL) return (EPERM); if (idx >= vhe->npages) return (0); ctx = (void *) malloc(vhe->ops->context_size, M_TEMP, M_WAITOK); fp = (u_char *) malloc(vhe->ops->hash_len, M_TEMP, M_WAITOK); kva = 0; error = uvm_map(kernel_map, &kva, PAGE_SIZE, NULL, UVM_UNKNOWN_OFFSET, 0, UVM_MAPFLAG(UVM_PROT_READ, UVM_PROT_READ, UVM_INH_NONE, UVM_ADV_NORMAL, UVM_FLAG_NOMERGE)); if (error) goto bad; pmap_kenter_pa(kva, pg->phys_addr, VM_PROT_READ); page_fp = (u_char *) vhe->page_fp + (vhe->ops->hash_len * idx); (vhe->ops->init)(ctx); (vhe->ops->update)(ctx, (void *) kva, ((vhe->npages - 1) == idx) ? vhe->last_page_size : PAGE_SIZE); (vhe->ops->final)(fp, ctx); pmap_kremove(kva, PAGE_SIZE); uvm_unmap(kernel_map, kva, kva + PAGE_SIZE); error = veriexec_fp_cmp(vhe->ops, page_fp, fp); if (error) { struct proc *p; const char *msg; if (veriexec_strict > 0) { msg = "Pages modified: Killing process."; } else { msg = "Pages modified."; error = 0; } p = curlwp->l_proc; veriexec_report(msg, "[page_in]", va, p, REPORT_NOVERBOSE, REPORT_ALARM, REPORT_NOPANIC); if (error) { ksiginfo_t ksi; KSI_INIT(&ksi); ksi.ksi_signo = SIGKILL; ksi.ksi_code = SI_NOINFO; ksi.ksi_pid = p->p_pid; ksi.ksi_uid = 0; kpsignal(p, &ksi, NULL); } } bad: free(ctx, M_TEMP); free(fp, M_TEMP); return (error); } /* * Veriexec remove policy code. */ int veriexec_removechk(struct proc *p, struct vnode *vp, const char *pathbuf) { struct veriexec_hashtbl *tbl; struct veriexec_hash_entry *vhe; 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); if (vhe == NULL) { /* Lockdown mode: Deny access to non-monitored files. */ if (veriexec_strict >= 3) return (EPERM); return (0); } veriexec_report("Remove request.", pathbuf, &va, p, REPORT_NOVERBOSE, REPORT_ALARM, REPORT_NOPANIC); /* IPS mode: Deny removal of monitored files. */ if (veriexec_strict >= 2) return (EPERM); tbl = veriexec_tblfind(va.va_fsid); if (tbl == NULL) { veriexec_report("veriexec_removechk: Inconsistency " "detected: Could not get table for file in lists.", pathbuf, &va, NULL, REPORT_NOVERBOSE, REPORT_NOALARM, REPORT_PANIC); } LIST_REMOVE(vhe, entries); if (vhe->fp != NULL) free(vhe->fp, M_TEMP); if (vhe->page_fp != NULL) free(vhe->page_fp, M_TEMP); free(vhe, M_TEMP); tbl->hash_count--; return (error); } /* * Veriexe rename policy. */ int veriexec_renamechk(struct vnode *vp, const char *from, const char *to) { struct proc *p; struct veriexec_hash_entry *vhe; struct vattr va; int error; p = curlwp->l_proc; error = VOP_GETATTR(vp, &va, p->p_ucred, p); if (error) return (error); if (veriexec_strict >= 3) { printf("Veriexec: veriexec_renamechk: Preventing rename " "of \"%s\" [%ld:%llu] to \"%s\", uid=%u, pid=%u: " "Lockdown mode.\n", from, va.va_fsid, (unsigned long long)va.va_fileid, to, p->p_ucred->cr_uid, p->p_pid); return (EPERM); } /* XXX: dev_t is 32bit, long can be 64bit. */ vhe = veriexec_lookup((dev_t)va.va_fsid, (ino_t)va.va_fileid); if (vhe != NULL) { if (veriexec_strict >= 2) { printf("Veriexec: veriexec_renamechk: Preventing " "rename of \"%s\" [%ld:%llu] to \"%s\", " "uid=%u, pid=%u: IPS mode, file " "monitored.\n", from, va.va_fsid, (unsigned long long)va.va_fileid, to, p->p_ucred->cr_uid, p->p_pid); return (EPERM); } printf("Veriexec: veriexec_rename: Monitored file \"%s\" " "[%ld:%llu] renamed to \"%s\", uid=%u, pid=%u.\n", from, va.va_fsid, (unsigned long long)va.va_fileid, to, p->p_ucred->cr_uid, p->p_pid); } return (0); } /* * Routine for maintaining mostly consistent message formats in Verified * Exec. * * 'verbose_only' - if 1, the message will be printed only if veriexec is * in verbose mode. * 'alarm' - if 1, the message is considered an alarm and will be printed * at all times along with pid and user credentials. * 'die' - if 1, the system will call panic() instead of printf(). */ void veriexec_report(const u_char *msg, const u_char *filename, struct vattr *va, struct proc *p, int verbose, int alarm, int die) { void (*f)(const char *, ...); if (msg == NULL || filename == NULL || va == NULL) return; if (die) f = panic; else f = (void (*)(const char *, ...)) printf; if (!verbose || (verbose <= veriexec_verbose)) { if (!alarm || p == NULL) f("veriexec: %s [%s, %ld:%" PRIu64 "%s", msg, filename, va->va_fsid, va->va_fileid, die ? "]" : "]\n"); else f("veriexec: %s [%s, %ld:%" PRIu64 ", pid=%u, uid=%u, " "gid=%u%s", msg, filename, va->va_fsid, va->va_fileid, p->p_pid, p->p_cred->p_ruid, p->p_cred->p_rgid, die ? "]" : "]\n"); } }