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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4089 Β· 6 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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 over PHY[] (4 B/entry from offset 0x0C) β†’ ≀ ~1 KB over-read.
  • Walk 2: mps_mapping.c:2155-2187 and :2187-2260 β€” NumElements (U8, mpi2_ioc.h:701) over ConfigElement[] (8 B/entry) β†’ ≀ ~2 KB over-read; _mapping_check_update_ir_mt_idx:2105-2107 re-walks identically.
  • The kmalloc(sizeof(_map_phy_change) * num_entries) at :2053 and kmalloc(sizeof(u64) * NumElements) at :2155 are correctly sized for the local arrays β€” the over-read is strictly of fw_event->event_data.
  • Consumed values: phy_change->dev_handle/reason drive _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_wwid matches β€” i.e. garbage feeds the mapping engine's decision inputs (and mpssas_fw_work's parallel walk at mps_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): u32 sz (also kills the EventDataLength*4 u16 wrap), allocation floored at the walk extent, bcopy limited to the true frame length. Compile-validated: applied to guest /usr/src with the batch; make in sys/dev/raid/mps with -Werror linked mps.ko (BUILD_EXIT=0; fix_build.log in the DF-4085 pack). /usr/src restored 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_testable
baseline no→ patch + rebuild →patched clean

Applied 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
↓ fix.diffper-fix-DF-4089

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.