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

iwm phy_db: unbounded firmware channel-group id β†’ up-to-1 MiB M_WAITOK allocations from interrupt context + uint8_t truncation of the group-count upload loop (-1β†’255 hard-fails firmware load)

Summary

set_section takes the fw-supplied chg_id verbatim (no bound): first CHG notif allocates up to 1MiB M_WAITOK|M_ZERO from the RX interrupt path, each later chunk adds up to 65535B held until detach. Independently, send_all_channel_groups takes uint8_t while callers pass int - n>=256 truncates mod 256 (groups silently never uploaded, TX-power hazard) and init -1 becomes 255 -> get_section NULL -> EINVAL -> iwm_load_firmware aborts (device DoS) whenever fw sends no groups of a type. Truncation-only (no memory corruption); hostile-fw trust model; no iwm NIC on guest. Cap-chg_id + int-typed-count + skip-when-unset fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4816 Β· 5 files
FileTypeDescriptionSize
README.md β€” 3.5 KB ↓ raw
VERDICT.md β€” 4.6 KB ↓ raw
verdict.json β€” 4.0 KB view raw
fix.diff β€” 2.7 KB view raw
manifest.json β€” 1.3 KB view raw

DF-4816 β€” iwm phy_db: unvalidated firmware chg_id (group index) β†’ unbounded

group-array allocation + uint8_t truncation of group count in send path

File

sys/dev/netif/iwm/if_iwm_phy_db.c (pass 2, GLM 5.3 re-audit)

Class

CWE-197 (numeric truncation error) + CWE-770 (allocation without bounds / uncontrolled total). Correctness / resource-exhaustion defect reachable only by the device firmware (hostile-device model: compromised or buggy Wi-Fi firmware image on Intel Wireless 7265/8000+ NICs). Not kernel memory corruption and not reachable by an unprivileged local user through any syscall path.

Build / run

There is no runnable PoC for this finding on the audit QEMU guest: the defect requires an Intel Wireless 7265+/8000+ PCI device (8086:095a/095b/24f*/…) whose firmware emits IWM_CALIB_RES_NOTIF_PHY_DB (0x6b) notifications with a crafted first channel-group id. The guest has no such device (verified: pciconf -l shows 7 generic QEMU devices, no 8086 wireless, no iwm module). Verification is therefore static (code-path proof below); runtime reproduction requires bare-metal hardware with a patched firmware image.

What a hostile firmware sends (trigger description)

During the init-ucode calibration phase (driver flow: iwm_run_init_ucode() β†’ notif-wait callback iwm_wait_phy_db_entry() β†’ iwm_phy_db_set_section()), the firmware emits:

struct iwm_calib_res_notif_phy_db {
    uint16_t type;      /* 4 = IWM_PHY_DB_CALIB_CHG_PAPD or 5 = CHG_TXP */
    uint16_t length;
    uint8_t  data[];    /* data[0..1] = chg_id (group index), fw-chosen */
} __packed;

chg_id is taken verbatim from data[0..1] (if_iwm_phy_db.c:279 / :293) with no upper bound, and two independent defects follow:

  1. Unbounded allocation / accumulation (if_iwm_phy_db.c:285-290, 299-304): on the first CHG notification of each type the driver allocates (chg_id + 1) * sizeof(struct iwm_phy_db_entry) β€” up to 65536 entries = 1 MiB per type β€” with M_WAITOK|M_ZERO from the RX interrupt path (iwm_intr β†’ iwm_notif_intr β†’ iwm_handle_rxb β†’ iwm_notification_wait_notify β†’ iwm_wait_phy_db_entry). Each subsequent CHG notification for a fresh index allocates length (up to 65535) more bytes of M_DEVBUF held until detach, bounded only by the calibration wait timeout β€” i.e. hundreds of MB of unreclaimable kernel memory from a few seconds of hostile notifications.

  2. uint8_t truncation (if_iwm_phy_db.c:455-458 param uint8_t max_ch_groups; call sites 541-542 and 551-552 pass the int n_group_papd/n_group_txp): - n_group_* >= 256 β†’ count truncated mod 256 β†’ groups β‰₯ 256 are silently never uploaded to the runtime firmware (radio runs with missing PAPD/TXP calibration β€” TX-power correctness hazard), or nothing at all is sent when the count is a multiple of 256. - n_group_* == -1 (init value β€” fw sent no groups of that type) β†’ passed as 255 β†’ first iwm_phy_db_get_section() returns NULL β†’ iwm_phy_db_send_all_channel_groups() returns EINVAL β†’ iwm_send_phy_db_data() fails β†’ iwm_load_firmware() aborts (if_iwm.c:4856-4857) β†’ interface cannot start (device DoS).

