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

XPT_RESET_DEV allocates from the normal command pool but frees via the high-priority list, and underflows tm_count: both pools' accounting corrupted, TM simq-freeze discipline permanently disabled (DF-1223 twin)

Summary

mpssas_action_resetdev allocates its TM with mps_alloc_command (pops the normal req_list) but the completion frees it via mpssas_free_tm, which executes --tm_count (never incremented on this path; 0 -> 0xFFFFFFFF) and mps_free_high_priority_command (pushes the hp list). Each XPT_RESET_DEV permanently leaks one SMID from the normal pool, injects a normal-pool SMID into the hp pool (seeded at init only with SMIDs <= HighPriorityCredit), and poisons the refcount so the alloc_tm 0->1 xpt_freeze_simq transition never fires again - breaking the driver's own 'stop sending commands during task management' invariant. Repetition exhausts the normal pool -> adapter-wide CAM_RESRC_UNAVAIL. Symmetric-alloc fix + memset.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3894 Β· 8 files
FileTypeDescriptionSize
README.md β€” 3.5 KB ↓ raw
VERDICT.md β€” 3.4 KB ↓ raw
run.sh β€” 111 B view raw
run.log β€” 482 B view raw
env.txt β€” 274 B view raw
fix.diff β€” 1.2 KB view raw
verdict.json β€” 2.8 KB view raw
manifest.json β€” 710 B view raw

DF-3894 β€” XPT_RESET_DEV allocates a regular-pool command but frees it via the high-priority list, and underflows tm_count: both command pools corrupted, TM simq-freeze discipline permanently disabled

File: sys/dev/raid/mps/mps_sas.c (pass 2). Twin of DF-1223 (filed against mpr_sas.c; the mpt-era original of the same pattern).

What

mpssas_action_resetdev allocates its task-management command from the normal pool:

        tm = mps_alloc_command(sc);            /* mps_sas.c:2765 β€” pops sc->req_list */

but the completion handler frees it through the TM helper, which pushes onto the high-priority pool and decrements the TM refcount that this command never incremented:

 bailout:
        mpssas_free_tm(sc, tm);                /* mps_sas.c:2832 */
 mpssas_free_tm(...):
        if (--sc->sassc->tm_count == 0) {      /* mps_sas.c:262 β€” 0 -> UINT_MAX */
                xpt_release_simq(...);
        }
        mps_free_high_priority_command(sc, tm);/* mps_sas.c:269 β€” pushes high_priority_req_list */

Pools (sys/dev/raid/mps/mpsvar.h): - mps_alloc_command pops sc->req_list (mpsvar.h:543-555) - mps_free_high_priority_command inserts into sc->high_priority_req_list (mpsvar.h:558-577) - At init, SMIDs <= facts->HighPriorityCredit are seeded onto the hp list and the rest onto the normal list (mps.c:864-867).

tm_count is u_int (sys/dev/raid/mps/mps_sas.h:100); XPT_RESET_DEV never calls mpssas_alloc_tm (which is what increments it, mps_sas.c:242), so the decrement in mpssas_free_tm underflows 0 β†’ 0xFFFFFFFF.

Impact (three coupled effects, all persistent)

  1. Pool accounting corruption: the normal req_list permanently loses one command per XPT_RESET_DEV (SMID leak), and high_priority_req_list gains an SMID above the firmware's HighPriorityCredit budget. Repeated device resets (normal CAM error recovery on a flaky disk) drain the normal pool until mps_alloc_command returns NULL β†’ all I/O on the adapter fails (CAM_RESRC_UNAVAIL storm). A later mpssas_alloc_tm can hand out the mis-pooled SMID, sending a high-priority request whose SMID is outside the high-priority credit range β€” undefined firmware behavior.
  2. tm_count poisoned forever: after the underflow, tm_count wraps through UINT_MAX; subsequent mpssas_alloc_tm no longer sees the 0β†’1 transition (if (sc->sassc->tm_count++ == 0), mps_sas.c:242), so the simq is never frozen while task management is in flight β€” violating the exact invariant the refcount exists for ("LSI's firmware requires us to stop sending commands when we're doing task management", mps_sas.c:231-234) β€” and mpssas_free_tm never releases it either (balance is preserved only by both sides being broken).
  3. The underflowed refcount also breaks any future legitimate freeze accounting after a diag reset cleanup walks the tm list.

Trigger: XPT_RESET_DEV reaching the SIM β€” issued by CAM periph error recovery (da(4) escalating timeouts) or camcontrol reset <dev> (root). No special race required; every single occurrence corrupts the pools.

Build/run

run.sh records the hardware gate. Source-level certainty: total.

Fix

