DF-4852 / verdict.json
{ "finding_id": "DF-4852", "status": "reproduced", "reproduced": 1, "impact": "none", "confidence": "certain", "verdict": "Reproduced on the guest as root: dm_target_stripe_inits chunk-size guard (dm_target_striped.c:98-99) does int*int arithmetic, so chunk sizes >= 2^22 pass by signed overflow -- the kernel ACCEPTED 4194304 (2^22, product 2^31 wraps to INT_MIN) and 2147483647 (INT_MAX, product wraps to -256), stored them in tsc->stripe_chunksize, and correctly REJECTED the non-overflow control 1048576 with EINVAL (dmesg: dm: Error unsupported chunk size 524288KB); the table ioctl readback confirmed the kernel stored 2 4194304 .... Impact is integrity/validation only (no kernel memory unsafety: with chunksize < 2^31 every strategy/dump intermediate stays in range -- verified in the finding text), hence impact=none and severity Low; /dev/mapper/control is 0640 root:operator so the attacker is already privileged. Fix behaviorally validated: with (uintmax_t)chunksize*DEV_BSIZE > MAXPHYS patched module, cases A and C return EINVAL while the legitimate 128-block chunk still loads (fix_run.log).", "exploit_chain": "", "evidence": "run.log (all four cases + params readback 2 4194304 ... + PASS), dmesg_run.log (dm: Stripe 2 devices chunk size 4194304KB accepted-BUG, 524288KB rejected-control, 2147483647KB accepted-BUG), fix_run.log (patched module: A and C -> EINVAL, D still accepted), VERDICT.md full math + no-memory-corruption analysis", "kernel_refs": [ "sys/dev/disk/dm/striped/dm_target_striped.c:98-103", "sys/dev/disk/dm/striped/dm_target_striped.c:111-117", "sys/dev/disk/dm/striped/dm_target_striped.c:306-343", "sys/dev/disk/dm/device-mapper.c:584-596", "sys/dev/disk/dm/device-mapper.c:181", "sys/cpu/x86_64/include/param.h:122" ], "poc_changes": "no seed; wrote proplib dm-ioctl PoC. Two iterations were required: DragonFly prop_dictionary_send_ioctl() is one-way (unlike NetBSD), so the table readback needs prop_dictionary_sendrecv_ioctl(); and a remove_all() preamble is needed after an earlier run leaves the device (EEXIST).", "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": 1200, "guest_dirty": 0, "build_cmd": "guest: cd /tmp/df4852 && cc -O -Wall -o dm_chunk dm_chunk.c -lprop (modules: kldload dm; kldload dm_target_striped)", "run_cmd": "guest: /tmp/df4852/dm_chunk (fix check: kldload /usr/src/sys/dev/disk/dm/striped/dm_target_striped.ko && /tmp/df4852_p/dm_chunk)", "code_hash": "9bf8b857eccf6c5d9f9e5be635437fc31f7ef7da47a5f59e4d5007971cda1ee1", "notes": "kprintf at :100 labels the block count as KB (cosmetic). DF-1933 and DF-2454 (known, not re-reported) are the SAME ksnprintf-length-tracking root in stripe_table/stripe_info -- DF-2454 is effectively the info() variant of DF-1933. Refcount audit: dm_pdev_insert/decr are 1:1 per stripe_dev; dm_table_add_deps holds NO pdev reference (dm_table_free_deps only frees maps), and its -1 dup return being ignored by the ctor is therefore harmless.", "recommended_fix": "Cast to 64-bit before the multiply: if (chunksize < 1 || (uintmax_t)chunksize * DEV_BSIZE > MAXPHYS) -- see fix.diff", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff applies clean (hunk at :96) and dm_target_striped.ko rebuilds -Werror (fix_build.log, RC=0). BEHAVIORALLY validated on the guest: with the patched module loaded, the previously-accepted overflow values 4194304 and 2147483647 now return EINVAL with the correct dm: Error unsupported chunk size dmesg lines while the legitimate 128-block chunk still loads (fix_run.log: cases A/C UNEXPECTED=now-rejected, D accepted).", "fix_evidence": "fix.diff; fix_build.log (patched module, -Werror, RC=0); fix_run.log (patched behavior: A/C EINVAL, D accepted)" } |