β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3815

mpr_resize_queues() divide-by-zero when firmware reports IOCMaxChainSegmentSize == 1

Summary

sges_per_frame = IOCMaxChainSegmentSize - 1; only seg==0 is special-cased. A device reporting 1 in IOC Facts (SAS3 always takes this branch since MsgVersion>=2.05) makes the num_chains computation divide by zero -> integer-divide trap -> kernel panic at attach or any reallocating reinit. Malicious/broken PCIe device (same device-trust class as DF-1228, different sink): unauthenticated kernel panic at attach/reinit. Clamp < 2 to the default.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3815 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.0 KB ↓ raw
VERDICT.md β€” 1.6 KB ↓ raw
probe.log β€” 479 B view raw
fix.diff β€” 526 B view raw
manifest.json β€” 764 B view raw
verdict.json β€” 1.6 KB view raw

DF-3815 β€” mpr_resize_queues(): division by zero on IOCMaxChainSegmentSize == 1 (device-controlled panic)

What

mpr_resize_queues() (sys/dev/raid/mpr/mpr.c:364-453):

if (sc->facts->MsgVersion >= MPI2_VERSION_02_05) {           /* mpr.c:397 */
        chain_seg_size = htole16(sc->facts->IOCMaxChainSegmentSize);
        if (chain_seg_size == 0)                              /* mpr.c:399 */
                chain_seg_size = MPR_DEFAULT_CHAIN_SEG_SIZE;  /* = 8 */
        sc->chain_frame_size = chain_seg_size *
            MPR_MAX_CHAIN_ELEMENT_SIZE;                       /* *16 */
} ...
sges_per_frame = sc->chain_frame_size/sizeof(MPI2_IEEE_SGE_SIMPLE64)-1;  /* mpr.c:416 */
...
sc->num_chains = (maxio / PAGE_SIZE + sges_per_frame - 2) /
    sges_per_frame * reqcr;                                   /* mpr.c:431-432 */

sges_per_frame == IOCMaxChainSegmentSize - 1. Only == 0 is special-cased; a firmware reporting IOCMaxChainSegmentSize == 1 yields sges_per_frame == 0 and the division at mpr.c:431 divides by zero β†’ integer-divide trap β†’ kernel panic. (SAS3 firmware always reports β‰₯ 2.05 message version, so the guard is always taken.)

Threat

The value comes straight from the controller's IOC Facts reply (doorbell read in mpr_request_sync(), stored unvalidated into sc->facts). A malicious/broken PCIe device (or corrupted DMA/doorbell) is fully trusted here β€” the same device-trust class as the already-filed DF-1228 (SMID) but a distinct sink. Impact: unauthenticated kernel panic at attach or at any reallocating reinit β†’ local DoS by inserted hardware. No privilege boundary is crossed; severity Low per the "requires specific hardware" rubric row.

Not runnable here

Guest has no SAS3 controller (probe.log identical to DF-3814's); the panic can only be proven on real (or emulated malicious) hardware.

Fix

Clamp chain_seg_size < 2 to the default (see fix.diff).

Env

  • DragonFly dfbsd 6.5-DEVELOPMENT #0 (X86_64_GENERIC), x86_64
VERDICT.md
↓ download raw

DF-3815 β€” VERDICT

status: inconclusive (runtime) β€” arithmetic is certain; no hardware to feed IOCMaxChainSegmentSize==1 on this guest.

Proof of the arithmetic

  • chain_frame_size = IOCMaxChainSegmentSize * 16 (mpr.c:397-402; MPR_MAX_CHAIN_ELEMENT_SIZE = 16, mprvar.h:53).
  • sges_per_frame = chain_frame_size/16 - 1 = IOCMaxChainSegmentSize - 1 (mpr.c:416; sizeof(MPI2_IEEE_SGE_SIMPLE64) == 16).
  • IOCMaxChainSegmentSize == 0 β†’ default 8 (safe); == 1 β†’ sges_per_frame = 0 β†’ (maxio / PAGE_SIZE + 0 - 2) / 0 at mpr.c:431-432 β†’ #DE trap β†’ panic. Note maxio would be (0*MaxChainDepth+1)*PAGE_SIZE = PAGE_SIZE, so the numerator is -1-ish; either way the division faults.
  • Reachability: attach (mpr_iocfacts_allocate β†’ mpr_resize_queues, mpr.c:612) and every reallocating reinit. MsgVersion >= MPI2_VERSION_02_05 holds for all SAS3 firmware, so the branch is always taken on this hardware generation.

Runtime

Driver present (142 pci/mpr), controller absent (probe.log) β†’ cannot feed crafted facts at runtime β†’ inconclusive; fix not testable.

Fix

fix.diff: treat chain_seg_size < 2 as invalid and use MPR_DEFAULT_CHAIN_SEG_SIZE. (Upstream-shared code β€” FreeBSD mpr has the same == 0-only guard.)

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

No SAS3 hardware on guest; baseline/patched kernels cannot be driven through IOC Facts parsing.

fix.diff.
↓ fix.diffper-fix-DF-3815

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md arithmetic proof; fix.diff (< 2 clamp).

PoC changes

No runnable PoC without a device; source-level only.

Verified recommended fix

Clamp IOCMaxChainSegmentSize < 2 to MPR_DEFAULT_CHAIN_SEG_SIZE in mpr_resize_queues().

Verdict

Arithmetic is source-certain: sges_per_frame = IOCMaxChainSegmentSize - 1 (mpr.c:416 via chain_frame_size = seg*16), only ==0 is guarded (mpr.c:399), and mpr.c:431-432 divides by sges_per_frame β€” a device reporting 1 in IOC Facts (trusted doorbell data) panics the kernel with a divide-by-zero trap at attach or reallocating reinit. Runtime inconclusive: driver compiled in but no SAS3 controller on the guest to supply crafted facts (probe.log).