mps_sas_lsi: PhysDisk OFFLINE branch uses |= instead of &= ~ to clear RAID_COMPONENT flag, locking disk out of OS permanently
| Field | Value |
|---|---|
| ID | DF-1698 |
| File | sys/dev/raid/mps/mps_sas_lsi.c |
| Lines | 481 |
| Severity | Info |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N |
| CWE | CWE-480 Use of Incorrect Operator; CWE-697 Incorrect Comparison |
| Confidence | certain |
| Status | new |
| CVE match | dfly_specific |
| Created | 2026-07-18 |
Summary
In mpssas_fw_work's MPI2_EVENT_IR_PHYSICAL_DISK case, the
OFFLINE/NOT_CONFIGURED/NOT_COMPATIBLE/default sub-branch of the
inner NewValue switch attempts to clear MPS_TARGET_FLAGS_RAID_COMPONENT
from targ->flags but writes:
targ->flags |= ~MPS_TARGET_FLAGS_RAID_COMPONENT;
instead of:
targ->flags &= ~MPS_TARGET_FLAGS_RAID_COMPONENT;
The OR-with-complement sets every bit of targ->flags (including
RAID_COMPONENT itself), so the disk is never re-exposed to the OS even
after it transitions out of the RAID component state.
The opposite branch at line 468 (targ->flags |=
MPS_TARGET_FLAGS_RAID_COMPONENT) and the matching mpr_sas_lsi.c sibling
both use |= correctly when setting the flag; only the clearing site here
is wrong.
Root cause
mps_sas_lsi.c:481:
targ->flags |= ~MPS_TARGET_FLAGS_RAID_COMPONENT;
inside the inner switch (event_data->NewValue) { case
MPI2_RAID_PD_STATE_OFFLINE: case MPI2_RAID_PD_STATE_NOT_CONFIGURED: case
MPI2_RAID_PD_STATE_NOT_COMPATIBLE: default: ... } of the
MPI2_EVENT_IR_PHYSICAL_DISK handler.
The author intended to clear the RAID_COMPONENT bit (exposing the disk
to the OS once it leaves the volume) but used bitwise-OR with the
complement, which sets bits 0..3, 6..31 (and notably also bit 4 =
RAID_COMPONENT, since ~(1<<4) | (1<<4) == 0xFFFFFFFF).
The result: targ->flags is set to all-ones and stays that way.
Downstream at mps_sas.c:1579:
if (targ->flags & MPS_TARGET_FLAGS_RAID_COMPONENT)
return CAM_TID_INVALID;
returns CAM_TID_INVALID for every subsequent SCSI IO to that target β
the disk is permanently invisible to the OS after one
PhysDisk-state-offline event, until reattachment.
Contrast line 331 (mpssas_rescan_target path) and line 468 (the
ONLINE/DEGRADED/REBUILDING/OPTIMAL/HOT_SPARE path) which
correctly use |= to set the bit.
Threat model
No direct security impact.
A malicious/compromised SAS HBA that flips a physical disk to
OFFLINE/NOT_CONFIGURED/NOT_COMPATIBLE state causes the OS to
permanently reject IO to that target (availability-style denial of the
disk to userland, but the disk was just declared offline by firmware
anyway, so the practical impact is small).
No memory-safety consequence; targ->flags is a plain u32 in a fixed
struct slot, so writing 0xFFFFFFFF is in-bounds.
Pure logic bug β defence-in-depth only.
PoC
Not exploitable beyond the logic bug itself. Trigger: a SAS HBA (real or
emulated) emits an MPI2_EVENT_IR_PHYSICAL_DISK event with
ReasonCode=MPI2_EVENT_IR_PHYSDISK_RC_STATE_CHANGED and
NewValue=MPI2_RAID_PD_STATE_OFFLINE (or NOT_CONFIGURED /
NOT_COMPATIBLE) for a handle whose target is currently marked as a RAID
component.
The target's flags become 0xFFFFFFFF; subsequent SCSI IO via
mps_sas_action (mps_sas.c:1579) returns CAM_TID_INVALID
indefinitely.
No memory corruption; no privilege gain; no info leak.
Recommended fix
Use the correct operator to clear the bit:
--- a/sys/dev/raid/mps/mps_sas_lsi.c
+++ b/sys/dev/raid/mps/mps_sas_lsi.c
@@ -478,7 +478,7 @@
targ = mpssas_find_target_by_handle(sassc, 0,
event_data->PhysDiskDevHandle);
if (targ) {
- targ->flags |= ~MPS_TARGET_FLAGS_RAID_COMPONENT;
+ targ->flags &= ~MPS_TARGET_FLAGS_RAID_COMPONENT;
kprintf("%s %d: Found Target for handle 0x%x. \n",
__func__, __LINE__,
event_data->PhysDiskDevHandle);
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1698 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | source-only confirmation + mechanism + fix | 1.5 KB | β raw |
| fix.diff | suggested-fix | Replace |= ~FLAG with &= ~FLAG. | 512 B | view raw |
| ../fix_build_new.log | build-log | Batch kernel build with new fixes (rc=0, -Werror) | 5.6 MB | β download |
DF-1698 β PoC Verification Verdict
Category: raid (IN GENERIC)
Source: sys/dev/raid/mps/mps_sas_lsi.c:481
Guest: 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 (X86_64_GENERIC, INVARIANTS ON, no SMAP/SMEP/KASLR)
Date verified: 2026-07-25
Verdict: REPRODUCED (source-only confirmation; GENERIC-compiled, no HW)
Mechanism
PhysDisk OFFLINE branch: targ->flags |= ~MPS_TARGET_FLAGS_RAID_COMPONENT. Should be &= ~FLAG. The |= sets ALL bits except RAID_COMPONENT, corrupting the target's flags word (sets STALK/FOUND/WILD etc).
In GENERIC kernel build: YES (file compiled by X86_64_GENERIC)
Reproduction status
This finding is GENERIC-compiled but trigger requires specific runtime state: the vulnerable code path requires specific runtime state (specific device probe, RAID config, sysctl, or process context) not reproducible from the unprivileged audit guest. The QEMU guest has no GPU passthrough, no physical NIC/RAID HW, and these modules are not exercised. The bug is therefore confirmed by source-level trace of the cited path:line data flow rather than by a runtime PoC. The cited code, guards (or lack thereof), and types were verified against the audited sys/ tree.
Fix
Replace |= ~FLAG with &= ~FLAG.
See fix.diff for the standalone git-apply-able unified diff. Validated by applying the 38 new-finding batch diffs (including this one) and building a single X86_64_GENERIC kernel (rc=0, -Werror clean).
Fix verification
fixedVALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.
VALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
REPRODUCED (source-only): mps PhysDisk OFFLINE branch: targ->flags |= ~MPS_TARGET_FLAGS_RAID_COMPONENT (should be &= ~FLAG); sets ALL bits except RAID_COMPONENT, corrupting target flags word.
Verified recommended fix
REPRODUCED (source-only): mps PhysDisk OFFLINE branch: targ->flags |= ~MPS_TARGET_FLAGS_RAID_COMPONENT (should be &= ~FLAG); sets ALL bits except RAID_COMPONENT, corrupting target flags word.
Verdict
REPRODUCED (source-only): mps PhysDisk OFFLINE branch: targ->flags |= ~MPS_TARGET_FLAGS_RAID_COMPONENT (should be &= ~FLAG); sets ALL bits except RAID_COMPONENT, corrupting target flags word.
No comments yet.