Module Name: src Committed By: maxv Date: Sun Dec 2 21:00:13 UTC 2018
Modified Files: src/share/mk: bsd.sys.mk src/sys/arch/amd64/conf: GENERIC src/sys/arch/amd64/include: param.h src/sys/conf: files ssp.mk src/sys/kern: files.kern subr_pool.c sys_syscall.c src/sys/sys: systm.h src/sys/uvm: uvm_km.c Added Files: src/sys/arch/amd64/include: kleak.h src/sys/kern: subr_kleak.c src/usr.sbin/kleak: Makefile kleak.c Log Message: Introduce KLEAK, a new feature that can detect kernel information leaks. It works by tainting memory sources with marker values, letting the data travel through the kernel, and scanning the kernel<->user frontier for these marker values. Combined with compiler instrumentation and rotation of the markers, it is able to yield relevant results with little effort. We taint the pools and the stack, and scan copyout/copyoutstr. KLEAK is supported on amd64 only for now, but it is not complicated to add more architectures (just a matter of having the address of .text, and a stack unwinder). A userland tool is provided, that allows to execute a command in rounds and monitor the leaks generated all the while. KLEAK already detected directly 12 kernel info leaks, and prompted changes that in total fixed 25+ leaks. Based on an idea developed jointly with Thomas Barabosch (of Fraunhofer FKIE). To generate a diff of this commit: cvs rdiff -u -r1.286 -r1.287 src/share/mk/bsd.sys.mk cvs rdiff -u -r1.508 -r1.509 src/sys/arch/amd64/conf/GENERIC cvs rdiff -u -r0 -r1.1 src/sys/arch/amd64/include/kleak.h cvs rdiff -u -r1.26 -r1.27 src/sys/arch/amd64/include/param.h cvs rdiff -u -r1.1216 -r1.1217 src/sys/conf/files cvs rdiff -u -r1.2 -r1.3 src/sys/conf/ssp.mk cvs rdiff -u -r1.25 -r1.26 src/sys/kern/files.kern cvs rdiff -u -r0 -r1.1 src/sys/kern/subr_kleak.c cvs rdiff -u -r1.227 -r1.228 src/sys/kern/subr_pool.c cvs rdiff -u -r1.11 -r1.12 src/sys/kern/sys_syscall.c cvs rdiff -u -r1.279 -r1.280 src/sys/sys/systm.h cvs rdiff -u -r1.145 -r1.146 src/sys/uvm/uvm_km.c cvs rdiff -u -r0 -r1.1 src/usr.sbin/kleak/Makefile src/usr.sbin/kleak/kleak.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/share/mk/bsd.sys.mk diff -u src/share/mk/bsd.sys.mk:1.286 src/share/mk/bsd.sys.mk:1.287 --- src/share/mk/bsd.sys.mk:1.286 Fri Aug 3 02:34:31 2018 +++ src/share/mk/bsd.sys.mk Sun Dec 2 21:00:13 2018 @@ -1,4 +1,4 @@ -# $NetBSD: bsd.sys.mk,v 1.286 2018/08/03 02:34:31 kamil Exp $ +# $NetBSD: bsd.sys.mk,v 1.287 2018/12/02 21:00:13 maxv Exp $ # # Build definitions used for NetBSD source tree builds. @@ -232,6 +232,14 @@ CPUFLAGS+= -Wa,--fatal-warnings CFLAGS+= ${CPUFLAGS} AFLAGS+= ${CPUFLAGS} +.if ${KLEAK:U0} > 0 +KLEAKFLAGS= -fsanitize-coverage=trace-pc +.for f in subr_kleak.c +KLEAKFLAGS.${f}= # empty +.endfor +CFLAGS+= ${KLEAKFLAGS.${.IMPSRC:T}:U${KLEAKFLAGS}} +.endif + .if !defined(NOPIE) && (!defined(LDSTATIC) || ${LDSTATIC} != "-static") # Position Independent Executable flags PIE_CFLAGS?= -fPIE Index: src/sys/arch/amd64/conf/GENERIC diff -u src/sys/arch/amd64/conf/GENERIC:1.508 src/sys/arch/amd64/conf/GENERIC:1.509 --- src/sys/arch/amd64/conf/GENERIC:1.508 Sat Nov 24 18:23:29 2018 +++ src/sys/arch/amd64/conf/GENERIC Sun Dec 2 21:00:13 2018 @@ -1,4 +1,4 @@ -# $NetBSD: GENERIC,v 1.508 2018/11/24 18:23:29 bouyer Exp $ +# $NetBSD: GENERIC,v 1.509 2018/12/02 21:00:13 maxv Exp $ # # GENERIC machine description file # @@ -22,7 +22,7 @@ include "arch/amd64/conf/std.amd64" options INCLUDE_CONFIG_FILE # embed config file in kernel binary -#ident "GENERIC-$Revision: 1.508 $" +#ident "GENERIC-$Revision: 1.509 $" maxusers 64 # estimated number of users @@ -125,6 +125,10 @@ options KDTRACE_HOOKS # kernel DTrace h #options KASAN #no options SVS +# Kernel Info Leak Detector. +#makeoptions KLEAK=1 +#options KLEAK + # Compatibility options # x86_64 never shipped with a.out binaries; the two options below are # only relevant to 32-bit i386 binaries Index: src/sys/arch/amd64/include/param.h diff -u src/sys/arch/amd64/include/param.h:1.26 src/sys/arch/amd64/include/param.h:1.27 --- src/sys/arch/amd64/include/param.h:1.26 Wed Aug 22 12:07:43 2018 +++ src/sys/arch/amd64/include/param.h Sun Dec 2 21:00:13 2018 @@ -1,4 +1,4 @@ -/* $NetBSD: param.h,v 1.26 2018/08/22 12:07:43 maxv Exp $ */ +/* $NetBSD: param.h,v 1.27 2018/12/02 21:00:13 maxv Exp $ */ #ifdef __x86_64__ @@ -11,6 +11,7 @@ #include <machine/cpu.h> #if defined(_KERNEL_OPT) #include "opt_kasan.h" +#include "opt_kleak.h" #endif #endif @@ -61,7 +62,7 @@ #define SSIZE 1 /* initial stack size/NBPG */ #define SINCR 1 /* increment of stack/NBPG */ -#ifdef KASAN +#if defined(KASAN) || defined(KLEAK) #define UPAGES 8 #elif defined(DIAGNOSTIC) #define UPAGES 5 /* pages of u-area (1 for redzone) */ Index: src/sys/conf/files diff -u src/sys/conf/files:1.1216 src/sys/conf/files:1.1217 --- src/sys/conf/files:1.1216 Wed Nov 7 07:43:07 2018 +++ src/sys/conf/files Sun Dec 2 21:00:13 2018 @@ -1,4 +1,4 @@ -# $NetBSD: files,v 1.1216 2018/11/07 07:43:07 maxv Exp $ +# $NetBSD: files,v 1.1217 2018/12/02 21:00:13 maxv Exp $ # @(#)files.newconf 7.5 (Berkeley) 5/10/93 version 20171118 @@ -30,6 +30,7 @@ defparam opt_syslimits.h CHILD_MAX OPEN_ defflag opt_diagnostic.h _DIAGNOSTIC defflag GPROF defflag KASAN +defflag KLEAK defparam opt_copy_symtab.h makeoptions_COPY_SYMTAB Index: src/sys/conf/ssp.mk diff -u src/sys/conf/ssp.mk:1.2 src/sys/conf/ssp.mk:1.3 --- src/sys/conf/ssp.mk:1.2 Sun Jan 8 17:10:35 2017 +++ src/sys/conf/ssp.mk Sun Dec 2 21:00:13 2018 @@ -1,4 +1,4 @@ -# $NetBSD: ssp.mk,v 1.2 2017/01/08 17:10:35 christos Exp $ +# $NetBSD: ssp.mk,v 1.3 2018/12/02 21:00:13 maxv Exp $ .if ${USE_SSP:Uno} == "yes" COPTS.kern_ssp.c+= -fno-stack-protector -D__SSP__ @@ -10,6 +10,8 @@ COPTS.kern_ssp.c+= -fno-stack-protector COPTS.cpu.c+= -fno-stack-protector .endif +COPTS.subr_kleak.c+= -fno-stack-protector + # The following files use alloca(3) or variable array allocations. # Their full name is noted as documentation. VARSTACK= \ Index: src/sys/kern/files.kern diff -u src/sys/kern/files.kern:1.25 src/sys/kern/files.kern:1.26 --- src/sys/kern/files.kern:1.25 Thu Nov 15 09:38:57 2018 +++ src/sys/kern/files.kern Sun Dec 2 21:00:13 2018 @@ -1,4 +1,4 @@ -# $NetBSD: files.kern,v 1.25 2018/11/15 09:38:57 maxv Exp $ +# $NetBSD: files.kern,v 1.26 2018/12/02 21:00:13 maxv Exp $ # # kernel sources @@ -117,6 +117,7 @@ file kern/subr_interrupt.c kern file kern/subr_iostat.c kern file kern/subr_ipi.c kern file kern/subr_kcpuset.c kern +file kern/subr_kleak.c kleak defflag opt_kmem.h KMEM_GUARD KMEM_SIZE defparam opt_kmem.h KMEM_GUARD_DEPTH Index: src/sys/kern/subr_pool.c diff -u src/sys/kern/subr_pool.c:1.227 src/sys/kern/subr_pool.c:1.228 --- src/sys/kern/subr_pool.c:1.227 Mon Sep 10 13:11:05 2018 +++ src/sys/kern/subr_pool.c Sun Dec 2 21:00:13 2018 @@ -1,4 +1,4 @@ -/* $NetBSD: subr_pool.c,v 1.227 2018/09/10 13:11:05 maxv Exp $ */ +/* $NetBSD: subr_pool.c,v 1.228 2018/12/02 21:00:13 maxv Exp $ */ /*- * Copyright (c) 1997, 1999, 2000, 2002, 2007, 2008, 2010, 2014, 2015 @@ -33,11 +33,12 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: subr_pool.c,v 1.227 2018/09/10 13:11:05 maxv Exp $"); +__KERNEL_RCSID(0, "$NetBSD: subr_pool.c,v 1.228 2018/12/02 21:00:13 maxv Exp $"); #ifdef _KERNEL_OPT #include "opt_ddb.h" #include "opt_lockdebug.h" +#include "opt_kleak.h" #endif #include <sys/param.h> @@ -105,6 +106,14 @@ static void pool_redzone_check(struct po # define pool_redzone_check(pp, ptr) /* NOTHING */ #endif +#ifdef KLEAK +static void pool_kleak_fill(struct pool *, void *); +static void pool_cache_kleak_fill(pool_cache_t, void *); +#else +#define pool_kleak_fill(pp, ptr) __nothing +#define pool_cache_kleak_fill(pc, ptr) __nothing +#endif + static void *pool_page_alloc_meta(struct pool *, int); static void pool_page_free_meta(struct pool *, void *); @@ -937,6 +946,7 @@ pool_get(struct pool *pp, int flags) KASSERT((((vaddr_t)v + pp->pr_itemoffset) & (pp->pr_align - 1)) == 0); FREECHECK_OUT(&pp->pr_freecheck, v); pool_redzone_fill(pp, v); + pool_kleak_fill(pp, v); return (v); } @@ -2256,6 +2266,7 @@ pool_cache_get_slow(pool_cache_cpu_t *cc FREECHECK_OUT(&pc->pc_freecheck, object); pool_redzone_fill(&pc->pc_pool, object); + pool_cache_kleak_fill(pc, object); return false; } @@ -2303,6 +2314,7 @@ pool_cache_get_paddr(pool_cache_t pc, in splx(s); FREECHECK_OUT(&pc->pc_freecheck, object); pool_redzone_fill(&pc->pc_pool, object); + pool_cache_kleak_fill(pc, object); return object; } @@ -2728,6 +2740,26 @@ pool_page_free_meta(struct pool *pp, voi vmem_free(kmem_meta_arena, (vmem_addr_t)v, pp->pr_alloc->pa_pagesz); } +#ifdef KLEAK +static void +pool_kleak_fill(struct pool *pp, void *p) +{ + if (__predict_false(pp->pr_roflags & PR_NOTOUCH)) { + return; + } + kleak_fill_area(p, pp->pr_size); +} + +static void +pool_cache_kleak_fill(pool_cache_t pc, void *p) +{ + if (__predict_false(pc->pc_ctor != NULL || pc->pc_dtor != NULL)) { + return; + } + pool_kleak_fill(&pc->pc_pool, p); +} +#endif + #ifdef POOL_REDZONE #if defined(_LP64) # define PRIME 0x9e37fffffffc0000UL Index: src/sys/kern/sys_syscall.c diff -u src/sys/kern/sys_syscall.c:1.11 src/sys/kern/sys_syscall.c:1.12 --- src/sys/kern/sys_syscall.c:1.11 Sat Mar 7 16:38:49 2015 +++ src/sys/kern/sys_syscall.c Sun Dec 2 21:00:13 2018 @@ -1,4 +1,4 @@ -/* $NetBSD: sys_syscall.c,v 1.11 2015/03/07 16:38:49 christos Exp $ */ +/* $NetBSD: sys_syscall.c,v 1.12 2018/12/02 21:00:13 maxv Exp $ */ /*- * Copyright (c) 2006 The NetBSD Foundation, Inc. @@ -30,7 +30,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: sys_syscall.c,v 1.11 2015/03/07 16:38:49 christos Exp $"); +__KERNEL_RCSID(0, "$NetBSD: sys_syscall.c,v 1.12 2018/12/02 21:00:13 maxv Exp $"); #include <sys/syscall_stats.h> #include <sys/syscallvar.h> @@ -85,6 +85,7 @@ SYS_SYSCALL(struct lwp *l, const struct error = trace_enter(code, callp, TRACE_ARGS); if (__predict_false(error != 0)) return error; + kleak_fill_stack(); error = sy_call(callp, l, &uap->args, rval); trace_exit(code, callp, &uap->args, rval, error); return error; Index: src/sys/sys/systm.h diff -u src/sys/sys/systm.h:1.279 src/sys/sys/systm.h:1.280 --- src/sys/sys/systm.h:1.279 Fri Oct 5 22:12:37 2018 +++ src/sys/sys/systm.h Sun Dec 2 21:00:13 2018 @@ -1,4 +1,4 @@ -/* $NetBSD: systm.h,v 1.279 2018/10/05 22:12:37 christos Exp $ */ +/* $NetBSD: systm.h,v 1.280 2018/12/02 21:00:13 maxv Exp $ */ /*- * Copyright (c) 1982, 1988, 1991, 1993 @@ -43,6 +43,7 @@ #include "opt_ddb.h" #include "opt_multiprocessor.h" #include "opt_gprof.h" +#include "opt_kleak.h" #endif #if !defined(_KERNEL) && !defined(_STANDALONE) #include <stdbool.h> @@ -269,6 +270,18 @@ int copyoutstr(const void *, void *, siz int copyin(const void *, void *, size_t); int copyout(const void *, void *, size_t); +#ifdef KLEAK +#define copyout kleak_copyout +#define copyoutstr kleak_copyoutstr +int kleak_copyout(const void *, void *, size_t); +int kleak_copyoutstr(const void *, void *, size_t, size_t *); +void kleak_fill_area(void *, size_t); +void kleak_fill_stack(void); +#else +#define kleak_fill_area(a, b) __nothing +#define kleak_fill_stack() __nothing +#endif + #ifdef _KERNEL typedef int (*copyin_t)(const void *, void *, size_t); typedef int (*copyout_t)(const void *, void *, size_t); Index: src/sys/uvm/uvm_km.c diff -u src/sys/uvm/uvm_km.c:1.145 src/sys/uvm/uvm_km.c:1.146 --- src/sys/uvm/uvm_km.c:1.145 Sun Nov 4 13:48:27 2018 +++ src/sys/uvm/uvm_km.c Sun Dec 2 21:00:13 2018 @@ -1,4 +1,4 @@ -/* $NetBSD: uvm_km.c,v 1.145 2018/11/04 13:48:27 mlelstv Exp $ */ +/* $NetBSD: uvm_km.c,v 1.146 2018/12/02 21:00:13 maxv Exp $ */ /* * Copyright (c) 1997 Charles D. Cranor and Washington University. @@ -152,7 +152,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: uvm_km.c,v 1.145 2018/11/04 13:48:27 mlelstv Exp $"); +__KERNEL_RCSID(0, "$NetBSD: uvm_km.c,v 1.146 2018/12/02 21:00:13 maxv Exp $"); #include "opt_uvmhist.h" @@ -701,6 +701,10 @@ uvm_km_alloc(struct vm_map *map, vsize_t pmap_update(pmap_kernel()); + if ((flags & UVM_KMF_ZERO) == 0) { + kleak_fill_area((void *)kva, size); + } + UVMHIST_LOG(maphist,"<- done (kva=0x%jx)", kva,0,0,0); return(kva); } Added files: Index: src/sys/arch/amd64/include/kleak.h diff -u /dev/null src/sys/arch/amd64/include/kleak.h:1.1 --- /dev/null Sun Dec 2 21:00:14 2018 +++ src/sys/arch/amd64/include/kleak.h Sun Dec 2 21:00:13 2018 @@ -0,0 +1,101 @@ +/* $NetBSD: kleak.h,v 1.1 2018/12/02 21:00:13 maxv Exp $ */ + +/* + * Copyright (c) 2018 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Maxime Villard. + * + * 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. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 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/ksyms.h> + +#include <amd64/pmap.h> +#include <amd64/vmparam.h> + +static void +kleak_md_init(uintptr_t *sva, uintptr_t *eva) +{ + extern char __rodata_start; + *sva = (uintptr_t)KERNTEXTOFF; + *eva = (uintptr_t)&__rodata_start; +} + +static inline bool +__md_unwind_end(const char *name) +{ + if (!strcmp(name, "syscall") || + !strcmp(name, "handle_syscall") || + !strncmp(name, "Xintr", 5) || + !strncmp(name, "Xhandle", 7) || + !strncmp(name, "Xresume", 7) || + !strncmp(name, "Xstray", 6) || + !strncmp(name, "Xhold", 5) || + !strncmp(name, "Xrecurse", 8) || + !strcmp(name, "Xdoreti") || + !strncmp(name, "Xsoft", 5)) { + return true; + } + + return false; +} + +static void +kleak_md_unwind(struct kleak_hit *hit) +{ + uint64_t *rbp, rip; + const char *mod; + const char *sym; + int error; + + rbp = (uint64_t *)__builtin_frame_address(0); + + hit->npc = 0; + + while (1) { + /* 8(%rbp) contains the saved %rip. */ + rip = *(rbp + 1); + + if (rip < KERNBASE) { + break; + } + error = ksyms_getname(&mod, &sym, (vaddr_t)rip, KSYMS_PROC); + if (error) { + break; + } + hit->pc[hit->npc++] = rip; + if (__md_unwind_end(sym)) { + break; + } + + rbp = (uint64_t *)*(rbp); + if (rbp == 0) { + break; + } + + if (hit->npc >= KLEAK_HIT_MAXPC) { + break; + } + } +} Index: src/sys/kern/subr_kleak.c diff -u /dev/null src/sys/kern/subr_kleak.c:1.1 --- /dev/null Sun Dec 2 21:00:14 2018 +++ src/sys/kern/subr_kleak.c Sun Dec 2 21:00:13 2018 @@ -0,0 +1,440 @@ +/* $NetBSD: subr_kleak.c,v 1.1 2018/12/02 21:00:13 maxv Exp $ */ + +/* + * Copyright (c) 2018 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Maxime Villard. Based on an idea developed by Maxime Villard and + * Thomas Barabosch of Fraunhofer FKIE. + * + * 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. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 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: subr_kleak.c,v 1.1 2018/12/02 21:00:13 maxv Exp $"); + +#include <sys/param.h> +#include <sys/device.h> +#include <sys/kernel.h> +#include <sys/param.h> +#include <sys/conf.h> +#include <sys/systm.h> +#include <sys/kmem.h> +#include <sys/sysctl.h> +#include <sys/types.h> + +#include <sys/ksyms.h> +#include <sys/callout.h> + +#define __RET_ADDR __builtin_return_address(0) + +#undef copyout +#undef copyoutstr + +static uintptr_t kleak_kernel_text __read_mostly; + +static bool kleak_enabled = false; +static int kleak_nrounds = 1; /* tunable [1:8] */ + +static bool dummy1, dummy2, dummy3; + +static kmutex_t kleak_mtx __cacheline_aligned; + +static uint8_t *kleak_buf; +static size_t kleak_buf_size; + +static uint8_t kleak_pattern_list[8] = { + 154, 157, 221, 218, 159, 169, 167, 181 +}; +static size_t kleak_index; +static volatile uint8_t kleak_pattern_byte; + +/* -------------------------------------------------------------------------- */ + +#define KLEAK_HIT_MAXPC 16 +#define KLEAK_RESULT_MAXHIT 32 + +struct kleak_hit { + size_t len; + size_t leaked; + size_t npc; + uintptr_t pc[KLEAK_HIT_MAXPC]; +}; + +struct kleak_result { + size_t nhits; + size_t nmiss; + struct kleak_hit hits[KLEAK_RESULT_MAXHIT]; +}; + +static struct kleak_result result; + +/* The MD code. */ +#include <machine/kleak.h> + +/* -------------------------------------------------------------------------- */ + +static void +kleak_note(const void *pc, size_t len, size_t leaked) +{ + struct kleak_hit *hit; + uint8_t *byte; + size_t i, off; + + if ((uintptr_t)pc < kleak_kernel_text) { + return; + } + off = ((uintptr_t)pc - kleak_kernel_text); + if (__predict_false(off >= kleak_buf_size)) { + return; + } + + byte = kleak_buf + off; + + mutex_enter(&kleak_mtx); + + *byte |= __BIT(kleak_index); + for (i = 0; i < kleak_nrounds; i++) { + if (__predict_true((*byte & __BIT(i)) == 0)) + goto out; + } + + *byte = 0; + + if (__predict_false(result.nhits == KLEAK_RESULT_MAXHIT)) { + result.nmiss++; + goto out; + } + + hit = &result.hits[result.nhits++]; + hit->len = len; + hit->leaked = leaked; + kleak_md_unwind(hit); + +out: + mutex_exit(&kleak_mtx); +} + +int +kleak_copyout(const void *kaddr, void *uaddr, size_t len) +{ + const uint8_t *ptr = (const uint8_t *)kaddr; + size_t remain = len; + size_t cnt = 0; + + if (!kleak_enabled) { + goto out; + } + + while (remain-- > 0) { + if (__predict_false(*ptr == kleak_pattern_byte)) { + cnt++; + } + ptr++; + } + + if (__predict_false(cnt > 0)) { + kleak_note(__RET_ADDR, len, cnt); + } + +out: + return copyout(kaddr, uaddr, len); +} + +int +kleak_copyoutstr(const void *kaddr, void *uaddr, size_t len, size_t *done) +{ + const uint8_t *ptr = (const uint8_t *)kaddr; + size_t remain = len; + size_t cnt = 0; + + if (!kleak_enabled) { + goto out; + } + + while (remain-- > 0) { + if (*ptr == '\0') { + break; + } + if (__predict_false(*ptr == kleak_pattern_byte)) { + cnt++; + } + ptr++; + } + + if (__predict_false(cnt > 0)) { + kleak_note(__RET_ADDR, len, cnt); + } + +out: + return copyoutstr(kaddr, uaddr, len, done); +} + +void +kleak_fill_area(void *ptr, size_t len) +{ + memset(ptr, kleak_pattern_byte, len); +} + +void +kleak_fill_stack(void) +{ + char buf[USPACE-(2*PAGE_SIZE)]; + explicit_memset(buf, kleak_pattern_byte, sizeof(buf)); +} + +void __sanitizer_cov_trace_pc(void); + +/* + * We want an explicit memset, but inlined. So use a builtin with optimization + * disabled. + */ +void __attribute__((optimize("O0"))) +__sanitizer_cov_trace_pc(void) +{ + char buf[512]; + __builtin_memset(buf, kleak_pattern_byte, sizeof(buf)); +} + +/* -------------------------------------------------------------------------- */ + +static void +kleak_init(void) +{ + uintptr_t sva, eva; + + kleak_md_init(&sva, &eva); + kleak_kernel_text = sva; + + kleak_index = 0; + kleak_pattern_byte = kleak_pattern_list[kleak_index]; + + if (kleak_buf == NULL) { + mutex_init(&kleak_mtx, MUTEX_DEFAULT, IPL_NONE); + kleak_buf_size = (size_t)eva - (size_t)sva; + kleak_buf = kmem_zalloc(kleak_buf_size, KM_SLEEP); + } else { + /* Already initialized, just reset. */ + mutex_enter(&kleak_mtx); + memset(kleak_buf, 0, kleak_buf_size); + mutex_exit(&kleak_mtx); + } +} + +static int +kleak_rotate(void) +{ + mutex_enter(&kleak_mtx); + kleak_index++; + mutex_exit(&kleak_mtx); + + /* XXX: Should be atomic. */ + kleak_pattern_byte = kleak_pattern_list[kleak_index]; + + if (kleak_index >= kleak_nrounds) { + return ENOENT; + } + + return 0; +} + +static int +sysctl_kleak_rounds(SYSCTLFN_ARGS) +{ + struct sysctlnode node; + int error, val; + + val = *(int *)rnode->sysctl_data; + + node = *rnode; + node.sysctl_data = &val; + + error = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (error != 0 || newp == NULL) + return error; + if (val < 1 || val > 8) + return EINVAL; + if (kleak_enabled) + return EINVAL; + + *(int *)rnode->sysctl_data = val; + + return 0; +} + +static int +sysctl_kleak_patterns(SYSCTLFN_ARGS) +{ + struct sysctlnode node; + uint8_t val[8]; + int error; + + memcpy(val, rnode->sysctl_data, sizeof(val)); + + node = *rnode; + node.sysctl_data = &val; + + error = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (error != 0 || newp == NULL) + return error; + if (kleak_enabled) + return EINVAL; + + memcpy(rnode->sysctl_data, val, 8 * sizeof(uint8_t)); + + return 0; +} + +static int +sysctl_kleak_start(SYSCTLFN_ARGS) +{ + struct sysctlnode node; + int error; + bool val; + + val = *(bool *)rnode->sysctl_data; + + node = *rnode; + node.sysctl_data = &val; + + error = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (error != 0 || newp == NULL) + return error; + if (kleak_enabled) + return EEXIST; + if (!val) + return EINVAL; + + kleak_init(); + memset(&result, 0, sizeof(result)); + kleak_enabled = true; + + return 0; +} + +static int +sysctl_kleak_rotate(SYSCTLFN_ARGS) +{ + struct sysctlnode node; + int error; + bool val; + + val = *(bool *)rnode->sysctl_data; + + node = *rnode; + node.sysctl_data = &val; + + error = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (error != 0 || newp == NULL) + return error; + if (!val) + return EINVAL; + + return kleak_rotate(); +} + +static int +sysctl_kleak_stop(SYSCTLFN_ARGS) +{ + struct sysctlnode node; + int error; + bool val; + + val = *(bool *)rnode->sysctl_data; + + node = *rnode; + node.sysctl_data = &val; + + error = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (error != 0 || newp == NULL) + return error; + if (!val) + return EINVAL; + + kleak_enabled = false; + + return 0; +} + +static int +sysctl_kleak_result(SYSCTLFN_ARGS) +{ + struct sysctlnode node; + + node = *rnode; + node.sysctl_data = &result; + node.sysctl_size = sizeof(result); + + return sysctl_lookup(SYSCTLFN_CALL(&node)); +} + +SYSCTL_SETUP(sysctl_kleak_setup, "sysctl kleak subtree setup") +{ + const struct sysctlnode *kleak_rnode; + + kleak_rnode = NULL; + + sysctl_createv(clog, 0, NULL, &kleak_rnode, + CTLFLAG_PERMANENT, + CTLTYPE_NODE, "kleak", NULL, + NULL, 0, NULL, 0, + CTL_CREATE, CTL_EOL); + + sysctl_createv(clog, 0, &kleak_rnode, NULL, + CTLFLAG_READWRITE, + CTLTYPE_INT, "rounds", + SYSCTL_DESCR("Number of rounds"), + sysctl_kleak_rounds, 0, &kleak_nrounds, 0, + CTL_CREATE, CTL_EOL); + sysctl_createv(clog, 0, &kleak_rnode, NULL, + CTLFLAG_READWRITE, + CTLTYPE_STRUCT, "patterns", + SYSCTL_DESCR("List of patterns"), + sysctl_kleak_patterns, 0, &kleak_pattern_list, + sizeof(kleak_pattern_list), + CTL_CREATE, CTL_EOL); + sysctl_createv(clog, 0, &kleak_rnode, NULL, + CTLFLAG_READWRITE, + CTLTYPE_BOOL, "start", + SYSCTL_DESCR("Start KLEAK"), + sysctl_kleak_start, 0, &dummy1, 0, + CTL_CREATE, CTL_EOL); + sysctl_createv(clog, 0, &kleak_rnode, NULL, + CTLFLAG_READWRITE, + CTLTYPE_BOOL, "rotate", + SYSCTL_DESCR("Rotate the pattern"), + sysctl_kleak_rotate, 0, &dummy2, 0, + CTL_CREATE, CTL_EOL); + sysctl_createv(clog, 0, &kleak_rnode, NULL, + CTLFLAG_READWRITE, + CTLTYPE_BOOL, "stop", + SYSCTL_DESCR("Stop KLEAK"), + sysctl_kleak_stop, 0, &dummy3, 0, + CTL_CREATE, CTL_EOL); + sysctl_createv(clog, 0, &kleak_rnode, NULL, + CTLFLAG_PERMANENT, + CTLTYPE_STRUCT, "result", + SYSCTL_DESCR("Get the result"), + sysctl_kleak_result, 0, NULL, 0, + CTL_CREATE, CTL_EOL); +} Index: src/usr.sbin/kleak/Makefile diff -u /dev/null src/usr.sbin/kleak/Makefile:1.1 --- /dev/null Sun Dec 2 21:00:14 2018 +++ src/usr.sbin/kleak/Makefile Sun Dec 2 21:00:13 2018 @@ -0,0 +1,14 @@ +# $NetBSD: Makefile,v 1.1 2018/12/02 21:00:13 maxv Exp $ + +NOMAN= # defined + +PROG= kleak +SRCS= kleak.c + +LDADD+= -lelf +LDADD+= -lutil +DPADD+= ${LIBELF} +DPADD+= ${LIBUTIL} + +.include <bsd.own.mk> +.include <bsd.prog.mk> Index: src/usr.sbin/kleak/kleak.c diff -u /dev/null src/usr.sbin/kleak/kleak.c:1.1 --- /dev/null Sun Dec 2 21:00:14 2018 +++ src/usr.sbin/kleak/kleak.c Sun Dec 2 21:00:13 2018 @@ -0,0 +1,344 @@ +/* $NetBSD: kleak.c,v 1.1 2018/12/02 21:00:13 maxv Exp $ */ + +/* + * Copyright (c) 2018 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Maxime Villard. Based on an idea developed by Maxime Villard and + * Thomas Barabosch of Fraunhofer FKIE. + * + * 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. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 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 <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> +#include <errno.h> +#include <err.h> +#include <fcntl.h> +#include <sys/param.h> +#include <sys/sysctl.h> +#include <sys/wait.h> + +#include <gelf.h> +#include <inttypes.h> +#include <libelf.h> +#include <util.h> + +#include <sys/rbtree.h> + +#define _PATH_KSYMS "/dev/ksyms" + +struct sym { + char *name; + uint64_t value; + uint64_t size; +}; + +static struct sym **syms = NULL; +static size_t nsyms = 0; + +static int +compare_value(const void *p1, const void *p2) +{ + const struct sym *s1 = *(const struct sym * const *)p1; + const struct sym *s2 = *(const struct sym * const *)p2; + + if (s1->value > s2->value) { + return -1; + } else if (s1->value < s2->value) { + return 1; + } + /* + * to produce a stable result, it's better not to return 0 + * even for __strong_alias. + */ + if (s1->size > s2->size) { + return -1; + } else if (s1->size < s2->size) { + return 1; + } + return strcmp(s1->name, s2->name); +} + +static void +ksymload(void) +{ + Elf *e; + Elf_Scn *s; + GElf_Shdr sh_store; + GElf_Shdr *sh; + Elf_Data *d; + int fd; + size_t size, i; + + fd = open(_PATH_KSYMS, O_RDONLY); + if (fd == -1) { + err(EXIT_FAILURE, "open"); + } + if (elf_version(EV_CURRENT) == EV_NONE) { + goto elffail; + } + e = elf_begin(fd, ELF_C_READ, NULL); + if (e == NULL) { + goto elffail; + } + for (s = elf_nextscn(e, NULL); s != NULL; s = elf_nextscn(e, s)) { + sh = gelf_getshdr(s, &sh_store); + if (sh == NULL) { + goto elffail; + } + if (sh->sh_type == SHT_SYMTAB) { + break; + } + } + if (s == NULL) { + errx(EXIT_FAILURE, "no symtab"); + } + d = elf_getdata(s, NULL); + if (d == NULL) { + goto elffail; + } + assert(sh->sh_size == d->d_size); + size = sh->sh_size / sh->sh_entsize; + for (i = 1; i < size; i++) { + GElf_Sym st_store; + GElf_Sym *st; + struct sym *sym; + + st = gelf_getsym(d, (int)i, &st_store); + if (st == NULL) { + goto elffail; + } + if (ELF_ST_TYPE(st->st_info) != STT_FUNC) { + continue; + } + sym = emalloc(sizeof(*sym)); + sym->name = estrdup(elf_strptr(e, sh->sh_link, st->st_name)); + sym->value = (uint64_t)st->st_value; + sym->size = st->st_size; + nsyms++; + syms = erealloc(syms, sizeof(*syms) * nsyms); + syms[nsyms - 1] = sym; + } + qsort(syms, nsyms, sizeof(*syms), compare_value); + return; +elffail: + errx(EXIT_FAILURE, "libelf: %s", elf_errmsg(elf_errno())); +} + +static const char * +ksymlookup(uint64_t value, uint64_t *offset) +{ + size_t hi; + size_t lo; + size_t i; + + /* + * try to find the smallest i for which syms[i]->value <= value. + * syms[] is ordered by syms[]->value in the descending order. + */ + + hi = nsyms - 1; + lo = 0; + while (lo < hi) { + const size_t mid = (lo + hi) / 2; + const struct sym *sym = syms[mid]; + + assert(syms[lo]->value >= sym->value); + assert(sym->value >= syms[hi]->value); + if (sym->value <= value) { + hi = mid; + continue; + } + lo = mid + 1; + } + assert(lo == nsyms - 1 || syms[lo]->value <= value); + assert(lo == 0 || syms[lo - 1]->value > value); + for (i = lo; i < nsyms; i++) { + const struct sym *sym = syms[i]; + + if (sym->value <= value && + (sym->size == 0 || value - sym->value <= sym->size )) { + *offset = value - sym->value; + return sym->name; + } + if (sym->size != 0 && sym->value + sym->size < value) { + break; + } + } + return NULL; +} + +/* -------------------------------------------------------------------------- */ + +#define KLEAK_HIT_MAXPC 16 +#define KLEAK_RESULT_MAXHIT 32 + +struct kleak_hit { + size_t len; + size_t leaked; + size_t npc; + uintptr_t pc[KLEAK_HIT_MAXPC]; +}; + +struct kleak_result { + size_t nhits; + size_t nmiss; + struct kleak_hit hits[KLEAK_RESULT_MAXHIT]; +}; + +static int nrounds = 4; + +static void kleak_dump(struct kleak_result *result) +{ + struct kleak_hit *hit; + const char *name; + uint64_t offset; + size_t i, j; + + for (i = 0; i < result->nhits; i++) { + hit = &result->hits[i]; + printf("+ Possible info leak: " + "[len=%zu, leaked=%zu]\n", hit->len, hit->leaked); + for (j = 0; j < hit->npc; j++) { + name = ksymlookup(hit->pc[j], &offset); + if (name == NULL) { + name = "(unknown)"; + } + printf("| #%zu %p in %s\n", j, (void *)hit->pc[j], + name); + } + } + + printf("> Summary: %d rounds, %zu hits, %zu misses\n", + nrounds, result->nhits, result->nmiss); +} + +__dead static void +usage(void) +{ + fprintf(stderr, "%s [-n nrounds] cmd\n", getprogname()); + exit(EXIT_FAILURE); +} + +int main(int argc, char *argv[]) +{ + struct kleak_result result; + uint8_t markers[8]; + size_t len; + int i, ch; + pid_t pid; + bool val; + + while ((ch = getopt(argc, argv, "n:")) != -1) { + switch (ch) { + case 'n': + nrounds = atoi(optarg); + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + if (argc == 0) { + usage(); + } + + if (nrounds < 1 || nrounds > 8) { + usage(); + } + + ksymload(); + + /* In case a previous run was killed. */ + val = true; + sysctlbyname("kleak.stop", NULL, NULL, &val, sizeof(val)); + + if (sysctlbyname("kleak.rounds", NULL, NULL, &nrounds, sizeof(nrounds)) == -1) + err(EXIT_FAILURE, "couldn't set rounds"); + + len = sizeof(markers); + if (sysctlbyname("kleak.patterns", &markers, &len, NULL, 0) == -1) + err(EXIT_FAILURE, "couldn't set rounds"); + + printf("> Launching with markers: 0x%02x 0x%02x 0x%02x 0x%02x " + "0x%02x 0x%02x 0x%02x 0x%02x\n", markers[0], markers[1], markers[2], + markers[3], markers[4], markers[5], markers[6], markers[7]); + + val = true; + if (sysctlbyname("kleak.start", NULL, NULL, &val, sizeof(val)) == -1) + err(EXIT_FAILURE, "couldn't start kleak"); + + for (i = 0; i < nrounds; i++) { + printf("[kleak] Pass %d " + "-----------------------------------------------------------\n", i+1); + + pid = fork(); + switch (pid) { + case -1: + err(EXIT_FAILURE, "fork"); + case 0: + execvp(argv[0], argv); + _Exit(EXIT_FAILURE); + } + for (;;) { + int status; + + pid = wait4(-1, &status, 0, NULL); + if (pid == -1) { + if (errno == ECHILD) { + break; + } + err(EXIT_FAILURE, "wait4"); + } + if (pid != 0 && WIFEXITED(status)) { + break; + } + } + + if (i < nrounds - 1) { + val = true; + if (sysctlbyname("kleak.rotate", NULL, NULL, &val, sizeof(val)) == -1) + err(EXIT_FAILURE, "couldn't rotate"); + } + } + + val = true; + if (sysctlbyname("kleak.stop", NULL, NULL, &val, sizeof(val)) == -1) + err(EXIT_FAILURE, "couldn't stop kleak"); + + len = sizeof(result); + if (sysctlbyname("kleak.result", &result, &len, NULL, 0) == -1) + err(EXIT_FAILURE, "couldn't get the result"); + + kleak_dump(&result); + + return 0; +}