From patchwork Fri Oct 28 11:21:36 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pavel Machek X-Patchwork-Id: 9401819 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id CB57C60231 for ; Fri, 28 Oct 2016 11:21:56 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id BDF242A7BF for ; Fri, 28 Oct 2016 11:21:56 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id B1D6D2A7C3; Fri, 28 Oct 2016 11:21:56 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.2 required=2.0 tests=BAYES_00, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from mother.openwall.net (mother.openwall.net [195.42.179.200]) by mail.wl.linuxfoundation.org (Postfix) with SMTP id 30E582A7BF for ; Fri, 28 Oct 2016 11:21:55 +0000 (UTC) Received: (qmail 15878 invoked by uid 550); 28 Oct 2016 11:21:53 -0000 Mailing-List: contact kernel-hardening-help@lists.openwall.com; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-ID: Reply-To: kernel-hardening@lists.openwall.com Delivered-To: mailing list kernel-hardening@lists.openwall.com Received: (qmail 15852 invoked from network); 28 Oct 2016 11:21:53 -0000 Date: Fri, 28 Oct 2016 13:21:36 +0200 From: Pavel Machek To: Mark Rutland Cc: Kees Cook , Peter Zijlstra , Arnaldo Carvalho de Melo , kernel list , Ingo Molnar , Alexander Shishkin , "kernel-hardening@lists.openwall.com" Message-ID: <20161028112136.GA5635@amd> References: <20161026204748.GA11177@amd> <20161027082801.GE3568@worktop.programming.kicks-ass.net> <20161027091104.GB19469@amd> <20161027093334.GK3102@twins.programming.kicks-ass.net> <20161027212747.GA18147@amd> <20161028095141.GA5806@leverpostej> MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: <20161028095141.GA5806@leverpostej> User-Agent: Mutt/1.5.23 (2014-03-12) Subject: Re: [kernel-hardening] rowhammer protection [was Re: Getting interrupt every million cache misses] X-Virus-Scanned: ClamAV using ClamSMTP Hi! > I missed the original, so I've lost some context. You can read it on lkml, but I guess you did not lose anything important. > Has this been tested on a system vulnerable to rowhammer, and if so, was > it reliable in mitigating the issue? > > Which particular attack codebase was it tested against? I have rowhammer-test here, commit 9824453fff76e0a3f5d1ac8200bc6c447c4fff57 Author: Mark Seaborn . I do not have vulnerable machine near me, so no "real" tests, but I'm pretty sure it will make the error no longer reproducible with the newer version. [Help welcome ;-)] > > +struct perf_event_attr rh_attr = { > > + .type = PERF_TYPE_HARDWARE, > > + .config = PERF_COUNT_HW_CACHE_MISSES, > > + .size = sizeof(struct perf_event_attr), > > + .pinned = 1, > > + /* FIXME: it is 1000000 per cpu. */ > > + .sample_period = 500000, > > +}; > > I'm not sure that this is general enough to live in core code, because: Well, I'd like to postpone debate 'where does it live' to the later stage. The problem is not arch-specific, the solution is not too arch-specific either. I believe we can use Kconfig to hide it from users where it does not apply. Anyway, lets decide if it works and where, first. > * the precise semantics of performance counter events varies drastically > across implementations. PERF_COUNT_HW_CACHE_MISSES, might only map to > one particular level of cache, and/or may not be implemented on all > cores. If it maps to one particular cache level, we are fine (or maybe will trigger protection too often). If some cores are not counted, that's bad. > * On some implementations, it may be that the counters are not > interchangeable, and for those this would take away > PERF_COUNT_HW_CACHE_MISSES from existing users. Yup. Note that with this kind of protection, one missing performance counter is likely to be small problem. > > + *ts = now; > > + > > + /* FIXME msec per usec, reverse logic? */ > > + if (delta < 64 * NSEC_PER_MSEC) > > + mdelay(56); > > +} > > If I round-robin my attack across CPUs, how much does this help? See below for new explanation. With 2 CPUs, we are fine. On monster big-little 8-core machines, we'd probably trigger protection too often. Pavel diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index e24e981..c6ffcaf 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -315,6 +315,7 @@ config PGTABLE_LEVELS source "init/Kconfig" source "kernel/Kconfig.freezer" +source "kernel/events/Kconfig" menu "Processor type and features" diff --git a/kernel/events/Kconfig b/kernel/events/Kconfig new file mode 100644 index 0000000..7359427 --- /dev/null +++ b/kernel/events/Kconfig @@ -0,0 +1,9 @@ +config NOHAMMER + tristate "Rowhammer protection" + help + Enable rowhammer attack prevention. Will degrade system + performance under attack so much that attack should not + be feasible. + + To compile this driver as a module, choose M here: the + module will be called nohammer. diff --git a/kernel/events/Makefile b/kernel/events/Makefile index 2925188..03a2785 100644 --- a/kernel/events/Makefile +++ b/kernel/events/Makefile @@ -4,6 +4,8 @@ endif obj-y := core.o ring_buffer.o callchain.o +obj-$(CONFIG_NOHAMMER) += nohammer.o + obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o obj-$(CONFIG_UPROBES) += uprobes.o diff --git a/kernel/events/nohammer.c b/kernel/events/nohammer.c new file mode 100644 index 0000000..d96bacd --- /dev/null +++ b/kernel/events/nohammer.c @@ -0,0 +1,140 @@ +/* + * Attempt to prevent rowhammer attack. + * + * On many new DRAM chips, repeated read access to nearby cells can cause + * victim cell to flip bits. Unfortunately, that can be used to gain root + * on affected machine, or to execute native code from javascript, escaping + * the sandbox. + * + * Fortunately, a lot of memory accesses is needed between DRAM refresh + * cycles. This is rather unusual workload, and we can detect it, and + * prevent the DRAM accesses, before bit flips happen. + * + * Thanks to Peter Zijlstra . + * Thanks to presentation at blackhat. + */ + +#include +#include +#include + +static struct perf_event_attr rh_attr = { + .type = PERF_TYPE_HARDWARE, + .config = PERF_COUNT_HW_CACHE_MISSES, + .size = sizeof(struct perf_event_attr), + .pinned = 1, + .sample_period = 10000, +}; + +/* + * How often is the DRAM refreshed. Setting it too high is safe. + */ +static int dram_refresh_msec = 64; + +static DEFINE_PER_CPU(struct perf_event *, rh_event); +static DEFINE_PER_CPU(u64, rh_timestamp); + +static void rh_overflow(struct perf_event *event, struct perf_sample_data *data, struct pt_regs *regs) +{ + u64 *ts = this_cpu_ptr(&rh_timestamp); /* this is NMI context */ + u64 now = ktime_get_mono_fast_ns(); + s64 delta = now - *ts; + + *ts = now; + + if (delta < dram_refresh_msec * NSEC_PER_MSEC) + mdelay(dram_refresh_msec); +} + +static __init int rh_module_init(void) +{ + int cpu; + +/* + * DRAM refresh is every 64 msec. That is not enough to prevent rowhammer. + * Some vendors doubled the refresh rate to 32 msec, that helps a lot, but + * does not close the attack completely. 8 msec refresh would probably do + * that on almost all chips. + * + * Thinkpad X60 can produce cca 12,200,000 cache misses a second, that's + * 780,800 cache misses per 64 msec window. + * + * X60 is from generation that is not yet vulnerable from rowhammer, and + * is pretty slow machine. That means that this limit is probably very + * safe on newer machines. + */ + int cache_misses_per_second = 12200000; + +/* + * Maximum permitted utilization of DRAM. Setting this to f will mean that + * when more than 1/f of maximum cache-miss performance is used, delay will + * be inserted, and will have similar effect on rowhammer as refreshing memory + * f times more often. + * + * Setting this to 8 should prevent the rowhammer attack. + */ + int dram_max_utilization_factor = 8; + + /* + * Hardware should be able to do approximately this many + * misses per refresh + */ + int cache_miss_per_refresh = (cache_misses_per_second * dram_refresh_msec)/1000; + + /* + * So we do not want more than this many accesses to DRAM per + * refresh. + */ + int cache_miss_limit = cache_miss_per_refresh / dram_max_utilization_factor; + +/* + * DRAM is shared between CPUs, but these performance counters are per-CPU. + */ + int max_attacking_cpus = 2; + + /* + * We ignore counter overflows "too far away", but some of the + * events might have actually occurent recently. Thus additional + * factor of 2 + */ + + rh_attr.sample_period = cache_miss_limit / (2*max_attacking_cpus); + + printk("Rowhammer protection limit is set to %d cache misses per %d msec\n", + (int) rh_attr.sample_period, dram_refresh_msec); + + /* XXX borken vs hotplug */ + + for_each_online_cpu(cpu) { + struct perf_event *event; + + event = perf_event_create_kernel_counter(&rh_attr, cpu, NULL, rh_overflow, NULL); + per_cpu(rh_event, cpu) = event; + if (!event) { + pr_err("Not enough resources to initialize nohammer on cpu %d\n", cpu); + continue; + } + pr_info("Nohammer initialized on cpu %d\n", cpu); + } + return 0; +} + +static __exit void rh_module_exit(void) +{ + int cpu; + + for_each_online_cpu(cpu) { + struct perf_event *event = per_cpu(rh_event, cpu); + + if (event) + perf_event_release_kernel(event); + } + return; +} + +module_init(rh_module_init); +module_exit(rh_module_exit); + +MODULE_DESCRIPTION("Rowhammer protection"); +//MODULE_LICENSE("GPL v2+"); +MODULE_LICENSE("GPL");