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

iwm_handle_rxb: PAGE_SIZE sc_cmd_resp copy from possibly-offset response packet reads up to ~4KB past the 4096-byte RX cluster; iwm_alive_fn v3 fallback reads fixed struct without payload-length check

Summary

On NVM_ACCESS/MCC_UPDATE command responses, iwm_handle_rxb copies sizeof(sc->cmd_resp)==PAGE_SIZE==4096 bytes from pkt regardless of packet length or offset; HAVEROOM() permits packet offsets up to 4087 inside the 4096-byte RX cluster, and multi-notification buffers are normal firmware behavior, so the copy spans up to offset bytes past the mbuf cluster end. This buffer becomes resp_pkt for every synchronous command and is parsed by NVM/MCC consumers. Sibling sink: iwm_alive_fn's v3 fallback reads fixed struct offsets from the raw notification packet with no payload-length check. Distinct from DF-1145 (MPDU lengths/phy/stats sinks). Firmware-controlled RX buffer layout: heap OOB read of up to ~4KB past a 4KB cluster into driver state consumed for NVM/regulatory parsing and a console-printed MAC. Fix: clamp copy to actual packet length + validate ALIVE payload.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3630 Β· 6 files
FileTypeDescriptionSize
README.md β€” 3.2 KB ↓ raw
VERDICT.md β€” 2.7 KB ↓ raw
env.txt β€” 740 B view raw
fix.diff β€” 905 B view raw
manifest.json β€” 966 B view raw
verdict.json β€” 2.7 KB view raw

DF-3630 β€” iwm_handle_rxb: unconditional PAGE_SIZE copy of command responses reads past the 4KB RX cluster

Where

sys/dev/netif/iwm/if_iwm.c:5509-5515 (NVM_ACCESS/MCC_UPDATE response path) and :5572-5577 (long-command response path) inside iwm_handle_rxb(); consumer iwm_send_cmd()/iwm_free_resp() at sys/dev/netif/iwm/if_iwm_util.c:327,369.

What

