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)
PoC verification
Evidence pack
findings/poc/DF-3815 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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
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) / 0at mpr.c:431-432 β #DE trap β panic. Notemaxiowould 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_05holds 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_testableNo SAS3 hardware on guest; baseline/patched kernels cannot be driven through IOC Facts parsing.
fix.diff.
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).
No comments yet.