DF-3103 / verdict.json
{ "finding_id": "DF-3103", "status": "reproduced", "reproduced": 1, "impact": "leak", "confidence": "certain", "verdict": "udf_vget() computes the fentry allocation size as UDF_FENTRY_SIZE + l_ea + l_ad in wrapping 32-bit arithmetic from disk-controlled fields, so crafted l_ea/l_ad wrap it to a small kmalloc while udf_readatoffset()s embedded-ICB path (udf_vnops.c:1037-1039) still uses &fentry->data[l_ea] with *size = l_ad (sign-extended negative int); uiomove() then copies user-chosen byte counts from up to 4 GB past the undersized M_UDFFENTRY allocation. Reproduced as uid 1001: read(EMB, 96/1024/4096) each returned OOB kernel heap bytes; EMB2 with l_ea=0x40000000 produced EFAULT, proving the kernel dereferences the attacker-chosen wild pointer. Patched module rejects the malformed file entry with EINVAL at vget.", "exploit_chain": "crafted UDF image (attacker-supplied media or downloaded image mounted by root) -> unprivileged open()+read() of the embedded file -> deterministic kernel heap disclosure at attacker-chosen offset/length; wild-offset variant yields EFAULT/page-fault (DoS on kernels without copy-fault guards). Read primitive only; serves as the info-leak half of an exploit pair with the DF-0831 write primitive on the same surface.", "evidence": [ "run.log: read() returned 96 bytes from embedded file x3 + DF3103_LEAK_CONFIRMED x3 (deterministic), 1024- and 4096-byte reads also returned OOB heap", "run.log: read(96) failed: Bad address for EMB2 (l_ea=0x40000000 -> EFAULT on the wild kernel read)", "fix_run.log: open /mnt/EMB: Invalid argument and open /mnt/EMB2: Invalid argument on the patched module", "craft_img.py: EMB l_ea=0x1000 l_ad=0xFFFFF150 (176+...=0x100000200 -> int 0x200); EMB2 l_ea=0x40000000 l_ad=0xC0000140" ], "kernel_refs": [ "sys/vfs/udf/udf_vfsops.c:527", "sys/vfs/udf/udf_vfsops.c:528", "sys/vfs/udf/udf_vnops.c:1037", "sys/vfs/udf/udf_vnops.c:1039", "sys/vfs/udf/udf_vnops.c:403" ], "poc_changes": "Seed PoC authored fresh for pass 2 (extends the DF-0831 crafter). One iteration: added <fcntl.h>; added the EMB2 wild-pointer variant after the first successful leak runs to prove the primitive beyond the slab-mapped neighborhood.", "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": 240, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o /tmp/df3102_poc /tmp/df3102_poc.c (guest); python3 craft_img.py df3102.udf (host)", "run_cmd": "./run.sh (host driver: mount as root; su maxx -c /tmp/df3102_poc emb /mnt/EMB 96|1024|4096 and ... emb /mnt/EMB2 96)", "code_hash": "b50456f09049a3a3f9cecccbf847b544c5408e238f3831a07660d0c0ed0b9d2a", "notes": "Distinct from pass-1 DF-0880 (bcopy source over-read at the same vget): this is the vnops sink \u2014 allocation-size wrap plus negative *size sign-extended through uiomove. The leaked neighborhood was zeroed slab on this run; determinism and the EFAULT variant carry the proof. Guest reset with-src after validation.", "recommended_fix": "Validate l_ea/l_ad in udf_vget against bsize - UDF_FENTRY_SIZE (FE is one block), and give udf_readatoffset()s embedded path offset/remaining-length handling (FreeBSD-style).", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0 (same kernel; udf.ko module rebuilt from patched /usr/src/sys/vfs/udf and kldloaded)", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Combined fix (vget validation + embedded-path offset handling + zero-size loop break) applied, udf.ko rebuilt, same image remounted: open(/mnt/EMB) and open(/mnt/EMB2) now fail with EINVAL at vget before any allocation or read; no OOB access occurs (fix_run.log).", "fix_evidence": [ "fix_run.log", "fix_build.log", "fix.diff", "df31xx_combined_fix.diff" ] } |