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

hptmv_event_notify uses closed-blob param2 as MV_U8 channel index into sataEvents[8]/VDevices[8] with no bounds check (hardening)

Summary

The MV core callback truncates MV_U32 param2 to MV_U8 and indexes sataEvents[8] and VDevices[8] without a range check; the only caller is the precompiled raid.o blob, so the value's discipline is unverifiable - the guard belongs in the open-source glue. If the closed core ever emits param2 >= 8 or a truncation mismatch, a 4-byte enum write lands mid-struct and VDevices[] is read out of bounds. One-line guard fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3925 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.0 KB ↓ raw
VERDICT.md β€” 1.3 KB ↓ raw
fix.diff β€” 379 B view raw
env.txt β€” 1.2 KB view raw
manifest.json β€” 774 B view raw
verdict.json β€” 1.6 KB view raw

DF-3925 β€” hptmv_event_notify trusts the closed-source MV core's param2 as a channel index with no bounds check

What

sys/dev/raid/hptmv/entry.c:850-901:

static MV_BOOLEAN
hptmv_event_notify(MV_SATA_ADAPTER *pMvSataAdapter, MV_EVENT_TYPE eventType,
                                   MV_U32 param1, MV_U32 param2)
{
    ...
    case MV_EVENT_TYPE_SATA_CABLE:
    {
        MV_U8 channel = param2;                    /* :860 β€” 32->8 truncation, no range check */
        if (param1 == EVENT_CONNECT) {
            pAdapter->sataEvents[channel] = SATA_EVENT_CHANNEL_CONNECTED;   /* :864 */
        ...
        else if (param1 == EVENT_DISCONNECT) {
            pAdapter->sataEvents[channel] = SATA_EVENT_CHANNEL_DISCONNECTED;/* :872 */
            device_change(pAdapter, channel, FALSE);                        /* :875 -> VDevices[channel] */

IAL_ADAPTER_T.sataEvents[MV_SATA_CHANNELS_NUM=8] and .VDevices[MV_SATA_CHANNELS_NUM=8] (osbsd.h:170,176; mvSata.h:83). A param2

= 8 (or a truncation mismatch) writes an enum 4 bytes past sataEvents[] into the middle of the adapter struct, and device_change indexes VDevices[channel] OOB β†’ out-of-struct PVDevice pointer use β†’ memory corruption.

Provenance (why this is only speculative)

hptmv_event_notify is called only by the precompiled MV core library (x86_64-elf.raid.o.uu, linked via Makefile hptmvraid.bo) β€” there is no source for the caller in-tree. The historical FreeBSD mvSata core generates MV_EVENT_TYPE_SATA_CABLE with channelIdx from loops bounded by MV_SATA_CHANNELS_NUM, so on well-behaved core code param2 ∈ [0,7]. But the check belongs in the open-source glue: this is defense-in-depth against a closed blob whose bounds discipline cannot be audited (and whose input is device-controlled MMIO state).

Fix

--- a/sys/dev/raid/hptmv/entry.c
+++ b/sys/dev/raid/hptmv/entry.c
@@ -857,6 +857,9 @@ hptmv_event_notify(...)
        {
            MV_U8   channel = param2;

+           if (channel >= MV_SATA_CHANNELS_NUM)
+               return MV_FALSE;
+           
            if (param1 == EVENT_CONNECT)

(see fix.diff)

VERDICT.md
↓ download raw

DF-3925 VERDICT

status: not_reproduced (hardware-gated, caller unauditable) | impact observed: none | source certainty: speculative

Analysis

  • Sink: entry.c:864/872 write sataEvents[channel] (8-entry array, osbsd.h:170) and entry.c:875 device_change(...VDevices[channel]...) (8-entry array, osbsd.h:176), with channel = (MV_U8)param2 taken from the callback args with no range validation (entry.c:860).
  • Source: the only caller is inside the closed binary blob x86_64-elf.raid.o.uu (mvSata core). No in-tree call site can be checked; the historical open mvSata core bounded channelIdx by MV_SATA_CHANNELS_NUM.
  • Therefore the OOB is reachable only if the blob ever emits param2 >= 8 or a truncation mismatch β€” unverifiable. Filed as a hardening finding (speculative), not a demonstrated bug. Runtime reproduction additionally requires the RR1820 card (absent, env.txt).

Fix validation

Hardware-gated; one-line guard, no behavior change for valid channels.

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

one-line guard authored in fix.diff; cannot validate without hardware

fix.diff in this pack; never applied to sys/
↓ fix.diffper-fix-DF-3925

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md provenance analysis; Makefile links hptmvraid.bo from x86_64-elf.raid.o.uu (closed)

PoC changes

n/a (fresh pack)

Verified recommended fix

Validate channel < MV_SATA_CHANNELS_NUM at the top of the MV_EVENT_TYPE_SATA_CABLE case

Verdict

entry.c:860 truncates the MV core callback's MV_U32 param2 to MV_U8 and uses it unvalidated to index sataEvents[8] (:864/:872) and VDevices[8] (:875). The only caller is the precompiled mvSata core blob (x86_64-elf.raid.o.uu) whose param2 discipline cannot be audited from source; the historical core bounded it to [0,7]. Filed as speculative hardening: add a range guard in the open glue. No runtime test possible (no RR1820 on guest).