{"repro_id":"REPRO-2026-00373","version":6,"title":"Linux kernel iwlwifi PCIe UAF/double-free — iwl_pcie_rx_free() leaves freed RX pointers (rx_pool, global_table, rxq, alloc_page) non-NULL after free","repro_type":"security","status":"published","severity":"medium","description":"Linux kernel iwlwifi PCIe transport: use-after-free / double-free via stale RX pointers left non-NULL after iwl_pcie_rx_free() (CWE-416; missing pointer invalidation on error unwind, CWE-459). Upstream title: \"wifi: iwlwifi: pcie: null RX pointers after free\" (Emmanuel Grumbach, Intel; fixed via upstream 7.2 commit 2c79d7a7b583050c9f58041465cb46fe3483ab5d, verified on git.kernel.org: NULLs rx_pool, global_table, rxq, alloc_page after free). NOTE on request id: operator initially referenced \"CVE-2026-93796B\", which is not a valid CVE identifier (cve.org API rejects the format; no such record). Canonical advisory is CVE-2026-93796; only the canonical ID is registered here.","root_cause":"# CVE-2026-93796 — Root Cause Analysis: iwlwifi PCIe `iwl_pcie_rx_free()` double-free / use-after-free\n\n## Summary\n\nThe Linux kernel iwlwifi PCIe transport frees its RX bookkeeping objects in\n`iwl_pcie_rx_free()` (`drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/rx.c`,\nolder trees: `pcie/rx.c`) but never invalidates the pointers on the still-live\n`struct iwl_trans_pcie`. The function's own re-entry guard uses\n`trans_pcie->rxq == NULL` as the \"nothing allocated\" sentinel, so after a first\nfree — performed on the nic-init error unwind when `iwl_pcie_tx_init()` fails\ninside `iwl_pcie_nic_init()` — any later teardown or retry path\n(interface down/up, driver unbind/rebind, reprobe, suspend/resume, error\nrecovery) that calls `iwl_pcie_rx_free()` again passes the guard and re-frees\n`rx_pool`, `global_table`, `rxq` (kfree) and `alloc_page` (__free_pages),\nproducing a double-free, and dereferences the freed `rx_pool[]`/`rxq[]` arrays,\nproducing a slab use-after-free (CWE-416, with missing pointer invalidation\nCWE-459).\n\n## Impact\n\n- Component: Linux kernel `iwlwifi` PCIe transport (`drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/rx.c`).\n- Affected versions: `< 6.12.111`; `6.13.0 – 6.18.53`; `6.19.0 – 7.1.x`.\n- Fixed in: 6.12.111 (`6ec5bf430cf7466cb412e47f2761594092781fac`),\n  6.18.53 (`3456c5bcc987aeb182e50e30c9b02be8420e9953`), upstream 7.2\n  (`2c79d7a7b583050c9f58041465cb46fe3483ab5d`).\n- Consequences: kernel memory corruption (double-free / slab use-after-free) in\n  the WiFi driver teardown path — kernel crash (DoS) and a potentially\n  exploitable heap corruption primitive on systems with Intel WiFi PCIe\n  hardware. This run confirms memory corruption only; no code execution is\n  claimed.\n\n## Impact Parity\n\n- Disclosed/claimed maximum impact: memory corruption (double-free / UAF) —\n  KASAN-detectable; the advisory does not claim RCE.\n- Reproduced impact this run: on the v6.18.52 KASAN+SLUB_DEBUG+DEBUG_OBJECTS\n  kernel the second `iwl_pcie_rx_free()` produced (a) a KASAN\n  `use-after-free` 8-byte read of the freed `rx_pool` in\n  `iwl_pcie_free_rbs_pool` <- `iwl_pcie_rx_free` [iwlwifi], (b) a BAD_PAGE\n  taint from the repeated `__free_pages(alloc_page)`, and (c) a kernel oops\n  (NULL/wild dereference in `dma_free_attrs` <- `iwl_pcie_free_rxq_dma` <-\n  `iwl_pcie_rx_free`) from walking the freed `rxq[]` state. The identical\n  trigger on the fixed kernel completes cleanly 2/2 (negative control).\n- Parity: `full` for the claimed memory-corruption impact class.\n- Not demonstrated: code execution (out of scope per the advisory/ticket).\n\n## Root Cause\n\n`iwl_pcie_rx_free()` (v6.18.52, rx.c line 1204) ends with:\n\n```c\n\tkfree(trans_pcie->rx_pool);\n\tkfree(trans_pcie->global_table);\n\tkfree(trans_pcie->rxq);\n\n\tif (trans_pcie->alloc_page)\n\t\t__free_pages(trans_pcie->alloc_page, trans_pcie->rx_page_order);\n```\n\nNone of the four freed members are set to `NULL`, yet the function starts with:\n\n```c\n\tif (!trans_pcie->rxq) {\n\t\tIWL_DEBUG_INFO(trans, \"Free NULL rx context\\n\");\n\t\treturn;\n\t}\n```\n\nThe first call (nic-init unwind after a TX-init failure) frees everything but\nleaves `rxq` non-NULL, defeating the sentinel. A second call on the same live\ntransport object walks the freed `rxq[]`/`rx_pool[]` arrays\n(`iwl_pcie_free_rbs_pool()` reads `rx_pool[i].page`, the queue loop reads\n`rxq->bd`/`rxq->napi.poll`) and then `kfree()`s the same three pointers again\nand re-frees `alloc_page` — UAF reads plus double-frees.\n\nThe fix (upstream `2c79d7a7b583050c9f58041465cb46fe3483ab5d`, \"wifi: iwlwifi:\npcie: null RX pointers after free\", Emmanuel Grumbach, Intel) sets\n`rx_pool`, `global_table`, `rxq` and `alloc_page` to `NULL` immediately after\nfreeing, so repeated cleanup and retry paths fail safe on the sentinel.\n\n## Reproduction Steps\n\n1. `bundle/repro/reproduction_steps.sh` (self-contained; see that file).\n2. The script:\n   - clones linux-stable `v6.18.52` (vulnerable) into the prepared project cache;\n   - verifies `iwl_pcie_rx_free()` lacks the pointer-NULLing fix;\n   - because QEMU cannot emulate an Intel WiFi PCIe device (explicitly\n     anticipated by the ticket), patches a hardware-independent selftest trigger\n     **into the real driver** (`pcie/gen1_2/rxfree-selftest.c` + a\n     `rx_free_selftest=1` module-parameter hook in `iwl_drv_init()`): it builds\n     RX state exactly as `iwl_pcie_rx_alloc()` does (kcalloc of\n     `rxq`/`rx_pool`/`global_table`, `alloc_pages` for `alloc_page`, initialised\n     RB-allocator work/lists) and then calls the **real, unmodified\n     `iwl_pcie_rx_free()` twice** — first = nic-init unwind after\n     `iwl_pcie_tx_init()` failure, second = later teardown/retry on the same\n     live transport object;\n   - builds a `defconfig`+KASAN(outline)+SLUB_DEBUG+DEBUG_OBJECTS kernel with\n     `CONFIG_IWLWIFI=m`, producing a vulnerable `iwlwifi.ko`; then applies the\n     stable fix hunk to `rx.c` and rebuilds a fixed `iwlwifi.ko` against the\n     same kernel;\n   - boots the kernel twice per build in QEMU/KVM with a busybox initramfs that\n     `insmod`s the module with `rx_free_selftest=1`, capturing the serial log.\n3. Expected evidence: vulnerable boots emit `BUG: KASAN: use-after-free` in\n   `iwl_pcie_free_rbs_pool` <- `iwl_pcie_rx_free` (`[iwlwifi]`) followed by a\n   BAD_PAGE taint and a fatal oops in `iwl_pcie_free_rxq_dma`; fixed boots\n   print `SELFTEST-COMPLETE` with all four pointers NULLed and no KASAN splat.\n\n## Evidence\n\n- `bundle/logs/vm_vuln_attempt1.log`, `bundle/logs/vm_vuln_attempt2.log` —\n  KASAN splats naming the RX teardown path (2/2 attempts).\n- `bundle/logs/vm_fixed_attempt1.log`, `bundle/logs/vm_fixed_attempt2.log` —\n  negative control, selftest completes cleanly (2/2 attempts).\n- `bundle/logs/build_bzimage.log`, `bundle/logs/build_mod_vuln.log`,\n  `bundle/logs/build_mod_fixed.log` — build transcripts.\n- `bundle/repro/runtime_manifest.json` — runtime evidence manifest with\n  artifact hashes and immutable target identity\n  (`git:https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git@8f3741e6feb045da5b406df0a80b42a1adfb289b` = v6.18.52).\n- Environment: x86_64, QEMU/KVM (`-machine accel=kvm:tcg`), KASAN outline,\n  SLUB_DEBUG, DEBUG_OBJECTS(_FREE/_TIMERS) enabled.\n\n## Recommendations / Next Steps\n\n- Backport/apply upstream `2c79d7a7b583050c9f58041465cb46fe3483ab5d`\n  (\"null RX pointers after free\") — already in 6.12.111 / 6.18.53 / 7.2.\n- Upgrade affected kernels to >= 6.12.111 / >= 6.18.53 / >= 7.2.\n- Regression test: an in-tree KUnit or error-injection test that forces\n  `iwl_pcie_tx_init()` failure and then re-runs teardown would lock the\n  invariant \"rx free is idempotent\" in.\n\n## Additional Notes\n\n- Idempotency: the script is re-runnable; it restores `rx.c` to the vulnerable\n  state, caches the bzImage and both module builds in the project cache, and\n  re-executes fresh QEMU proofs on every run.\n- Evidence scope: QEMU exposes no Intel WiFi PCIe device, so the trigger is a\n  patched-in selftest that executes the **real** vulnerable function with\n  faithful allocator state — matching the ticket's explicit fallback\n  (\"reproduce the unwind/retry sequence with a patched-in test trigger … state\n  the scope honestly\"). The double-free/UAF depends only on the pointer\n  invalidation bug in `iwl_pcie_rx_free()`, not on hardware behavior: on real\n  hardware the same two calls are reached via nic-init unwind + teardown/retry.\n- Scope: memory corruption only; RCE is not claimed and was not attempted.\n","cve_id":"CVE-2026-93796","cwe_id":"CWE-416","source_url":"https://git.kernel.org/stable/c/2c79d7a7b583050c9f58041465cb46fe3483ab5d","reproduced_at":"2026-09-24T19:27:06.935667+00:00","duration_secs":3340.0,"tool_calls":167,"handoffs":2,"total_cost_usd":5.120794,"agent_costs":{"claim_matcher":0.023142,"judge":0.962774,"learning_policy":0.015593,"repro":2.86583,"support":0.084212,"vuln_variant":1.169243},"cost_breakdown":{"claim_matcher":{"gpt-5.4-mini-2026-03-17":0.023142},"judge":{"gpt-5.6-sol":0.962774},"learning_policy":{"gpt-5.4-mini-2026-03-17":0.015593},"repro":{"accounts/fireworks/models/kimi-k3":2.86583},"support":{"accounts/fireworks/models/kimi-k3":0.084212},"vuln_variant":{"accounts/fireworks/models/kimi-k3":1.169243}},"vulnerable_version_variant_outcome":"unknown","fix_bypass_outcome":"unknown","variant_disclosure_state":"unknown","quality":{"confidence":"medium","idempotent_verified":false,"community_verifications":0},"evidence":{"workflow":{"profile":"known_vulnerability","schema_version":2,"stages":["support","claim_contract","repro","judge","vuln_variant"]}},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-09-24T19:27:07.867637+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":7476,"category":"analysis"},{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":17294,"category":"reproduction_script"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":1657,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":1598,"category":"other"}]}