DF-2819 / verdict.json
{ "finding_id": "DF-2819", "status": "reproduced", "reproduced": 1, "impact": "none", "confidence": "certain", "verdict": "Reproduced exactly: the kmalloc_obj node zone for tmpfs refuses allocation (ENOSPC via M_NULLOK at tmpfs_subr.c:110-113) once sum(ks_use[].memuse) reaches the raised limit, but memuse is accounted in cacheline-aligned units (ks_objsize=320B vs sizeof(struct tmpfs_node)=272B, guest-measured via KLD probe), so a tmpfs mounted with inodes=1600000 filled at 1,360,369 files (85.02%, predicted 1,360,000) with vmstat -m showing tmpfs_node MemUse==Limit==415M while tm_nodes_inuse(1.36M) < tm_nodes_max(1.6M). No panic (tmpfs passes M_NULLOK), no memory-safety impact; the defect is quota correctness/availability (every tmpfs, including default /tmp, delivers ~85% of its advertised inode budget) plus an API trap: ks_objsize is not exported so any caller computing count*sizeof limits hits it.", "exploit_chain": "", "evidence": [ "run.log: 'stopped at 1360369: No space left on device' (k*320 >= 1600000*272 first holds at k=1360000)", "vmstat.txt: tmpfs_node row '1.30M 415M 428M 415M 1.30M' (MemUse==Limit); idle 1-node tmpfs row shows per-object accounting of exactly 320 bytes", "env.txt + szprobe dmesg: 'SZPROBE node=272 nodealign=320 dirent=88 direntalign=128'", "VERDICT.md: full derivation, offset analysis (root node + KMALLOC_LOOSE_SIZE flush granularity)" ], "kernel_refs": [ "sys/kern/kern_kmalloc.c:899", "sys/kern/kern_kmalloc.c:694", "sys/kern/kern_kmalloc.c:706", "sys/kern/kern_kmalloc.c:715", "sys/kern/kern_slaballoc.c:610", "sys/vfs/tmpfs/tmpfs_vfsops.c:206", "sys/vfs/tmpfs/tmpfs_subr.c:110" ], "poc_changes": "Authored trigger from scratch (fill.c file-creation loop as unprivileged user; szprobe KLD to measure sizeof/alignment in the running kernel); no seed PoC existed.", "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": 95, "guest_dirty": 0, "build_cmd": "cc -O2 -o /root/fill /root/fill.c; (cd /root/szprobe && make)", "run_cmd": "mount -t tmpfs -o size=8g,inodes=1600000 none /mnt/tfill && chmod 777 /mnt/tfill && su -m nobody -c '/mnt/tfill/fill /mnt/tfill 1500000'", "code_hash": "31c66f545cf3df52054b1364e80c1074014f34c365353ed3d52be01c30a2c897", "notes": "Guest reset (vm.sh reset with-src) after evidence capture; guest_dirty=0. Panic variant of the limit gate (kern_kmalloc.c:706) chased and killed for tmpfs (M_NULLOK); no other kmalloc_obj zone is both fillable by an unprivileged user and raised/default-limited within reach.", "recommended_fix": "tmpfs: raise the node-zone limit with __VM_CACHELINE_ALIGN(sizeof(struct tmpfs_node)) * tm_nodes_max (see fix.diff); longer term export ks_objsize to callers.", "fix_status": "not_testable", "fix_kernel_uname": "", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Fix not kernel-validated (Low non-corruption finding; nativekernel rebuild + 1.6M-file re-run disproportionate). Fixed-threshold arithmetic proven exact: with limit N*320 the k-th alloc checks (k-1)*320 >= N*320 iff k >= N+1, and tmpfs refuses at tm_nodes_inuse >= N (tmpfs_subr.c:107) first, so the zone limit can never bind before the fs cap.", "fix_evidence": ["fix.diff"] } |