Allocate the TM with mpssas_alloc_tm() (balanced against mpssas_free_tm, freezes the simq per the driver's own TM discipline), and zero the reused request frame while at it (mpssas_action_resetdev is the only TM sender that does not memset its req β€” mps_sas.c:512-513, 579 contrast). See fix.diff.

VERDICT.md
↓ download raw

DF-3894 β€” VERDICT

Status: not_reproduced (hardware-gated) β€” defect certain from source. Impact class (on real hardware): persistent driver-state corruption β†’ adapter-wide I/O failure / firmware-contract violation (DoS).

Proof chain

  1. sys/dev/raid/mps/mps_sas.c:2765 β€” tm = mps_alloc_command(sc); mps_alloc_command pops from sc->req_list (sys/dev/raid/mps/mpsvar.h:543-555).
  2. sys/dev/raid/mps/mps_sas.c:2830-2833 β€” every exit path of mpssas_resetdev_complete calls mpssas_free_tm(sc, tm).
  3. sys/dev/raid/mps/mps_sas.c:252-270 β€” mpssas_free_tm executes --sc->sassc->tm_count (line 262) and mps_free_high_priority_command(sc, tm) (line 269), which inserts onto sc->high_priority_req_list (mpsvar.h:576).
  4. Nothing on the XPT_RESET_DEV path ever incremented tm_count (only mpssas_alloc_tm does, mps_sas.c:242) nor removed the command from req_list's counterpart. So per occurrence: - req_list shrinks by one, permanently (normal-pool SMID leak); - high_priority_req_list grows by one, containing an SMID seeded to the normal list at init (sys/dev/raid/mps/mps.c:864-867 puts only SMIDs <= facts->HighPriorityCredit on the hp list); - tm_count (u_int, sys/dev/raid/mps/mps_sas.h:100) underflows 0 β†’ 0xFFFFFFFF.
  5. Poisoned-refcount consequence: the next mpssas_alloc_tm evaluates sc->sassc->tm_count++ == 0 against UINT_MAX (false) β†’ no xpt_freeze_simq while a TM is in flight, breaking the invariant documented at mps_sas.c:231-234 ("LSI's firmware requires us to stop sending commands when we're doing task management"); the matching mpssas_free_tm then decrements 0 β†’ UINT_MAX again. From the first XPT_RESET_DEV onward, TM/scsi-io mutual exclusion never holds again.
  6. Reachability: mpssas_action case XPT_RESET_DEV (mps_sas.c:981-984) β€” CAM error recovery (periph timeouts escalating to device reset) or camcontrol reset. Same defect shape as DF-1223 (mpr_sas.c:3351-3425), which was accepted with the alloc/free pool mismatch as root cause.

Why not reproduced on this guest

No SAS2 HBA (env.txt, run.log); the SIM never attaches so XPT_RESET_DEV cannot be routed to mpssas_action. On hardware, the divergence is observable in sysctl kern.ipc-style driver counters after one camcontrol reset: normal command pool count drops, hp pool count rises, tm_count (viewable with MPS_TRACE logging, mps_sas.c:246-247/266-267) prints 4294967295.

Exploitability

No direct memory-safety primitive (the lists themselves stay consistent β€” the corruption is accounting), but the end state is adapter-wide I/O denial (pool exhaustion β†’ CAM_RESRC_UNAVAIL for every subsequent command) plus firmware-contract violation during task management. DoS class.

Fix validation

Not testable on this guest. fix.diff swaps the allocation to mpssas_alloc_tm (making alloc/free symmetric and the tm_count/freeze discipline correct) and zeroes the request frame. This mirrors the mpr DF-1223 fix direction.

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

fix.diff restores pool/refcount symmetry; cannot be exercised on this guest (no SAS2 HBA).

findings/poc/DF-3894/fix.diff
↓ fix.diffper-fix-DF-3894

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (path:line proof of both pool mismatch and tm_count underflow), env.txt, run.sh + run.log (hardware gate), fix.diff (mpssas_alloc_tm + memset req)

PoC changes

Trigger is any XPT_RESET_DEV on real mps hardware; run.sh records the hardware gate.

Verified recommended fix

Allocate the XPT_RESET_DEV TM with mpssas_alloc_tm() so alloc/free pools and tm_count/freeze accounting balance; memset the request frame.

Verdict

mpssas_action_resetdev allocates its XPT_RESET_DEV task-management command from the normal pool (mps_alloc_command pops sc->req_list, mps_sas.c:2765, mpsvar.h:543-555) but mpssas_resetdev_complete frees it via mpssas_free_tm (mps_sas.c:2832), which pushes it onto sc->high_priority_req_list (mpsvar.h:576) and decrements sc->sassc->tm_count (mps_sas.c:262) that this path never incremented β€” tm_count (u_int, mps_sas.h:100) underflows 0β†’0xFFFFFFFF, permanently disabling the 0β†’1 xpt_freeze_simq transition in mpssas_alloc_tm (mps_sas.c:242) and violating the documented 'stop sending commands during task management' invariant (mps_sas.c:231-234). Each XPT_RESET_DEV (CAM error recovery or camcontrol reset) permanently leaks one SMID from the normal pool and injects a normal-pool SMID into the high-priority pool (seeded at init only with SMIDs <= facts->HighPriorityCredit, mps.c:864-867); repetition exhausts the normal pool (adapter-wide CAM_RESRC_UNAVAIL) and can hand a TM an out-of-credit SMID. Exact twin of DF-1223 (mpr_sas.c). Not reproduced: no SAS2 HBA in the guest, SIM never attaches; mismatch is certain from source.