DF-3179 / verdict.json
{ "finding_id": "DF-3179", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "REPRODUCED on stock INVARIANTS X86_64_GENERIC: an unprivileged user (uid 1001) running a plain link(2) flood in the default-boot /tmp tmpfs panics the kernel with tmpfs dirent: malloc limit exceeded (kern_link -> tmpfs_nlink -> tmpfs_alloc_dirent -> _kmalloc_obj) once the mounts per-mount dirent zone hits its default ks_limit = kmem_lim/10 = 390MB on the 4GB guest (~3.19M dirents; observed between 3.0M and 3.2M links, ~90s). Root cause is mount-side: tmpfs_mount (tmpfs_vfsops.c:200-207) raises ONLY the node zone limit; the dirent zone keeps the malloc default and tmpfs_alloc_dirent (tmpfs_subr.c:306-307) allocates with M_WAITOK and no M_NULLOK, so the allocator PANICs on limit exhaustion instead of returning NULL; tmpfs_nlink has no dirent budget. fix.diff (raise dirent+name zone limits with nodes_max + M_NULLOK/ENOSPC in tmpfs_alloc_dirent) validated in-guest on a rebuilt kernel: same flood terminates with ENOSPC, guest stays up, filesystem recovers after freeing dirents.", "exploit_chain": "", "evidence": [ "run.log (links=...3.0M then guest dies)", "panic.txt (full serial panic + backtrace tmpfs_alloc_dirent+0x38 <- tmpfs_nlink+0x11a <- kern_link+0x252)", "run.fixed.log (patched kernel: link(): No space left on device at ~3.12M, no panic)", "fix_validation.log", "env.txt" ], "kernel_refs": [ "sys/vfs/tmpfs/tmpfs_vfsops.c:200", "sys/vfs/tmpfs/tmpfs_vfsops.c:206", "sys/vfs/tmpfs/tmpfs_subr.c:306", "sys/kern/kern_slaballoc.c:371", "sys/kern/kern_slaballoc.c:877", "sys/kern/kern_kmalloc.c:706", "sys/vfs/tmpfs/tmpfs_vnops.c:1170" ], "poc_changes": "initial draft needed sys/statfs.h include removed (DragonFly declares statfs in sys/mount.h); otherwise the seeded concept (link flood) worked as-is", "attempts": 2, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 90, "guest_dirty": 0, "build_cmd": "cc -O2 -o linkflood linkflood.c", "run_cmd": "mkdir -p /tmp/df3179 && ./linkflood /tmp/df3179 (as unprivileged user)", "code_hash": "d00312504e39bb9b05a58e1504d68530e6e75520ae6e45087188c965a3ed42de", "notes": "Also reachable by root on any tmpfs via creat/mkdir/symlink when nodes_max exceeds (RAM/10)/sizeof(tmpfs_dirent), and unprivileged on usermounts (vfs.usermount=1); link() is the zero-budget path (no nodes/pages consumed). Fix validation: nativekernel BUILD_RC=0, installkernel INSTALL_RC=0, reboot into kernel #1, re-run -> ENOSPC + guest up.", "recommended_fix": "raise dirent+name zone limits in proportion to tm_nodes_max at mount and allocate dirents with M_NULLOK returning ENOSPC", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Mon Sep 7 22:45:12 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "baseline panics at ~3.1M links; patched kernel with fix.diff returns ENOSPC from link(2) at the same threshold, no panic, filesystem remains usable and recovers after dirent free; guest reset to clean snapshot afterwards", "fix_evidence": [ "fix_validation.log", "run.fixed.log" ] } |