DF-3169 / verdict.json
{ "finding_id": "DF-3169", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "REPRODUCED on stock INVARIANTS X86_64_GENERIC: a forced unmount (dounmount MNT_FORCE -> autofs_unmount frees every autofs_node and the autofs_mount after a 1 Hz request-drain scan, autofs_vfsops.c:195-261) races any in-flight autofs trigger (stat/getdents under the mountpoint). The triggers retry loop parks 1-2 s with no reference on the mount (autofs.c:451-453); the unmounts scan hits that gap, frees everything, and the triggers next autofs_trigger_one() run executes wholly on freed memory. Deterministic KLD-orchestrated VOP-path run (run.log): post-free request inserted into the live sc_requests TAILQ with ar_mount == freed amp; ar_from/ar_path filled with 0x41 markers planted in the reallocated freed-amp block (read-after-free of a reused 4288-byte kernel heap object, delivered to the automountd via AUTOFSREQUEST); 6 observed an_p->an_retries write-after-free transitions on the freed/reallocated 256-byte node block; plus autofs.c:409-413 can write an_cached/an_wildcards and arm a 600 s callout inside freed memory. autofs_nmkdir variant is a guaranteed NULL-deref panic (dummymount.mnt_data == NULL -> mtx_lock on NULL+offset) with a us window (code-trace certain; 40 probabilistic userspace cycles did not hit it). Not an unpriv->root chain: the free side requires roots umount -f; realistic incident is root/cron automount -u racing unprivileged stat churn -> kernel heap corruption or panic.", "exploit_chain": "unprivileged churn: stat /autofs_mp/key (VOP_NRESOLVE -> autofs_trigger, sleeps seconds) || root: umount -f /autofs_mp -> vflush(FORCECLOSE) force-vgones referenced vnodes -> request-drain scan sees empty TAILQ during triggers 2 s retry gap -> kfree(autofs_mount) + objcache_put(all nodes) -> trigger wakes -> autofs_trigger_one reads freed amp (strlcpy into daemon-visible request: heap-info leak of whichever object reuses the block), writes anp->an_retries/an_cached/an_wildcards and arms &anp->an_callout in freed memory, inserts request with dangling ar_mount; nmkdir variant NULL-deref panics. Primitive: repeated 4-byte increment + two bool writes + callout-state corruption at type-stable freed offsets; escalation to uid0 not applicable (attacker side already root).", "evidence": [ "run.log: DF3169: dounmount returned 0 (ticks=8296) ... ARE FREED NOW then DANGLING REQUEST ar=... ar_mount=0xfffff8008d214600 == FREED amp, ar_from=\"AAAA...\", 6x POLL ... <== WRITE-AFTER-FREE, SUMMARY dangling=1 write_after_free_transitions=6 ... UAF CONFIRMED", "run_kthread.log: same markers via direct kthread call into the real autofs_trigger (SAME-BLOCK reuse proven for both freed objects)", "panic.txt: Fatal trap 12 in autofs_trigger+0x28c during dounmount (interleaving proof; NULL itself is a kthread-curproc harness artifact)", "fix_run.log: identical harness on fix-v2 kernel -> CLEAN - no post-free access, dangling=0, write_after_free_transitions=0", "fix_variantB.log: 6/6 production-shaped umount -f cycles clean on patched kernel; run_variantB.log: 40 cycles unpatched (no panic, us window)", "FIX_NOTES.md: fix v1 (vfs_busy) deadlock analysis (ncp-lock->mnt_lock vs dounmount mnt_lock->cache_inval)" ], "kernel_refs": [ "sys/vfs/autofs/autofs_vnops.c:191-242", "sys/vfs/autofs/autofs_vnops.c:245-278", "sys/vfs/autofs/autofs_vnops.c:297-325", "sys/vfs/autofs/autofs.c:300-455", "sys/vfs/autofs/autofs.c:409-413", "sys/vfs/autofs/autofs_vfsops.c:195-261" ], "poc_changes": "Harness evolved through 3 revisions: v1 kthread direct-call (panicked in AUTOFS_WARN curproc NULL - artifact, kept as panic.txt); v2 kthread + vfs.autofs.debug=0 + same-CPU freed-block reallocation probes (marker 0x41 amp / zeroed node) - deterministic silent-UAF proof; v3 (final) drives the trigger through the REAL VOP path (userspace stat churn) so the fix could be validated at the vop boundary. sysctls: timeout=1 retry_attempts=12 retry_delay=2 interruptible=0(kthread)/1(vop).", "attempts": 5, "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": 8400, "guest_dirty": 0, "build_cmd": "cd /root/df3169 && make KERNCONF=X86_64_GENERIC && cc -O2 -pthread -o fake_daemon fake_daemon.c", "run_cmd": "/bin/sh /root/df3169/run.sh", "code_hash": "10682aa9a8ca6d630a1f58f620b5c9209a1e508e34467c09545584f4da48f946", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Mon Sep 7 15:11:46 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (stock + fix.diff)", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "FIXED. Baseline on stock kernel #0: dangling=1, 6 write-after-free transitions, UAF CONFIRMED. First fix revision (vfs_busy wrap) DEADLOCKED the guest inside dounmount (ncp-lock->mnt_lock AB-BA vs dounmounts mnt_lock->cache_inval; nlookup inner vfs_busy second cycle) - rejected, documented in FIX_NOTES.md. Shipped fix.diff v2 (per-mount am_dying/am_triggers references; autofs_unmount marks dying, drains, bounded-waits for triggers with EBUSY abort before freeing): identical harness returns CLEAN with dangling=0 and zero post-free transitions while dounmount correctly waits ~31 s for the in-flight trigger; 6/6 production-shaped umount -f cycles clean; normal autofs trigger-timeout and plain umount behavior preserved (sanity.sh). Residual sub-us entry race (VRECLAIMED check vs v_mount->mnt_data read under concurrent force-vgone) documented for upstream.", "fix_evidence": "fix_run.log (CLEAN summary), fix_variantB.log (6/6 cycles), fix_build.log (nativekernel RC=0 + installkernel IRC=0 + uname #1), FIX_NOTES.md (v1 deadlock), sanity.sh results in VERDICT.md", "notes": "The impact=panic reflects the demonstrated family: live kernel heap corruption (UAF read+write, deterministic) plus the guaranteed NULL-deref panic variant in autofs_nmkdir (dummymount->mnt_data==NULL). Unprivileged-only trigger of the free side is impossible (umount -f is privileged) - realistic incident: cron automount -u / admin umount -f racing user automount churn. panic.txts specific NULL is a harness artifact (kthread curproc) and is NOT claimed as product impact.", "recommended_fix": "Per-mount trigger references: am_dying/am_triggers in struct autofs_mount; acquire across autofs_nresolve/autofs_readdir/autofs_nmkdir bodies; autofs_unmount marks dying before the request-drain loop and bounded-waits (EBUSY abort) for am_triggers==0 before freeing nodes - no mnt_lock inside the vops (vfs_busy there deadlocks)." } |