├── README.md ├── fs.py └── zeromaps.c /README.md: -------------------------------------------------------------------------------- 1 | A "living" Linux process with no memory 2 | ======================================= 3 | 4 | 5 | 6 | tl;dr 7 | ---- 8 | 9 | - thread1 goes into uninterruptible sleep 10 | - thread2 unmaps everything and segfaults 11 | - segv can't kill the process because of thread1's D state 12 | - /proc/pid/maps is now empty 13 | - ??? 14 | - PROFIT!!! 15 | 16 | 17 | 18 | [![asciicast](https://asciinema.org/a/313677.svg)](https://asciinema.org/a/313677) 19 | 20 | 21 | 22 | Implementation details 23 | ---------------------- 24 | 25 | This code gets a list of all memory maps from `/proc/self/maps`, then creates a 26 | new executable map where it jits some code that calls `munmap()` on each of the 27 | maps it just got, and finally on the map it's on. This is just a quick example 28 | with no portability in mind, so the source code contains the actual bytes that 29 | would be emitted by a x64 compiler. After unmapping the final map, where the 30 | jit code lies, there's no new instruction to execute and a segfault is raised. 31 | 32 | This segfault can't kill the entire process if one thread is stuck in 33 | uninterruptible sleep. To reliably send a thread in such state, we create a 34 | simple FUSE filesystem in python, in which doing anything on a particular file 35 | will block until a key is pressed. 36 | 37 | This code also does its own "linking" to make sure that the list of maps 38 | doesn't get unmapped too early. 39 | 40 | 41 | Requirements 42 | ------------ 43 | 44 | - a c compiler 45 | - python2 + fuse 46 | - x64 47 | - a modern Linux with no vsyscall page (this page is too high up and munmap 48 | would return EINVAL) 49 | 50 | 51 | 52 | Why 53 | --- 54 | 55 | I don't know. I thought it was funny. 56 | -------------------------------------------------------------------------------- /fs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import fuse 4 | import time 5 | import sys 6 | import errno 7 | 8 | class fs(fuse.Operations): 9 | def getattr(self, path, fh=None): 10 | if "blockme" in path: 11 | raw_input('blocked, press enter') 12 | raise fuse.FuseOSError(errno.ENOENT) 13 | 14 | fuse.FUSE(fs(), sys.argv[1], foreground=True) 15 | -------------------------------------------------------------------------------- /zeromaps.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | void *thread(void *x) { open("x/blockme", 0); return 0; } 10 | 11 | int main() { 12 | pthread_t t; 13 | pthread_create(&t, 0, thread, 0); 14 | sleep(1); 15 | 16 | puts("spawned thread"); 17 | 18 | FILE *mapsfile = fopen("/proc/self/maps", "r"); 19 | 20 | struct m { long start, end; }; 21 | static struct m maps[128]; // oughta be enough for anyone 22 | int i = 0; 23 | 24 | while (fscanf(mapsfile, "%lx-%lx %*[^\n]", &maps[i].start, &maps[i].end) != EOF) i++; 25 | puts("got all the maps"); 26 | 27 | // - copy all the data we'll need in a new map 28 | // - add this new map's address at the end of the struct 29 | // - jit some code that does this: 30 | // void f() { struct m *x = f-2048; for (i in 0..n) munmap(m[i].start...); } 31 | // ^ will segfault when returning from the final munmap 32 | void *newmap = mmap(0, 4096, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); 33 | maps[i].start = (long)newmap; 34 | maps[i].end = (long)newmap + 4096; 35 | 36 | memcpy(newmap, maps, sizeof maps); 37 | 38 | #if 0 39 | static inline long munmap(long a, long b) { 40 | long ret; 41 | asm volatile("syscall" : "=a"(ret) : "a"(11), "D"(a), "S"(b) : "cc", "r11", "rcx"); 42 | return ret; 43 | } 44 | 45 | void f() { 46 | struct m { long start, end; } *p = (void *)((char *) f - 2048); 47 | while (1) { 48 | munmap(p->start, p->end - p->start); 49 | p++; 50 | } 51 | } 52 | #endif 53 | 54 | unsigned char bytes[] = { 55 | /* 0000000000000000 : */ 56 | /* 0: */ 0x48, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* movabs $0x0,%rdx */ 57 | /* ^^^^^^^^^^^^^^^^ f's address ^^^^^^^^^^^^^^^^^^ */ 58 | /* a: */ 0x41, 0xb8, 0x0b, 0x00, 0x00, 0x00, /* mov $0xb,%r8d */ 59 | /* 10: */ 0x48, 0x8b, 0xba, 0x00, 0xf8, 0xff, 0xff, /* mov -0x800(%rdx),%rdi */ 60 | /* 17: */ 0x48, 0x8b, 0xb2, 0x08, 0xf8, 0xff, 0xff, /* mov -0x7f8(%rdx),%rsi */ 61 | /* 1e: */ 0x44, 0x89, 0xc0, /* mov %r8d,%eax */ 62 | /* 21: */ 0x48, 0x29, 0xfe, /* sub %rdi,%rsi */ 63 | /* 24: */ 0x0f, 0x05, /* syscall */ 64 | /* 26: */ 0x48, 0x83, 0xc2, 0x10, /* add $0x10,%rdx */ 65 | /* 2a: */ 0xeb, 0xe4 /* jmp 10 */ 66 | }; 67 | 68 | void (*f)(void) = (void *)(char *)newmap + 2048; 69 | memcpy(bytes+2, &f, 8); 70 | memcpy(f, bytes, sizeof bytes); 71 | 72 | printf("BTW MY PID IS %d\n", getpid()); 73 | 74 | puts("start unmapping like crazy"); 75 | f(); 76 | } 77 | --------------------------------------------------------------------------------