Event element-count walks (SAS topo NumEntries, IR NumElements) never validated against the event frame length (mps twin of DF-3919)
Summary
The event payload is copied to kmalloc(EventDataLength*4), but the topo walk iterates PHY[0..NumEntries-1] and the IR walk ConfigElement[0..NumElements-1] using the count fields, with nothing relating them to the copied length. A firmware frame whose count exceeds its length over-reads up to ~1KB/~2KB past the kmalloc'd buffer; garbage handles/reasons then drive the missing-count/update paths. No direct copyout traced. Amplifier: sz = EventDataLength*4 is a u16 computation that wraps to 0 at 0x4000. Producer-side widen+floor fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4089 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.1 KB | β raw | |
| VERDICT.md | β | 2.4 KB | β raw | |
| env.txt | β | 1.4 KB | view raw | |
| fix.diff | β | 1.7 KB | view raw | |
| manifest.json | β | 606 B | view raw | |
| verdict.json | β | 2.4 KB | view raw |
DF-4089 β Event element-count walks never validated against the event frame length (SAS topo NumEntries, IR NumElements)
File: sys/dev/raid/mps/mps_mapping.c (topo walk :2043-2063; IR walks
:2155-2187 and :2185-2260; _mapping_check_update_ir_mt_idx:2105-2121)
Class: CWE-125 out-of-bounds read of the kmalloc'd event frame (mps twin
of mpr sibling finding DF-3919)
Severity: Low β pass 2 (GLM 5.3), bucket kernleak
Confidence: likely
The bug
mpssas_evt_handler (mps_sas_lsi.c:141-145) copies the event payload into
a kernel buffer sized only by the reply's EventDataLength*4 and the
deferred task then hands the raw pointer to the mapping engine. The mapping
engine walks the variable arrays by their independent count fields with
no cross-check:
/* mps_mapping.c:2043-2063 */
num_entries = event_data->NumEntries; /* U8, frame offset 0x08 */
...
for (i = 0; i < num_entries; i++, event_phy_change++, phy_change++)
phy_change->dev_handle = le16toh(event_phy_change->AttachedDevHandle);
/* PHY[i] starts at frame offset 0x0C, 4 bytes each */
/* mps_mapping.c:2161 / 2187 β same pattern */
for (i = 0; i < event_data->NumElements; i++, element++) /* U8 count */
... element->ElementFlags / ReasonCode / VolDevHandle ... /* 8B each */
_mapping_check_update_ir_mt_idx:2105-2107 re-walks
&event_data->ConfigElement[i] the same way. Nothing relates
NumEntries/NumElements to EventDataLength; a (buggy or malicious)
firmware frame whose count exceeds the copied length makes these loops read
up to ~1 KB (topo: 255Γ4 B) / ~2 KB (IR: 255Γ8 B) past the kmalloc'd
buffer β uninitialised/adjacent kernel heap contents, M_ZERO-allocated, so
typically neighbouring allocation data.
The garbage read is then used: dev_handle/reason values feed
_mapping_update_missing_count, _mapping_get_dev_info,
_mapping_add_new_device (handles β removal table, config-page fetches,
slot math) and mpssas_fw_work's own walk (mps_sas_lsi.c:205-237, same
unvalidated count) β mapping corruption / wrong-device addressing on
firmware-controlled garbage. No direct copyout of the over-read bytes to
userspace was traced (kernleak bucket is conservative: disclosure would need
a second sink).
Related latent amplifier in the same producer (noted, out of this file):
sz = le16toh(event->EventDataLength) * 4 is itself a u16 computation
(mps_sas_lsi.c:126,141) that wraps to 0 at EventDataLength == 0x4000.
Fix
fix.diff (against mps_sas_lsi.c β the producer is the right place since
the consumers never see the frame length): widen sz to u32, floor the
allocation at the maximum walk extent implied by the event's own count
fields (topo: offsetof(PHY) + NumEntries*4; IR:
offsetof(ConfigElement) + NumElements*8), and bcopy only the genuine
EventDataLength*4 bytes. Consumers then always read within the allocation
(zero-fill makes the surplus benign: dev_handle == 0 entries are skipped).
Compile-validated with the batch (-Werror, mps.ko linked β
fix_build.log in DF-4085's pack).
DF-4089 VERDICT
Status: untested β hardware-gated (no mps HBA on the audit guest,
env.txt); Low-severity OOB-read class, PoC skipped per batch precedent
(mpr twin DF-3919: untested/likely). Confidence: likely (arithmetic is
certain; the reachability of a length/count mismatch needs a lying IOC,
hence one notch below certain).
- Buffer provenance:
mps_sas_lsi.c:141-145βsz = le16toh(event->EventDataLength) * 4; kmalloc(sz); bcopy(...); the count fields are not part of that computation. - Walk 1 (this file):
mps_mapping.c:2043-2063βNumEntries(U8,mpi2_ioc.h:841) walked overPHY[](4 B/entry from offset 0x0C) β β€ ~1 KB over-read. - Walk 2:
mps_mapping.c:2155-2187and:2187-2260βNumElements(U8,mpi2_ioc.h:701) overConfigElement[](8 B/entry) β β€ ~2 KB over-read;_mapping_check_update_ir_mt_idx:2105-2107re-walks identically. - The
kmalloc(sizeof(_map_phy_change) * num_entries)at:2053andkmalloc(sizeof(u64) * NumElements)at:2155are correctly sized for the local arrays β the over-read is strictly offw_event->event_data. - Consumed values:
phy_change->dev_handle/reasondrive_mapping_update_missing_count:698,_mapping_get_dev_info:901,_mapping_add_new_device:1142+; IR elements drive volume add/remove incl._mapping_get_ir_mt_idx_from_wwidmatches β i.e. garbage feeds the mapping engine's decision inputs (andmpssas_fw_work's parallel walk atmps_sas_lsi.c:205-237). - No copyout of over-read bytes located β conservative bucket
kernleak, severity Low (matches the mpr twin's calibration). - Fix
fix.diff(producer-side,mps_sas_lsi.c): u32sz(also kills theEventDataLength*4u16 wrap), allocation floored at the walk extent,bcopylimited to the true frame length. Compile-validated: applied to guest/usr/srcwith the batch;makeinsys/dev/raid/mpswith-Werrorlinkedmps.ko(BUILD_EXIT=0;fix_build.login the DF-4085 pack)./usr/srcrestored pristine; guest reset with clean-source snapshot.
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_testableApplied to the guest /usr/src together with this batch's other mps fixes (DF-4085..DF-4090); make in sys/dev/raid/mps compiled all sources with -Werror and linked mps.ko (BUILD_EXIT=0). Runtime fix validation not testable: the path requires an mps(4) HBA delivering firmware data, absent on this guest.
findings/poc/DF-4089/fix.diff, findings/poc/DF-4085/fix_build.log
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (walks + buffer provenance), fix.diff (mps_sas_lsi.c), fix_build.log in DF-4085 pack
PoC changes
n/a (untested)
Verified recommended fix
Floor the fw_event->event_data allocation at the maximum walk extent implied by the event's own NumEntries/NumElements (and widen sz to u32) in mpssas_evt_handler.
Verdict
Low - PoC skipped per contract (hardware-gated, mpr twin DF-3919 precedent). The mapping engine walks event arrays by their independent count fields with no frame-length bound: mps_mapping_topology_change_event :2058 walks PHY[NumEntries] (U8, 4B each) and mps_mapping_ir_config_change_event :2161/:2187 plus _mapping_check_update_ir_mt_idx :2105 walk ConfigElement[NumElements] (U8, 8B each), while the backing buffer is kmalloc(EventDataLength4) (mps_sas_lsi.c:141). A firmware frame whose count exceeds its length over-reads up to ~1KB/~2KB past the allocation; read handles/reasons then drive missing-count updates, device adds and removal bookkeeping. Also noted: sz = EventDataLength4 is a u16 computation that wraps to 0 at 0x4000. Fix (producer-side allocation floor + u32) authored; module compile-validated with the batch.
No comments yet.