DF-3164 / verdict.json
{ "finding_id": "DF-3164", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "A mount(2) call with MNT_UPDATE and a non-NULL fspec on a live UDF mount reaches udf_mountfs, which overwrites mp->mnt_data (udf_vfsops.c:265) and, on any subsequent failure, frees the new udf_mnt at bail (:393-403) without restoring the old pointer; sys_mounts update-failure path (vfs_syscalls.c:408-419) keeps the mount alive, so every later VFS op derived from the mount operates on freed memory. Reproduced on the stock INVARIANTS guest: a lookup after the failed remount panicked in lwkt_gettoken called from udf_hashlookup/udf_vget on the freed struct udf_mnt (chunk already reused - proof of a live UAF primitive, not merely a stale read). Beyond the panic, udf_unmount on the freed structure performs a second VOP_CLOSE, second vrele and a double kfree, and the freed object is groomable, giving a memory-corruption surface behind the mount-privilege boundary (untrusted-media threat model). The companion success-path variant silently leaks the previous udf_mnt, its hash table and an open device vnode.", "exploit_chain": "root/delegated-mount: mount crafted-but-valid UDF image; call mount(2) {MNT_RDONLY|MNT_UPDATE, fspec=<garbage device>} on the live mount (fails EINVAL, mnt_data now dangling); groom the freed ~280-byte chunk (any kernel kmalloc activity; verified reused in practice); trigger via lookup (udf_vget->udf_hashlookup: token/hashtbl fields) or umount (im_devvp->v_rdev->si_mountpoint NULL write, VOP_CLOSE through forged v_op vector, double kfree) -> panic reproduced; weaponization to code execution plausible on the no-KASLR/no-SMEP guest but gated behind mount privilege, so escalation adds nothing - the demonstrated impact is kernel heap UAF + deterministic panic of a machine by mounting untrusted media", "evidence": [ "findings/poc/DF-3164/run.log: panic: assertion \"count & TOK_COUNTMASK\" failed in _lwkt_reltokref with stack lwkt_gettoken <- udf_hashlookup <- udf_vget", "findings/poc/DF-3164/panic_core.txt: decisive panic lines", "findings/poc/DF-3164/fix_run.log: errno=45 EOPNOTSUPP on patched kernel, mount stays listed, ls /mnt/T25 succeeds (FS_STILL_USABLE)", "findings/poc/DF-3164/trigger.c: direct mount(2) MNT_UPDATE trigger", "findings/poc/DF-3164/run.sh: full reproduction script" ], "kernel_refs": [ "sys/vfs/udf/udf_vfsops.c:153", "sys/vfs/udf/udf_vfsops.c:265", "sys/vfs/udf/udf_vfsops.c:393", "sys/kern/vfs_syscalls.c:408", "sys/vfs/udf/udf_vnops.c:947", "sys/vfs/udf/udf_vfsops.c:494" ], "poc_changes": "mount(8) on DragonFly rejects the 4-arg -u type special node form, so the seed idea was replaced with a direct mount(2) trigger (trigger.c) using a userland copy of struct udf_args from sys/vfs/udf/udf_mount.h; run.sh was made self-contained (vnconfig cleanup) after a Device busy iteration; lookup target switched to a never-cached name (T25) so the namecache cannot bypass VOP_LOOKUP.", "attempts": 3, "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": 420, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o /tmp/df3164_trigger /tmp/df3164_trigger.c", "run_cmd": "sh /tmp/df3164-run.sh (mounts /tmp/df3164-base.udf via vn0; mount(2) MNT_UPDATE fspec=/dev/vn1; then ls /mnt/T25)", "code_hash": "ec7df09cd20a6a8f14ab452b853de152dcc9b4d719ab62f99fd9f61e3b4a2bc8", "notes": "Baseline panic captured from serial console (ssh session dies with the kernel). Guest reset to clean-source snapshot afterwards. The successful-remount variant (leak of old udf_mnt/hashtbl/open devvp) is covered by the same fix (EOPNOTSUPP before any state change).", "recommended_fix": "Reject MNT_UPDATE with a non-NULL fspec in udf_mount (EOPNOTSUPP) and restore mp->mnt_data on every udf_mountfs bail path (save it on entry).", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Mon Sep 7 11:10:39 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff applied to guest /usr/src, kernel rebuilt (make nativekernel, 5.5 min) and installed. Re-running the byte-identical PoC: the MNT_UPDATE trigger now returns EOPNOTSUPP before udf_mountfs is entered, the UDF mount stays listed and healthy, and the post-failure lookup succeeds without any panic - the UAF is gone.", "fix_evidence": [ "findings/poc/DF-3164/fix_run.log: errno=45 (Operation not supported), /dev/vn0 on /mnt (udf, read-only, local), ls rc=0, FS_STILL_USABLE", "findings/poc/DF-3164/fix.diff" ] } |