Device-controlled sc->port_id indexes rxf.port[2]/port[4] unchecked in copy_stats_to_sc_be2/_be3 β OOB read past the stats DMA buffer into world-readable sysctls
Summary
port_id is a raw firmware dword stored unvalidated and used as the port[] array index against 2/4-entry arrays. ~66 dwords per refresh are copied into hw.oceN.stats.* sysctls readable by any user; port_id beyond the struct runs off the exactly-sized stats DMA allocation into adjacent heap. Exact-geometry replica (port_id 4/5 already past both array and buffer). NELEM-clamp fix (2 hunks).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4387 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.3 KB | β raw | |
| VERDICT.md | β | 2.4 KB | β raw | |
| trigger.c | β | 1.5 KB | view raw | |
| build.sh | β | 65 B | view raw | |
| run.sh | β | 89 B | view raw | |
| run.log | β | 654 B | view raw | |
| fix.diff | β | 881 B | view raw | |
| env.txt | β | 478 B | view raw | |
| manifest.json | β | 793 B | view raw | |
| verdict.json | β | 2.0 KB | view raw |
DF-4387 β Device-controlled sc->port_id indexes rxf.port[] unchecked in the BE2/BE3 stats copy (OOB read β world-readable sysctls)
sys/dev/netif/oce/oce_mbox.c:493βsc->port_id = fwcmd->params.rsp.port_id;(raw firmwareuint32_t, never range-checked)sys/dev/netif/oce/oce_sysctl.c:1146,1151βcopy_stats_to_sc_be2:port_stats = &nic_mbx->params.rsp.stats.rxf.port[port];whereport[2](oce_hw.h:2943)sys/dev/netif/oce/oce_sysctl.c:1211,1216βcopy_stats_to_sc_be3: same,port[4](oce_hw.h:3004)
~66 dwords are then copied from port_stats into sc->oce_stats_info, whose
fields are published world-readable under hw.oceN.stats.rx.* /
hw.oceN.stats.tx.*. port_id β₯ 2 (BE2) / β₯ 4 (BE3) reads past the port
array; a large enough port_id reads past the entire
mbx_get_nic_stats{,_v0} DMA allocation (oce_stats_init sizes stats_mem
to exactly the struct) into adjacent kernel heap β and those bytes land in
world-readable sysctls.
Build / run
./run.sh β hardware gate + replica printing the read offsets for
port_id 0..5 against the real array/struct geometry.
Expected
port_id=4: read at rxf+1056 [PAST port[] ARRAY] [PAST mbx_get_nic_stats
DMA BUFFER]-style lines. Kernel confirmation needs an Emulex adapter whose
QUERY_FW_CONFIG returns port_id β₯ array bound.
DF-4387 VERDICT
Trace
oce_mbox_get_fw_config()stores the raw firmware dword:sc->port_id = fwcmd->params.rsp.port_id(sys/dev/netif/oce/oce_mbox.c:493) β no mask, no range check anywhere in the driver (other uses pass it back to the device at oce_mbox.c:1063,1879).oce_refresh_nic_stats()βcopy_stats_to_sc_be2()/copy_stats_to_sc_be3()index with it: -sys/dev/netif/oce/oce_sysctl.c:1151βrxf.port[port],oce_rxf_stats_v0.port[2](oce_hw.h:2943) -sys/dev/netif/oce/oce_sysctl.c:1216βrxf.port[port],oce_rxf_stats_v1.port[4](oce_hw.h:3004) Both then copy ~26 port fields plusrxf_stats/pmemfields intosc->oce_stats_info.u0.be.- Exposure: every copied field is a
CTLFLAG_RDsysctl node created at attach (oce_add_stats_sysctls_be3, oce_sysctl.c:538-752) β readable by any user (sysctl hw.oceN.stats.rx.err.crc_errsetc.). - Allocation:
oce_stats_init()(oce_sysctl.c:1268-1284) sizes the DMA block to exactlysizeof(struct mbx_get_nic_stats_v0)/sizeof(struct mbx_get_nic_stats), so the OOB read runs off the end of theoce_dma_allocmapping into adjacent kernel heap onceport_idoutruns the struct (replica arithmetic inrun.log: port_id 4 already clears both the port[] array and the rxf-sized buffer model).
Threat model: device-controlled index (malicious/compromised NIC or DMA), unprivileged reader harvests the leaked dwords. Missing-validation class, same family as the DF-1576 device-trust pattern.
Verification (Phase V)
Kernel path hardware-gated (no 19a2: device, no hw.oce tree β run.log).
Demonstrated: exact-geometry replica (trigger.c, guest run in run.log)
showing which port_id values cross which boundary. fix.diff clamps
port_id to NELEM(rxf.port) at both copy functions; git apply --check
OK; patched file syntax-validated on the guest kernel toolchain (0 errors).
Bottom line
Certain code-wise, device-gated exploit-wise: Low. Two-line defense-in-depth clamp; worth fixing alongside any broader "validate fw_config" hardening.
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_testablenot_testable at runtime (no oce NIC); fix.diff (2 hunks) git-apply OK, patched oce_sysctl.c syntax-validated with guest kernel toolchain (0 errors).
fix.diff; guest cc -fsyntax-only of patched oce_sysctl.c: 0 errors.
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (guest replica: port_id=4/5 past array+buffer), VERDICT.md trace, fix.diff (git apply --check OK, syntax-validated)
PoC changes
n/a - pass-2 finding authored fresh (added stddef.h after first guest compile)
Verified recommended fix
clamp port to NELEM(rxf.port) in copy_stats_to_sc_be2/_be3 (fix.diff)
Verdict
sc->port_id is a raw firmware dword (oce_mbox.c:493, unvalidated) used to index rxf.port[2] (be2, oce_sysctl.c:1151) / rxf.port[4] (be3, oce_sysctl.c:1216); the ~66 dwords read per refresh land in world-readable hw.oceN.stats.* sysctls. port_id beyond the array reads wrong/past fields, and beyond the struct runs off the stats_mem DMA allocation (sized to exactly the mbx struct) into adjacent kernel heap -> unprivileged-readable device-shaped leak. Hardware-gated on guest; replica arithmetic in run.log. Low (malicious-peripheral class); fix is a NELEM clamp at both sites.
No comments yet.