iwm_send_cmd: TFD TB0 length field (12 bits of hi_n_len) truncates to zero at the wide-header max-payload boundary (hdrlen+paylen==4096)
Summary
The TB0 length packs into bits 4-15 of a u16 while the gate checks only paylen<=4088 (derived for the 4-byte legacy header) - with the 8-byte wide header the total hits 4096 and (4096<<4)&0xFFFF==0, so the fw-visible length becomes 0 (paylen 4085-4087 also encode over the 4092 hw cap). Guest-compiled unit reproduction of the exact expression. Fw-side only (DMA map covers the full cluster - no kernel memory-safety impact); ceiling is command loss/fw SYSASSERT radio reset. Bound-the-total fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4833 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| tfd_len_check.c | β | 3.1 KB | view raw | |
| build.sh | β | 88 B | view raw | |
| run.sh | β | 92 B | view raw | |
| run.log | β | 709 B | view raw | |
| env.txt | β | 830 B | view raw | |
| fix.diff | β | 800 B | view raw | |
| VERDICT.md | β | 3.3 KB | β raw | |
| README.md | β | 3.1 KB | β raw |
DF-4833 β iwm_send_cmd TFD TB0 length-field truncation at the wide-header payload boundary
What this is
Unit-level reproduction of the TFD (Transmit Frame Descriptor) TB0 length
encoding used by iwm_send_cmd() β sys/dev/netif/iwm/if_iwm_util.c:282-283:
desc->tbs[0].hi_n_len = htole16(iwm_get_dma_hi_addr(paddr)
| ((hdrlen + paylen) << 4));
struct iwm_tfd_tb.hi_n_len is a uint16_t whose bits 0-3 carry DMA address
bits [35:32] and bits 4-15 carry the buffer length β a 12-bit field, max 4095
(sys/dev/netif/iwm/if_iwmreg.h:1852-1858). The hardware spec documented in
the same header caps a single transmit buffer at (4K - 4) = 4092 bytes
(if_iwmreg.h:1882-1883).
iwm_send_cmd() accepts paylen up to IWM_MAX_CMD_PAYLOAD_SIZE = 4088
(if_iwmreg.h:6881, checked at if_iwm_util.c:220). That constant was
derived for the 4-byte legacy header ((4096-4) - sizeof(struct
iwm_cmd_header)), but a command with a non-zero group id uses the 8-byte
wide header (if_iwm_util.c:205-212, if_iwmreg.h:6866-6874). With
hdrlen=8, paylen=4088:
hdrlen + paylen == 4096β already 4 bytes over the hw per-buffer cap, and(4096 << 4) & 0xFFFF == 0β the 17th bit falls off theuint16_tand the firmware-visible TB0 length becomes zero: the command payload is invisible to the firmware.
paylen 4085..4087 (wide) also violate the 4092 hw cap (encode 4093..4095).
Impact
Firmware-side only: the fw DMA-fetches 0 bytes (or, for 4085-4087, an
over-cap length) for that command β command loss / potential fw SYSASSERT β
radio reset. No kernel memory-safety impact: the DMA map covers the whole
4096-byte cluster (m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, IWM_RBUF_SIZE) at
if_iwm_util.c:227), so fw reads stay in-bounds regardless of the length
field. Trigger surface: the only variable-size wide commands are scan
requests (if_iwm_scan.c:605-628, 714-728), whose size is
device-NVM-derived (iwm_scan_size(sc)) and pre-checked against
IWM_MAX_CMD_PAYLOAD_SIZE; reaching exactly paylen == 4088 requires a
specific NIC configuration, and scan submission is privileged
(SIOCS80211 β caps_priv_check_self(SYSCAP_NONET_WIFI),
sys/netproto/802_11/wlan/ieee80211_ioctl.c:3471-3472).
Severity: Info (latent hw-spec violation / hardening). Not kernel- exploitable, not practically triggerable.
Build / run
cc -o /tmp/tfdlen tfd_len_check.c /tmp/tfdlen
Expected output
See run.log β decisive lines:
paylen totlen enc.len(bits) dec.length verdict
4084 4092 65472 4092 ok
4085 4093 65488 4093 EXCEEDS HW TB CAP (4K-4)
...
4088 4096 0 0 EXCEEDS HW TB CAP (4K-4)
The demo compiles the exact constants and structure layouts from
if_iwmreg.h and evaluates the exact expression from if_iwm_util.c:282.
It was compiled and executed on the audit QEMU guest (DragonFly 6.5-DEVELOPMENT).
The in-kernel/fw manifestation was NOT exercised: the guest has no Intel
Wireless hardware (/dev/iwm* absent, no wifi PCI devices) β see VERDICT.md.
DF-4833 β VERDICT
Status: untested (unit-level arithmetic reproduced; kernel/fw manifestation not exercisable β no iwm hardware in the audit guest) Impact: none (no kernel memory-safety impact; fw-side length confusion) Confidence: certain (the truncation is proven arithmetic on the exact code constants; see run.log)
What was verified
- The defect is real at the code level.
tfd_len_check.creproduces the exact expression ofsys/dev/netif/iwm/if_iwm_util.c:282-283with the exact constants ofsys/dev/netif/iwm/if_iwmreg.h:
IWM_MAX_CMD_PAYLOAD_SIZE == 4088(if_iwmreg.h:6881);- wide header = 8 bytes (
if_iwmreg.h:6866-6874, selected atif_iwm_util.c:206-207wheniwm_cmd_groupid(code) != 0); hi_n_lencarries the TB length in bits 4-15 β 12 bits, max 4095 (if_iwmreg.h:1846-1858);- hw caps one transmit buffer at (4K-4) = 4092 (
if_iwmreg.h:1882-1883).
Result (guest-compiled, run.log): hdrlen=8, paylen=4088 β totlen 4096
β encoded TB0 length 0. paylen 4085-4087 encode 4093-4095 β over
the hw cap.
-
Why no kernel memory corruption. The mbuf path maps the full 4096-byte jcluster (
m_getjcl(..., IWM_RBUF_SIZE)atif_iwm_util.c:227,IWM_RBUF_SIZE == 4096atif_iwmvar.h:290), so whatever length the firmware believes, its DMA fetch of the command buffer stays inside the mapped cluster. The defect corrupts only the firmware's view of the command length. -
Why the manifestation is not testable here. The audit guest (DragonFly 6.5-DEVELOPMENT x86_64,
env.txt) has no Intel Wireless device β no/dev/iwm*, no wifi PCI function.iwm_send_cmd()runs only against real iwm firmware. Hence verdictuntested(the manifest's permitted vocabulary has nonot_testablefor poc status), impactnone. -
Reachability honesty. The only
iwm_send_cmd()callers whose paylen approaches the boundary are the scan request builders (sys/dev/netif/iwm/if_iwm_scan.c:605-628and714-728): they size the command fromiwm_scan_size(sc)β device/NVM-derived, not per-request user-tunable β and pre-rejectreq_len > IWM_MAX_CMD_PAYLOAD_SIZE. Hitting exactlypaylen == 4088requires a NIC whose supported-channel count makes the fixed request size land there. Scan submission is privileged (ieee80211_ioctl.c:3471-3472). Even when hit, the outcome is fw-side (dropped command / fw error / radio reset), bounded by the driver's restart machinery. This is why the finding is filed Info.
Exploit chain
None. No kernel memory-safety consequence; fw-side confusion only.
Fix validation
Not performed on hardware (fix_status: not_testable). The proposed
fix.diff (reject totlen > IWM_MAX_CMD_PAYLOAD_SIZE + sizeof(cmd->hdr),
i.e. 4092, covering both header widths) was authored against the read-only
sys/ tree after line-accurate confirmation of the root cause; it was not
applied to sys/.
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_testablefix.diff authored (reject totlen > 4092, covering both header widths) but not validated on hardware: the guest has no iwm NIC, so no baseline and no patched kernel run is possible; the fix is a pure bound tightening with no code-path change below the check.
fix.diff (unified diff vs sys/dev/netif/iwm/if_iwm_util.c:214-226, never applied to sys/)
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (decisive rows: paylen 4088 -> enc.len(bits)=0 / dec.length=0; paylen 4085-4087 -> 4093-4095 EXCEEDS HW TB CAP); tfd_len_check.c (exact constants+expression); VERDICT.md (full reasoning); env.txt (guest has no iwm hw)
PoC changes
authored fresh (no seed PoC existed); reproduces the encoding expression and header constants verbatim from if_iwmreg.h/if_iwm_util.c
Verified recommended fix
Bound the total, not the payload: reject when hdrlen+paylen > IWM_MAX_CMD_PAYLOAD_SIZE + sizeof(cmd->hdr) (== 4092, the hw TB cap) in iwm_send_cmd()
Verdict
The truncation is proven arithmetic: iwm_send_cmd() (sys/dev/netif/iwm/if_iwm_util.c:282-283) encodes the TB0 length in bits 4-15 of the 16-bit hi_n_len field (if_iwmreg.h:1846-1858) while admitting paylen up to IWM_MAX_CMD_PAYLOAD_SIZE=4088 (if_iwmreg.h:6881, check at if_iwm_util.c:220); with the 8-byte wide header (if_iwm_util.c:205-212) hdrlen+paylen==4096, which both exceeds the (4K-4)=4092 hw per-buffer cap (if_iwmreg.h:1882-1883) and wraps the 12-bit field to zero, making the command payload invisible to the firmware. The unit-level reproduction (run.log, compiled+executed on the audit guest) demonstrates the encoding exactly. No kernel memory-safety impact: the DMA map covers the full 4096-byte cluster (if_iwm_util.c:227), so fw fetches stay in-bounds regardless of the length field. The kernel/fw manifestation is not exercisable in the audit guest, which has no Intel Wireless hardware (env.txt: no /dev/iwm*, no wifi PCI function); only scan requests approach the boundary and their size is device-NVM-derived (if_iwm_scan.c:605-628) behind a privileged ioctl (ieee80211_ioctl.c:3471-3472). Filed Info: latent hw-spec violation, hardening only.
No comments yet.