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

mps_request_polled has no timeout cancellation: mps_update_events prints a NULL reply (NULL-deref panic) and callers free still-posted commands that late-complete against freed/reallocated cm state

Summary

After 1000x50ms without a reply, mps_request_polled returns ETIMEDOUT with the request still posted. (i) mps_update_events then calls mps_print_event(sc, reply) with reply==NULL (cm_reply zeroed) and the print derefs EventDataLength immediately - NULL-deref panic at attach/event re-registration. (ii) Both polled callers free the still-live command; a late reply hits mps_intr_locked and completes a free-list/REALLOCATED cm: spurious wakeup for the new owner, stale cm_reply recycled - reply-frame double-recycle and command-state corruption. Same trust boundary as DF-1369/4014 but distinct code and shape. NULL-guard + mark-timed-out fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4075 Β· 5 files
FileTypeDescriptionSize
verdict.json β€” 2.4 KB view raw
README.md β€” 297 B ↓ raw
VERDICT.md β€” 2.2 KB ↓ raw
fix.diff β€” 987 B view raw
manifest.json β€” 755 B view raw

DF-4075 β€” mps_request_polled timeout lifecycle: NULL-deref panic + freed-command late completion

File: sys/dev/raid/mps/mps.c:2140-2161 (polled loop), :1720-1727 (NULL print), :592-596 (free on error) Source-level finding; no mps controller on the audit guest (not runnable). See VERDICT.md.

VERDICT.md
↓ download raw

DF-4075 VERDICT β€” source-level certain, chip-gated (no LSI mps HBA on audit guest)

Shape (i): NULL-deref panic

  • mps_update_events():1719 error = mps_request_polled(sc, cm); :1720-1723 set error when reply is NULL / bad status; :1724 then calls mps_print_event(sc, reply) UNCONDITIONALLY.
  • On a polled timeout (mps_request_polled:2150-2157: 1000 iterations x 50 ms = 50 s with no reply), cm->cm_reply is NULL (never assigned; also zeroed by every prior mps_free_command β€” mpsvar.h:520).
  • mps_print_event() (mps_table.c) dereferences event->EventDataLength immediately β‡’ NULL deref β‡’ panic. Reachable at attach (mps_attach_log -> mps_register_events -> mps_update_events) and on every event re-registration, whenever the IOC stays silent ~50 s without faulting.

Shape (ii): timed-out command freed while still posted

  • mps_request_polled() returns ETIMEDOUT with the request still posted to the controller (no cancellation, no MPS_CM_FLAGS_COMPLETE).
  • Callers free immediately: mps_get_portfacts done: (:592-594 frees even on error), mps_update_events (:1727).
  • A late reply then hits mps_intr_locked:1581 cm = &sc->commands[desc->AddressReply.SMID] (SMID valid β€” DF-1369 covers invalid SMIDs): the cm is on the free list or REALLOCATED to a new request β‡’ spurious MPS_CM_FLAGS_COMPLETE / wakeup for the new owner, cm_reply assigned onto a free-list command, and mps_free_command later recycles the stale reply busaddr via mps_free_reply (mpsvar.h:518-519) β‡’ reply-frame double-recycle / command-state corruption.

Distinct from knowns

DF-4014 = mps_user.c ioctl wait path (frees cm + DMA buffer, device DMA writeback). DF-4015 = ioctl command leak. DF-1369 = SMID bounds. This finding is the mps.c core polled path.

Fix

NULL-guard the print; on timeout do not free a still-posted command (complete-with-error or defer reclamation to reinit).

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

Hardware-gated finding (no AMD VegaM GPU / no LSI mps controller on the audit guest): fix authored at source level (fix.diff) but not build/boot-validated.

fix.diff in this pack; authored post-analysis, not hardware-validated.
↓ fix.diffper-fix-DF-4075

Confirmed kernel references

Detail

Evidence (decisive lines)

mps.c:2150-2157 timeout loop leaves cm live; :1720-1724 NULL print; :592-596 and :1727 free-on-timeout; mpsvar.h:514-540 free semantics

PoC changes

n/a (source-level; no mps controller on the guest)

Verified recommended fix

NULL-guard the mps_print_event call; on polled timeout do not let callers free a still-posted command (complete it with error / mark and reclaim on reinit).

Verdict

Source-level certain, chip-gated (needs an mps-attached LSI IOC that stops answering). mps_request_polled() has no timeout cancellation: after 1000x50ms it returns ETIMEDOUT while the command is still posted to the controller. (i) mps_update_events():1719-1724 then calls mps_print_event(sc, reply) with reply == cm->cm_reply == NULL (mps_free_command zeroes it, mpsvar.h:520) and mps_print_event() dereferences event->EventDataLength unconditionally (mps_table.c) => NULL-deref panic. (ii) Both polled callers free the still-posted command on the error path (mps_get_portfacts:592-594 'done:', mps_update_events:1727); a late reply then lands on a freed/reallocated cm via mps_intr_locked:1581 -> spurious completion/wakeup of the new owner and a stale reply busaddr recycled by mps_free_reply (mpsvar.h:518-519) => reply-frame double-recycle and command-state corruption. Distinct from DF-4014 (user-ioctl wait path / DMA-buffer UAF) and DF-1369 (SMID validation).