iwm phy_db: latent heap OOB read in channel_id_to_txp() β entry size never checked before reading txp_chg->max_channel_idx (path currently dead in-tree)
Summary
channel_id_to_txp reads the 6-byte packed header from every TXP group entry after only a NULL check - set_section accepts lengths 0..5 and kmalloc(0) returns non-NULL in DFly, so a short entry reads up to 6 bytes past the object. Dead in-tree (only CFG/NCH call the get_section chain, never CHG - exhaustive call-graph proof) but becomes a live hostile-fw OOB read the moment the CHG lookup is wired as in newer iwlwifi. size>=sizeof(header) skip fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4817 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.3 KB | β raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| verdict.json | β | 2.5 KB | view raw | |
| fix.diff | β | 460 B | view raw | |
| manifest.json | β | 1.0 KB | view raw |
DF-4817 β iwm phy_db: latent heap OOB read in channel_id_to_txp() β
entry length never checked before reading txp_chg->max_channel_idx
File
sys/dev/netif/iwm/if_iwm_phy_db.c (pass 2, GLM 5.3 re-audit)
Class
CWE-125 (out-of-bounds read), latent / currently unreachable in-tree (hardening finding, Info severity).
The defect
channel_id_to_txp() (if_iwm_phy_db.c:372-393) walks the TXP channel-group
entries:
for (i = 0; i < phy_db->n_group_txp; i++) {
txp_chg = (void *)phy_db->calib_ch_group_txp[i].data;
if (!txp_chg)
return 0xff; /* NULL check only */
if (le16toh(txp_chg->max_channel_idx) >= ch_index) /* OOB read */
return i;
}
It validates only that data != NULL, never that
calib_ch_group_txp[i].size >= sizeof(struct iwm_phy_db_chg_txp) (6 bytes:
uint32_t space; uint16_t max_channel_idx; β if_iwmreg.h:2298-2301, packed).
A firmware that sent a CHG_TXP section with length in [0..5] produces a
kmalloc(size) entry (kmalloc(0) is a legal allocation in DragonFly β
kern_slaballoc.c handles the degenerate size==0 case and returns a non-NULL
pointer) whose max_channel_idx read runs past the allocation by up to 6
bytes. The NULL check passes because the pointer is non-NULL.
Why it is latent (reachability analysis)
channel_id_to_txp() is static and has exactly one caller:
iwm_phy_db_get_section_data() (if_iwm_phy_db.c:410), invoked when
type == IWM_PHY_DB_CALIB_CHG_TXP. iwm_phy_db_get_section_data() itself is
static with exactly two call sites β if_iwm_phy_db.c:506
(IWM_PHY_DB_CFG) and :523 (IWM_PHY_DB_CALIB_NCH), both in
iwm_send_phy_db_data(). Neither passes a CHG type, so the TXP lookup path
(including this read) is dead code in this tree today. It becomes live the
moment anyone wires iwm_phy_db_get_section_data() for channel-group types
(e.g. porting channel-selection flows from newer iwlwifi, where this helper
is used for per-channel TXP retrieval).
Build / run
No PoC possible: hardware-gated (see DF-4816 pack β guest has no Intel Wireless device) and the buggy path is unreachable in the current tree. Filed as Info: hardening/latent memory-safety defect with a one-line fix.
Fix
See fix.diff β size-check each entry before reading the header.
DF-4817 β VERDICT
Status: untested (latent defect, unreachable in-tree; hardware absent from audit guest) β code-level proof certain by inspection
Proof of the defect (static)
struct iwm_phy_db_chg_txpis 6 bytes packed (uint32_t space; uint16_t max_channel_idx;β sys/dev/netif/iwm/if_iwmreg.h:2298-2301).max_channel_idxsits at offset 4.- Entries are populated solely by
iwm_phy_db_set_section()(if_iwm_phy_db.c:312-321):entry->data = kmalloc(size)withsize= firmware-controlledphy_db_notif->length(uint16, used verbatim β only the source bounds are DF-1898's problem; the destination is exactlysizebytes), andentry->size = size. Alengthof 0..5 is accepted and yields a valid non-NULL allocation smaller than the 6-byte header (kmalloc(0) returns a valid pointer in DragonFly: kern_slaballoc.c "Handle the degenerate size == 0 case"). channel_id_to_txp()(if_iwm_phy_db.c:381-390) readstxp_chg->max_channel_idxfrom every entry after checking only!txp_chgβ an allocation of 0..5 bytes passes the NULL check and the 2-byte load at offset 4 reads out of bounds (up to 6 bytes past the object).
Reachability (why Info, not Low/Medium)
- Call graph, exhaustively (grep over the whole tree):
channel_id_to_txpβiwm_phy_db_get_section_data(if_iwm_phy_db.c:410, only caller) βiwm_send_phy_db_data(if_iwm_phy_db.c:506IWM_PHY_DB_CFG, :523IWM_PHY_DB_CALIB_NCH, only call sites). No caller passesIWM_PHY_DB_CALIB_CHG_TXP/CHG_PAPD, so the defective loop cannot execute in this tree as shipped. - Both functions are
staticto if_iwm_phy_db.c β no external caller can reach them either. - Impact if ever wired up: hostile-firmware heap OOB read of β€6 bytes per entry, value compared against a channel index β a low-value oracle; not a write primitive, not controllable beyond fw.
Why no runtime test
Same hardware constraint as DF-4816 (guest pciconf -l: no Intel Wireless
7265/8000+ device; no iwm module), compounded by the path being dead code
today. No syscall-reachable injection point exists.
Fix validation
fix.diff adds the missing entry-size check (skip entries smaller than
sizeof(struct iwm_phy_db_chg_txp)). Not build/run-testable on the guest
for the reasons above β fix_status = not_testable. Diff is trivially
compile-safe (type already visible at that point via if_iwmreg.h).
Bottom line
Real but latent memory-safety defect (missing length validation before a fixed-offset struct read) on a path that is currently dead in-tree; one-line fix removes it permanently. Filed at Info severity so it is on record before anyone revives the CHG lookup path.
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 adds the missing entry-size check. Not testable on the audit guest: no iwm hardware, and the defective loop is unreachable in the current tree (no caller passes a CHG type).
findings/poc/DF-4817/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (static proof: struct layout, population path, exhaustive call graph); README.md; fix.diff (entry size check)
PoC changes
No seed PoC; no PoC possible (hardware-gated + dead code path).
Verified recommended fix
In channel_id_to_txp, skip entries whose size is smaller than sizeof(struct iwm_phy_db_chg_txp) before reading max_channel_idx
Verdict
Latent heap out-of-bounds read (Info severity, hardening). channel_id_to_txp() reads txp_chg->max_channel_idx (offset 4, 2 bytes) from every TXP group entry after checking only entry->data != NULL (if_iwm_phy_db.c:381-390); iwm_phy_db_set_section() accepts firmware lengths of 0..5 bytes, producing allocations smaller than the 6-byte struct iwm_phy_db_chg_txp header (if_iwmreg.h:2298-2301), so the read runs up to 6 bytes past the object (kmalloc(0) returns a valid non-NULL pointer in DragonFly). Currently unreachable: channel_id_to_txp's only caller iwm_phy_db_get_section_data is invoked only with IWM_PHY_DB_CFG (if_iwm_phy_db.c:506) and IWM_PHY_DB_CALIB_NCH (:523) - never a channel-group type - and both functions are static to this file. The defect becomes a live hostile-firmware OOB read the moment the CHG lookup path is wired up (as in newer iwlwifi). No runtime test possible: guest lacks Intel Wireless hardware (verified) and the path is dead code in-tree.
No comments yet.