Expected result if run on real hardware with hostile fw

  • dmesg: either iwm_send_phy_db_data failure / firmware load abort, or silent partial calibration upload; sustained M_DEVBUF growth in vmstat -m during the calibration window.

Fix

See fix.diff (cap chg_id at 255; pass the group count as int and skip non-positive counts).

VERDICT.md
↓ download raw

DF-4816 β€” VERDICT

Status: untested (static proof only β€” hardware absent from audit guest) Impact class: none demonstrated at runtime; code-path defect proven certain by inspection

What was verified, and how

  1. Reachability of the defect (proven, static). - Only caller of iwm_phy_db_set_section() in the tree: iwm_wait_phy_db_entry() β€” sys/dev/netif/iwm/if_iwm.c:2831, registered as the notif-wait callback in iwm_run_init_ucode() (if_iwm.c:2952-2957). - Callback invocation chain (interrupt context): iwm_intr (if_iwm.c:5740, registered via bus_setup_intr with wlan_global_serializer, if_iwm.c:6028-6030) β†’ iwm_notif_intr (if_iwm.c:5876) β†’ iwm_handle_rxb (if_iwm.c:5724) β†’ iwm_notification_wait_notify (if_iwm.c:5407 β†’ if_iwm_notif_wait.c:118-153, invokes wait_entry->fn under lk_lk) β†’ iwm_wait_phy_db_entry β†’ iwm_phy_db_set_section. - chg_id is le16toh() of raw phy_db_notif->data[0..1] (if_iwm_phy_db.c:279, :293). Nothing upstream bounds it; nothing in set_section bounds it. The array sized (chg_id+1)*16 bytes is allocated with M_WAITOK|M_ZERO (if_iwm_phy_db.c:285-287, 299-301); (chg_id+1) max 65536 β†’ max 1 MiB per type, no integer overflow (uint16 β†’ int promotion; 65536*16 = 2^20 fits int). - Send-side truncation: iwm_phy_db_send_all_channel_groups() declares uint8_t max_ch_groups (if_iwm_phy_db.c:455-458); call sites pass the int fields phy_db->n_group_papd / phy_db->n_group_txp (if_iwm_phy_db.c:541-542, 551-552), init value -1 (if_iwm_phy_db.c:187-188). C integer conversion: -1 β†’ 255, 65536 β†’ 0, 300 β†’ 44. - Consequence chain for n_group == -1: loop i=0 β†’ iwm_phy_db_get_section chg_id(0) >= n_group(-1) β†’ NULL (if_iwm_phy_db.c:212-214) β†’ EINVAL (if_iwm_phy_db.c:469-470) β†’ iwm_send_phy_db_data error (if_iwm_phy_db.c:543-548) β†’ iwm_load_firmware goto error (if_iwm.c:4856-4857).

  2. Why it is NOT memory corruption (negative results). - The truncated loop count can only shrink (mod 256), never exceed the allocated group count, so the walk in iwm_phy_db_send_all_channel_groups cannot go out of bounds. - Entry allocation and entry->size are always the same size (if_iwm_phy_db.c:314-321); the send path uses entry->size bytes from entry->data (if_iwm_phy_db.c:476-479) and iwm_send_cmd skips zero-length fragments and rejects oversized payloads (if_iwm_util.c:271-277, 214-226). No OOB via the u16 length fields. - The OOB read against the actual RX packet size in set_section (memcpy at if_iwm_phy_db.c:319 and the chg_id reads at :279/:293) is DF-1898 (known, pass 1) β€” deliberately not re-reported here.

  3. Why runtime reproduction was not attempted. - Guest: DragonFly 6.5-DEVELOPMENT X86_64_GENERIC (QEMU). pciconf -l shows 7 devices, none an Intel Wireless 7265/8000+ NIC; no iwm module. The trigger is emitted only by the NIC firmware on the calibration completion path; there is no software-only injection point (the RX notification comes from DMA'd device buffers, not from any syscall). - No unprivileged-local-user path exists, so the mandatory uid=0 escalation objective of Phase V does not apply; per the review contract, Low-severity hardware/firmware-gated findings may be filed with static certainty and fix validation "not_testable".

Exploit chain

(none β€” not an escalation primitive; hostile-firmware-triggered correctness / resource-exhaustion defect. A compromised firmware already has stronger primitives, e.g. DF-1898.)

Fix validation

fix.diff authored (chg_id cap + int-typed group count + non-positive skip); not buildable against this guest (no iwm hardware to attach the driver; kernel build would succeed but the code path cannot execute), so fix_status = not_testable.

Bottom line

Certain static finding, Low severity: firmware-controlled group index without bounds produces up-to-1 MiB M_WAITOK allocations from interrupt context per group type plus modulo-256 truncation of the upload loop (incl. the -1β†’255 case that hard-fails firmware load). No kernel memory unsafety; matches upstream iwlwifi 3.14 heritage (upstream later added group-count validation).

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 against the read-only sys/ tree (cap + int count + non-positive skip). Not testable: the guest has no Intel Wireless 7265+ NIC, so neither baseline nor patched kernel can execute the calibration path. Compile-correctness of the diff verified by inspection (types and call sites updated consistently).

findings/poc/DF-4816/fix.diff
↓ fix.diffper-fix-DF-4816

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (full static proof with call chain and C-conversion analysis); README.md (trigger description); fix.diff (chg_id cap at 255 + int-typed group count + skip non-positive counts)

PoC changes

No seed PoC existed; no runnable PoC is possible without the iwm NIC and a hostile firmware image (RX notifications are DMA'd by the device, not injectable via syscalls). Pack documents the trigger and the static proof instead.

Verified recommended fix

Cap firmware-supplied chg_id at 255 in iwm_phy_db_set_section (EINVAL otherwise), change iwm_phy_db_send_all_channel_groups max_ch_groups param to int, and skip send when n_group_papd/n_group_txp <= 0

Verdict

Static-proof finding, Low severity. iwm_phy_db_set_section() takes the firmware-supplied channel-group id (chg_id) verbatim (if_iwm_phy_db.c:279,:293) with no upper bound: the first CHG notification of a type allocates (chg_id+1)*16 bytes (up to 1 MiB) with M_WAITOK|M_ZERO from the RX interrupt path (iwm_intr -> iwm_notif_intr -> iwm_handle_rxb -> iwm_notification_wait_notify -> iwm_wait_phy_db_entry, if_iwm.c:2831), and subsequent per-index chunks accumulate M_DEVBUF bounded only by the calibration wait timeout. Independently, iwm_phy_db_send_all_channel_groups() takes the group count as uint8_t (if_iwm_phy_db.c:455-458) while callers pass the int n_group_papd/n_group_txp (if_iwm_phy_db.c:541-542,:551-552): counts >=256 truncate mod 256 (calibration groups silently never uploaded to the runtime firmware), and the init value -1 becomes 255, making get_section() return NULL and send_all_channel_groups() return EINVAL so iwm_send_phy_db_data() fails and iwm_load_firmware() aborts (if_iwm.c:4856-4857) - a device DoS when the firmware sends no channel groups. Not kernel memory corruption (truncation can only shrink the loop; entry->size always equals the allocation), and not reachable by an unprivileged local user: the only trigger is a firmware-emitted IWM_CALIB_RES_NOTIF_PHY_DB notification. Runtime reproduction impossible on the audit guest: pciconf shows no Intel Wireless 7265/8000+ device and no iwm module (DragonFly 6.5-DEVELOPMENT X86_64_GENERIC QEMU guest, verified this run).