DragonFlyBSD Kernel Audit
DF-2819 / verdict.json
← back to finding ↓ download raw
{
  "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"]
}