uint32_t maxoff = IWM_RBUF_SIZE;                 /* 4096 (if_iwmvar.h:290) */
#define HAVEROOM(a) ((a) + sizeof(uint32_t) + sizeof(struct iwm_cmd_header) < maxoff)
while (HAVEROOM(offset)) {                       /* offset can be up to 4087 */
        struct iwm_rx_packet *pkt = mtodoff(m, ..., offset);
        ...
        case IWM_NVM_ACCESS_CMD:
        case IWM_MCC_UPDATE_CMD:
                if (sc->sc_wantresp == (((qid & ~0x80) << 16) | idx)) {
                        memcpy(sc->sc_cmd_resp,
                            pkt, sizeof(sc->sc_cmd_resp));   /* PAGE_SIZE == 4096 ! */
                }

sc_cmd_resp is uint8_t sc_cmd_resp[IWM_CMD_RESP_MAX] with IWM_CMD_RESP_MAX = PAGE_SIZE (if_iwmvar.h:312,500). The copy length is therefore 4096 bytes regardless of the actual firmware packet length or its offset inside the RX mbuf. The mbuf is a single IWM_RBUF_SIZE (4096-byte) cluster (iwm_rx_addbuf, if_iwm.c:3044-3055). A response packet that does not start at offset 0 β€” entirely normal, since the firmware may prepend other notifications to the same receive buffer, and iwm_handle_rxb explicitly walks multiple packets per buffer β€” makes the copy run up to offset bytes past the end of the cluster (offset max 4087 β†’ up to ~4KB heap OOB read).

The copied bytes are then served to synchronous command consumers as cmd->resp_pkt (if_iwm_util.c:327) and parsed, e.g. NVM chunk parsing reads nvm_resp->length/data from it. Data does not go directly to userspace, but the OOB heap read is real kernel-memory disclosure into driver state (and crash potential when the cluster ends at an unmapped boundary).

Same class, same fix pattern: iwm_alive_fn() (if_iwm.c:2768-2789) β€” the notification-wait callback receives the raw packet (iwm_notification_wait_notify(...) at :5407) and its legacy-v3 fallback branch (:2784-2789) reads palive3 fields with no iwm_rx_packet_payload_len() minimum check (the v4 branch only checks == sizeof(*palive)), an OOB read when the firmware emits a truncated ALIVE.

Not covered by DF-1145: that finding covers MPDU byte_count/mpdu_len, iwm_rx_rx_phy_cmd and iwm_handle_rx_statistics β€” not the sc_cmd_resp copies nor iwm_alive_fn.

Threat model

Malicious/errant firmware DMA content (same trust boundary as DF-1145, which was rated High). Multi-notification RX buffers are a normal firmware behavior, so even non-malicious short responses placed after another notification trigger the overread.

Reproduce

Kernel path needs iwm hardware (absent on guest β€” see env.txt). Arithmetic: copy spans [m_data+offset, m_data+offset+4096) while the cluster is [m_data, m_data+4096); overrun == offset bytes.

Fix

fix.diff β€” clamp the copy to the actual packet length (len, already computed at :5403-5404) capped by sizeof(sc->sc_cmd_resp), and require a minimum payload length in the iwm_alive_fn v3 fallback.

VERDICT.md
↓ download raw

DF-3630 VERDICT

Status: inconclusive (kernel path not exercisable β€” no iwm hardware on guest). Confidence in the defect: certain (source-level arithmetic).

Verified in source

  1. sc_cmd_resp is uint8_t[IWM_CMD_RESP_MAX], IWM_CMD_RESP_MAX == PAGE_SIZE = 4096 (sys/dev/netif/iwm/if_iwmvar.h:312,500).
  2. RX buffers are 4096-byte clusters: iwm_rx_addbuf() allocates m_getjcl(M_WAITOK, MT_DATA, M_PKTHDR, IWM_RBUF_SIZE) (sys/dev/netif/iwm/if_iwm.c:3053), IWM_RBUF_SIZE == 4096 (if_iwmvar.h:290).
  3. iwm_handle_rxb() walks multiple firmware packets per buffer starting at arbitrary offset bounded only by HAVEROOM(offset) = offset+8 < 4096 (if_iwm.c:5372-5405), i.e. offset ≀ 4087.
  4. On IWM_NVM_ACCESS_CMD/IWM_MCC_UPDATE_CMD response match (if_iwm.c:5509-5514) it copies sizeof(sc->sc_cmd_resp) = 4096 bytes from pkt β€” a copy span of [offset, offset+4096) in a 4096-byte cluster β†’ up to 4087 bytes past the end of the mbuf cluster whenever the response packet does not sit at offset 0. The packet's own length (len = iwm_rx_packet_len(pkt) + 4, :5403-5404) is computed but never used to bound this copy.
  5. The copied buffer becomes hcmd->resp_pkt (if_iwm_util.c:327) and is parsed by consumers (e.g. iwm_nvm_read_chunk if_iwm.c:1856-1903).
  6. Sibling: iwm_alive_fn() v3 fallback (if_iwm.c:2784-2789) reads a fixed-size struct from the raw notification packet without any payload length check (contrast the correct pattern at :5535 for DTS_MEASUREMENT and :2778 for the v4 size match).

Why not reproduced on the guest

No Intel Wireless PCI device exists (pciconf empty; kldload if_iwm attaches nothing β€” env.txt). The RX path cannot be driven without the device. The overread is pure arithmetic: overrun bytes == packet offset within the cluster.

Impact ceiling

Kernel heap OOB read (up to ~4KB past a 4KB mbuf cluster) into driver state consumed for NVM/regulatory parsing; contents can reach logs (ether_sprintf(sc->nvm_data->hw_addr) at if_iwm.c:6389 prints a MAC derived from parsed data). Potential crash if the cluster ends at an unmapped boundary. Requires firmware-controlled RX placement β€” same trust boundary as DF-1145 (High).

Fix validation

fix.diff authored; in-kernel validation impossible (no device) β†’ fix_status: not_testable.

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

fix.diff clamps the copy to the computed packet length (len, if_iwm.c:5403-5404) and adds an alive_fn payload check; applies cleanly; no device to validate in-kernel

fix.diff verified with patch --dry-run against sys/dev/netif/iwm/if_iwm.c; no kernel-level validation possible (no iwm device)
↓ fix.diffper-fix-DF-3630

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md; README.md (copy-span arithmetic); env.txt; fix.diff (length-clamped copy + alive_fn minimum-length check, applies clean)

PoC changes

n/a - source-level verification

Verified recommended fix

fix.diff clamps the copy to the computed packet length (len, if_iwm.c:5403-5404) and adds an alive_fn payload check

Verdict

iwm_handle_rxb() copies sizeof(sc->sc_cmd_resp)==PAGE_SIZE (4096) bytes from a command response packet (if_iwm.c:5511-5514) regardless of the packet's actual length or its offset in the 4096-byte RX cluster; HAVEROOM() allows offset up to 4087 (:5377-5380,5405), so a response preceded by any other notification overreads up to ~4087 bytes past the mbuf cluster. The copy is the source of every synchronous command response (hcmd->resp_pkt, if_iwm_util.c:327). Sibling: iwm_alive_fn() v3 fallback (:2784-2789) reads a fixed struct from the raw packet with no payload-length check. Distinct from DF-1145 (MPDU byte_count/mpdu_len, phy-cmd, statistics sinks). Not exercisable on the guest (no iwm device; env.txt).