DF-4472 / verdict.json
{ "finding_id": "DF-4472", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Uninitialized stack pointer `type` (never initialized at dm_ioctl.c:684/690-695; prop_dictionary_get_cstring_nocopy at :731 leaves it untouched when the \"type\" key is absent) is dereferenced as a C string by dm_target_lookup (strcmp, dm_target.c:127) and dm_target_autoload (ksnprintf, dm_target.c:74). Verified three ways on the guest: (1) groom mode proves the wild lookup executed on 250/250 rounds (typeless reload returns the :741 ENOENT and wipes the inactive table destroyed at :722-723 -- the only ENOENT return after that point); (2) console printed the garbage string via the autoload ksnprintf; (3) flood mode produced a fatal trap 12 page fault at strcmp+0xc (fault VA 0x8) and killed the guest. Ceiling is a reliable local DoS (panic) for an operator-group user -- wild read, no write primitive, so no escalation chain (hard blocker). Fix (type=NULL init + checked get_cstring_nocopy -> EINVAL) validated on a rebuilt dm.ko: groom returns EINVAL, flood survives 8x800 with no panic.", "exploit_chain": "", "evidence": "run.log (groom 50/50 + 200/200 destroy-proof; flood -> panic; patched: rv2=22, no panic); panic.txt (Fatal trap 12, Stopped at strcmp+0xc: movzbl (%rdi,%rax,1),%edx; dm: could not autoload module for target <garbage>); fix.diff + fix validation section of run.log", "kernel_refs": [ "sys/dev/disk/dm/dm_ioctl.c:684", "sys/dev/disk/dm/dm_ioctl.c:690-695", "sys/dev/disk/dm/dm_ioctl.c:730-732", "sys/dev/disk/dm/dm_ioctl.c:737-741", "sys/dev/disk/dm/dm_target.c:127", "sys/dev/disk/dm/dm_target.c:74", "sys/libprop/prop_dictionary_util.c:185-198" ], "poc_changes": "Authored fresh (pass-2 finding, no seed). Two modes: groom (valid reload then typeless reload + inactive-table query via table ioctl with DM_QUERY_INACTIVE_TABLE_FLAG) and flood (multi-threaded malformed reloads with padding entries to vary stack residue). Added pre-round verification that reload#1 actually loaded (defends against the trivial empty-table explanation).", "attempts": 4, "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": 2100, "guest_dirty": 0, "build_cmd": "cc -o dm_uninit_type dm_uninit_type.c -lprop -lpthread", "run_cmd": "./dm_uninit_type 200 ; ./dm_uninit_type --flood 8 800", "code_hash": "8ec0e07cab4afb394ad46ffd75c646e3431612c932feab9b5a4e83c64939b4cf", "notes": "Same trigger surface as the known DF-1639/DF-2448 family (missing proplist members) but a distinct defect: those are NULL-pointer derefs of cmd_data/iterator; this is an uninitialized stack POINTER VALUE used as a string. Threat model: /dev/mapper/control is 0640 root:operator (device-mapper.c:181) -- operator-group member or root required.", "recommended_fix": "Initialize type=NULL before the loop and per iteration; reject entries whose DM_TABLE_TYPE is missing/not a string with EINVAL instead of looking up the stale pointer.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Baseline: groom proved the wild read on 250/250 rounds and flood panicked the guest (trap 12, strcmp+0xc). Patched (dm.ko rebuilt from fix.diff, -Werror clean, kldload-ed): groom returns EINVAL(22) every round (type checked before lookup), flood 8 threads x 800 completes with no panic, guest healthy.", "fix_evidence": "run.log fix-validation section; fix.diff (combined dm_ioctl.c diff, hunk adding type=NULL + checked prop_dictionary_get_cstring_nocopy)" } |