DF-0785 / harness.c
/* * DF-0785 deterministic harness. * * Transcribes the EXACT allocation + copy sequence of ntfs_ntlookupfile * (sys/vfs/ntfs/ntfs_subr.c:867-891) into userspace, backed by a poisoned * allocator that places the "object" at the end of a guard-paged page so the * overflow is detected byte-exactly (no slab luck required). * * blsize = vap->va_a_iroot->ir_size; // :867 -> kmalloc size * rdsize = vap->va_datalen; // :868 -> copy size * rdbuf = kmalloc(blsize, M_TEMP, M_WAITOK); // :888 * ntfs_readattr(... rdsize ... rdbuf); // :890-891 -> memcpy * * No check rdsize<=blsize exists. Compare ntfs_ntreaddir:1105 which sizes * with max(va_datalen, f_dirblsz). With a poisoned allocator we prove the * overflow extent = rdsize - blsize attacker-controlled bytes. * * Build (Linux host): cc -O2 -o harness harness.c * Run: ./harness [blsize] [rdsize] (defaults 16 200) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <sys/mman.h> #include <unistd.h> static long PAGE = 0; /* Poisoned allocation: return a pointer to the LAST `blsize` bytes of a * fresh page, with the next page PROT_NONE. Any write past blsize faults * into the guard page -> deterministic SIGSEGV at the exact overflow byte. */ static unsigned char *poison_alloc(uint32_t blsize) { long pg = PAGE; /* two pages: lower usable, upper guard (PROT_NONE). Place object at the * very end of the lower page so overflow immediately hits the guard. */ char *base = mmap(NULL, pg * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (base == MAP_FAILED) { perror("mmap"); exit(2); } if (mprotect(base + pg, pg, PROT_NONE) != 0) { perror("mprotect"); exit(2); } return (unsigned char *)(base + pg - blsize); } int main(int argc, char **argv) { PAGE = sysconf(_SC_PAGESIZE); uint32_t blsize = (argc > 1) ? (uint32_t)strtoul(argv[1], 0, 0) : 16; /* ir_size */ uint32_t rdsize = (argc > 2) ? (uint32_t)strtoul(argv[2], 0, 0) : 200;/* va_datalen */ printf("[harness] DF-0785 ntfs_ntlookupfile overflow transcription\n"); printf("[harness] blsize (ir_size, kmalloc) = %u\n", blsize); printf("[harness] rdsize (va_datalen, copy) = %u\n", rdsize); if (rdsize <= blsize) { printf("[harness] rdsize <= blsize: no overflow (well-formed case). OK.\n"); return 0; } printf("[harness] OVERFLOW = %u attacker-controlled bytes past %u-byte object\n", rdsize - blsize, blsize); /* attacker-controlled resident $INDEX_ROOT data (what ntfs_readattr copies) */ unsigned char *attacker = malloc(rdsize); memset(attacker, 0x44, rdsize); /* "D" marker */ /* the buggy allocation */ unsigned char *rdbuf = poison_alloc(blsize); printf("[harness] rdbuf = %p (last %u bytes of a page; next page PROT_NONE)\n", (void *)rdbuf, blsize); /* the buggy copy: ntfs_readattr(... rdsize ... rdbuf) -> memcpy(rdbuf, src, rdsize) */ printf("[harness] performing ntfs_readattr-equivalent memcpy of %u bytes...\n", rdsize); fflush(stdout); memcpy(rdbuf, attacker, rdsize); /* <-- BUG: writes rdsize into blsize buffer */ /* If we reach here the allocator did not guard the tail (should not happen). */ printf("[harness] ERROR: overflow was NOT caught -- allocator guard missing\n"); return 3; } |