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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4833 Β· 8 files
FileTypeDescriptionSize
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 the uint16_t and 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.

VERDICT.md
↓ download raw

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

  1. The defect is real at the code level. tfd_len_check.c reproduces the exact expression of sys/dev/netif/iwm/if_iwm_util.c:282-283 with the exact constants of sys/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 at if_iwm_util.c:206-207 when iwm_cmd_groupid(code) != 0);
  • hi_n_len carries 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.

  1. Why no kernel memory corruption. The mbuf path maps the full 4096-byte jcluster (m_getjcl(..., IWM_RBUF_SIZE) at if_iwm_util.c:227, IWM_RBUF_SIZE == 4096 at if_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.

  2. 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 verdict untested (the manifest's permitted vocabulary has no not_testable for poc status), impact none.

  3. 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-628 and 714-728): they size the command from iwm_scan_size(sc) β€” device/NVM-derived, not per-request user-tunable β€” and pre-reject req_len > IWM_MAX_CMD_PAYLOAD_SIZE. Hitting exactly paylen == 4088 requires 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_testable
baseline no→ patch + rebuild →patched clean

fix.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/)
↓ fix.diffper-fix-DF-4833

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.