DF-5768 / verdict.json
{ "finding_id": "DF-5768", "status": "reproduced", "reproduced": true, "impact": "panic", "confidence": "certain", "verdict": "Card-derived ivar->erase_sector == 0 (mmc.c:1476-1477 SD / 1595-1596 MMC: erase_sector * write_bl_len / 512 truncates to 0 for a CSD with WRITE_BL_LEN bits=0) reaches production mmcsd_delete() (mmcsd.c:407-411) via MMC_IVAR_ERASE_SECTOR; the first BIO_FREEBLKS (kernel TRIM path, devfs_spec_freeblks devfs_vnops.c:2028-2049) executes `start % erase_sector` == %0 and panics the kernel. Reproduced deterministically (2/2 runs) end-to-end on the stock INVARIANTS kernel with a KLD harness that compiles the production mmc.c in and drives it with a synthetic malicious MMC card (fake mmcbr bridge); the builtin production mmcsd attached /dev/mmcsd0 and the panic fired inside it: Fatal trap 18: integer divide fault, Stopped at mmcsd_task+0x6cb: idivl %ecx,%eax. Fix validated on a rebuilt kernel: MAX(...,1) clamp at both derivation sites -> erase_sector=1, TRIM completes, clean kldunload, guest stays up. DoS only: the idiv traps before any memory misuse; no escalation path.", "exploit_chain": "malicious SD/MMC card -> CSD WRITE_BL_LEN=0 bits -> ivar->erase_sector=0 at discovery -> mmcsd0 attaches -> first BIO_FREEBLKS (TRIM via VOP_FREEBLKS/devfs_spec_freeblks or root-issued freeblks) -> mmcsd_delete modulo by zero -> Fatal trap 18 integer divide fault -> kernel panic / system down. (No memory-corruption primitive: trap fires before any misuse; impact ceiling is denial of service.)", "evidence": "panic.txt (full trap record incl. DF-5768: csd.erase_sector=1 csd.write_bl_len=1 -> ivar->erase_sector=0 then Fatal trap 18 and idivl %ecx,%eax in mmcsd_task+0x6cb); run.log (2 baseline runs); fix.log (patched kernel: erase_sector=1, biowait returns, kldunload rc=0); VERDICT.md; dfh_harness.c; build.log", "kernel_refs": [ "sys/bus/mmc/mmc.c:1476", "sys/bus/mmc/mmc.c:1595", "sys/bus/mmc/mmc.c:1142", "sys/bus/mmc/mmc.c:1147", "sys/bus/mmc/mmc.c:1538", "sys/bus/mmc/mmc.c:1669", "sys/bus/mmc/mmc.c:1949", "sys/dev/disk/mmcsd/mmcsd.c:407", "sys/dev/disk/mmcsd/mmcsd.c:408", "sys/dev/disk/mmcsd/mmcsd.c:409", "sys/dev/disk/mmcsd/mmcsd.c:411", "sys/vfs/devfs/devfs_vnops.c:2028", "sys/kern/subr_disk.c:701" ], "poc_changes": "The findings PoC sketch (direct syscall/ioctl trigger) was not viable: DragonFly has no DIOCGDELETE userland ioctl for this path. Replaced with the family-standard KLD harness: production sys/bus/mmc/mmc.c compiled verbatim into the module (SYSCTL/DRIVER_MODULE neutered with macro save/restore), registered on a synthetic mmcbr bridge implementing a JEDEC-conformant fake card (state machine: CMD2 stops answering after RCA assignment so production discovery terminates; SD probe fails -> mode_mmc; CSD with WRITE_BL_LEN bits=0, spec_vers=5, EXT_CSD SEC_CNT=4096/ERASE_GRP_SIZE=0). The trigger mirrors devfs_spec_freeblks() line-for-line (getpbuf + BUF_CMD_FREEBLKS + dev_dstrategy on the mmcsd0 cdev). Also fixed: module-init ordering (dfhbr devclass must exist before device_add_child -> deferred to a kthread), -Werror macro redefinitions, duplicate case labels (CMD3/SD_CMD3), and R1 status construction.", "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": 1560, "guest_dirty": 0, "build_cmd": "cd /tmp/df5768 && SYSDIR=/usr/src/sys make", "run_cmd": "kldload /tmp/df5768/df5768.ko # expect: Fatal trap 18 integer divide fault, guest down", "code_hash": "de7a085076ecf9b02e206e81fd702ebda7a9a6a5eb79d1829468c35fdd91b054", "notes": "Baseline panicked identically on 2 runs. Fix kernel #1: Sun Oct 11 18:02:40 UTC 2026 (nativekernel X86_64_GENERIC, both DF-5768+DF-5769 hunks applied to the guest /usr/src copy only; sys/ tree untouched on the host). Guest reset to the clean with-src snapshot after the runs. SD path equally affected (mmc.c:1476-1477); PoC exercised the MMC path. Card must also keep EXT_CSD[224]=0 / au_size=0 for the zero to survive the overrides - trivial for the card attacker. Raw mmcsd nodes are root:operator 0640 (subr_disk.c:701); the realistic trigger is filesystem-level TRIM on the mounted malicious card.", "recommended_fix": "Clamp the card-derived erase sector at both CSD-derivation sites: ivar->erase_sector = MAX(ivar->csd.erase_sector * ivar->csd.write_bl_len / MMC_SECTOR_SIZE, 1); (SD mmc.c:1476-1477 and MMC mmc.c:1595-1596) - see fix.diff, validated end-to-end.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Patched kernel (kernel #1 Sun Oct 11 18:02:40 UTC 2026, guest /usr/src only) with MAX(...,1) at mmc.c:1478/1597: identical PoC prints ivar->erase_sector=1, the FREEBLKS bio completes through the normal production erase path, divide-by-zero did NOT occur, kldunload rc=0, guest stays up. Baseline panic eliminated.", "fix_evidence": "fix.log (full patched-run console output incl. erase_sector=1, biowait return, clean kldunload); fix.diff (git apply --check clean against sys/)" } |