DF-2781 / verdict.json
{ "finding_id": "DF-2781", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "Unprivileged persistent kernel-memory exhaustion proven: 12 queues x 512 msgs x 16 KB = 96.4 MB remained allocated (vmstat -m: mqueues 97.5M) after the strander exited - queues and messages survive last close unless unlinked (sys_mqueue.c:391-399) and cannot be enumerated; sustained stranding drove the kernel to ENOMEM on mq_send/mq_open (M_WAITOK kmalloc failing) at ~390 MB on the 4 GB guest with 2.48 GB user memory still free, and retry loops made zero further progress - the ceiling is kernel-heap-side and the consumption is permanent until reboot. No system-wide or per-user queue/byte accounting exists (only per-process open count and per-queue geometry, sys_mqueue.c:71-76); Linux ships RLIMIT_MSGQUEUE + fs.mqueue.* for exactly this attack class.", "exploit_chain": "loop: mq_open(unique, {maxmsg=512, msgsize=16352}) -> send 512 x 16 KB messages (8.4 MB/queue) -> close (queue+messages persist) -> exit strander -> kernel retains everything; repeat with new names until kmalloc ENOMEM; no reclamation path without the (withheld) names.", "evidence": ["run.log: 96.4 MB stranded then 'mqueues 7.54K 97.5M' AFTER process exit", "run.exhaust.log: 'mq_send #280: Cannot allocate memory' + 'mq_open: Cannot allocate memory' at ~390 MB; zero progress across 2.5 min of retries", "fix_validation.log: with kern.mqueue.mq_max_queues=8 the strander stops after exactly 8 queues / 64.2 MB"], "kernel_refs": ["sys/kern/sys_mqueue.c:71-76", "sys/kern/sys_mqueue.c:391-399", "sys/kern/sys_mqueue.c:582-589"], "poc_changes": "msgsize lowered 16384->16352 (the send-path cap includes the 32-byte mq_msg header, so attr-accepted 16384 always EMSGSIZEs on send); brief/exhaust modes; byte accounting includes header+slack.", "attempts": 3, "guest_uname": "DragonFly dfbsd 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": 900, "guest_dirty": 0, "build_cmd": "cc -O2 -o strand strand.c", "run_cmd": "./strand brief; vmstat -m | grep -i mqueues; ./strand exhaust 100000", "code_hash": "9d0d97f87d09b0290da943736fd9b5569c74cadb22c75b5a0d0414b0addcc980", "notes": "Guest survived with basic ops working at the 390 MB ceiling (subsystem-dead degradation rather than full wedge on this box); the mechanism, permanence and ENOMEM are the demonstrated facts. Side observation: attr validation accepts mq_msgsize=16384 but mq_send1 rejects sending that size (header included in the cap) - cosmetic inconsistency, not filed.", "recommended_fix": "Add system-wide (and ideally per-uid) queue-count and queued-byte caps as sysctls (kern.mqueue.mq_max_queues in fix.diff, default 256); upstream should also consider an enumeration/cleanup interface.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Tue Sep 1 04:57:57 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "With kern.mqueue.mq_max_queues=8, strand exhaust stops after exactly 8 queues / 64.2 MB (mq_open ENOMEM at #9) and a second strander cannot create one queue; sysctl restored to 256. First fix iteration (cap check before lock acquisition, goto exit with mq==NULL) panicked the kernel - rejected, corrected placement; that incident surfaced DF-2782.", "fix_evidence": "fix_validation.log; fixiter1 analysis in VERDICT.md" } |