CVE-2026-93796: Verified Reproduction
CVE-2026-93796: 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
CVE-2026-93796 is verified against the affected target. Vulnerability class: Use-After-Free. This medium reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00373.
What Is CVE-2026-93796?
CVE-2026-93796 is a medium-severity Use-After-Free vulnerability. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00373).
CVE-2026-93796 Severity
CVE-2026-93796 is rated medium severity.
Medium — meaningful risk under specific conditions. Schedule a fix in the normal cycle.
How to Reproduce CVE-2026-93796
pruva-verify REPRO-2026-00373 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00373/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-93796
- reached the target end-to-end
- crash observed
- medium confidence
local trigger of iwlwifi nic-init failure (iwl_pcie_tx_init) followed by driver teardown/retry; reproduced hardware-independently via an in-driver selftest hook that runs the real iwl_pcie_rx_free() unwind+retry sequence
- iwl_pcie_nic_init
- iwl_pcie_tx_init failure unwind
- iwl_pcie_rx_free (1st free, pointers left non-NULL)
- later teardown/retry
- iwl_pcie_rx_free re-entry
- KASAN UAF read in iwl_pcie_free_rbs_pool, BAD_PAGE from repeated __free_pages, fatal oops in iwl_pcie_free_rxq_dma
How the agent worked
Root Cause and Exploit Chain for CVE-2026-93796
The Linux kernel iwlwifi PCIe transport frees its RX bookkeeping objects in
iwl_pcie_rx_free() (drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/rx.c,
older trees: pcie/rx.c) but never invalidates the pointers on the still-live
struct iwl_trans_pcie. The function's own re-entry guard uses
trans_pcie->rxq == NULL as the "nothing allocated" sentinel, so after a first
free — performed on the nic-init error unwind when iwl_pcie_tx_init() fails
inside iwl_pcie_nic_init() — any later teardown or retry path
(interface down/up, driver unbind/rebind, reprobe, suspend/resume, error
recovery) that calls iwl_pcie_rx_free() again passes the guard and re-frees
rx_pool, global_table, rxq (kfree) and alloc_page (__free_pages),
producing a double-free, and dereferences the freed rx_pool[]/rxq[] arrays,
producing a slab use-after-free (CWE-416, with missing pointer invalidation
CWE-459).
- Component: Linux kernel
iwlwifiPCIe transport (drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/rx.c). - Affected versions:
< 6.12.111;6.13.0 – 6.18.53;6.19.0 – 7.1.x. - Fixed in: 6.12.111 (
6ec5bf430cf7466cb412e47f2761594092781fac), 6.18.53 (3456c5bcc987aeb182e50e30c9b02be8420e9953), upstream 7.2 (2c79d7a7b583050c9f58041465cb46fe3483ab5d). - Consequences: kernel memory corruption (double-free / slab use-after-free) in the WiFi driver teardown path — kernel crash (DoS) and a potentially exploitable heap corruption primitive on systems with Intel WiFi PCIe hardware. This run confirms memory corruption only; no code execution is claimed.
Impact Parity
- Disclosed/claimed maximum impact: memory corruption (double-free / UAF) — KASAN-detectable; the advisory does not claim RCE.
- Reproduced impact this run: on the v6.18.52 KASAN+SLUB_DEBUG+DEBUG_OBJECTS
kernel the second
iwl_pcie_rx_free()produced (a) a KASANuse-after-free8-byte read of the freedrx_pooliniwl_pcie_free_rbs_pool<-iwl_pcie_rx_free[iwlwifi], (b) a BAD_PAGE taint from the repeated__free_pages(alloc_page), and (c) a kernel oops (NULL/wild dereference indma_free_attrs<-iwl_pcie_free_rxq_dma<-iwl_pcie_rx_free) from walking the freedrxq[]state. The identical trigger on the fixed kernel completes cleanly 2/2 (negative control). - Parity:
fullfor the claimed memory-corruption impact class. - Not demonstrated: code execution (out of scope per the advisory/ticket).
Root Cause
iwl_pcie_rx_free() (v6.18.52, rx.c line 1204) ends with:
kfree(trans_pcie->rx_pool);
kfree(trans_pcie->global_table);
kfree(trans_pcie->rxq);
if (trans_pcie->alloc_page)
__free_pages(trans_pcie->alloc_page, trans_pcie->rx_page_order);
None of the four freed members are set to NULL, yet the function starts with:
if (!trans_pcie->rxq) {
IWL_DEBUG_INFO(trans, "Free NULL rx context\n");
return;
}
The first call (nic-init unwind after a TX-init failure) frees everything but
leaves rxq non-NULL, defeating the sentinel. A second call on the same live
transport object walks the freed rxq[]/rx_pool[] arrays
(iwl_pcie_free_rbs_pool() reads rx_pool[i].page, the queue loop reads
rxq->bd/rxq->napi.poll) and then kfree()s the same three pointers again
and re-frees alloc_page — UAF reads plus double-frees.
The fix (upstream 2c79d7a7b583050c9f58041465cb46fe3483ab5d, "wifi: iwlwifi:
pcie: null RX pointers after free", Emmanuel Grumbach, Intel) sets
rx_pool, global_table, rxq and alloc_page to NULL immediately after
freeing, so repeated cleanup and retry paths fail safe on the sentinel.
Reproduction Steps
bundle/repro/reproduction_steps.sh(self-contained; see that file).- The script:
- clones linux-stable
v6.18.52(vulnerable) into the prepared project cache; - verifies
iwl_pcie_rx_free()lacks the pointer-NULLing fix; - because QEMU cannot emulate an Intel WiFi PCIe device (explicitly
anticipated by the ticket), patches a hardware-independent selftest trigger
into the real driver (
pcie/gen1_2/rxfree-selftest.c+ arx_free_selftest=1module-parameter hook iniwl_drv_init()): it builds RX state exactly asiwl_pcie_rx_alloc()does (kcalloc ofrxq/rx_pool/global_table,alloc_pagesforalloc_page, initialised RB-allocator work/lists) and then calls the real, unmodifiediwl_pcie_rx_free()twice — first = nic-init unwind afteriwl_pcie_tx_init()failure, second = later teardown/retry on the same live transport object; - builds a
defconfig+KASAN(outline)+SLUB_DEBUG+DEBUG_OBJECTS kernel withCONFIG_IWLWIFI=m, producing a vulnerableiwlwifi.ko; then applies the stable fix hunk torx.cand rebuilds a fixediwlwifi.koagainst the same kernel; - boots the kernel twice per build in QEMU/KVM with a busybox initramfs that
insmods the module withrx_free_selftest=1, capturing the serial log.
- clones linux-stable
- Expected evidence: vulnerable boots emit
BUG: KASAN: use-after-freeiniwl_pcie_free_rbs_pool<-iwl_pcie_rx_free([iwlwifi]) followed by a BAD_PAGE taint and a fatal oops iniwl_pcie_free_rxq_dma; fixed boots printSELFTEST-COMPLETEwith all four pointers NULLed and no KASAN splat.
Evidence
bundle/logs/vm_vuln_attempt1.log,bundle/logs/vm_vuln_attempt2.log— KASAN splats naming the RX teardown path (2/2 attempts).bundle/logs/vm_fixed_attempt1.log,bundle/logs/vm_fixed_attempt2.log— negative control, selftest completes cleanly (2/2 attempts).bundle/logs/build_bzimage.log,bundle/logs/build_mod_vuln.log,bundle/logs/build_mod_fixed.log— build transcripts.bundle/repro/runtime_manifest.json— runtime evidence manifest with artifact hashes and immutable target identity (git:https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git@8f3741e6feb045da5b406df0a80b42a1adfb289b= v6.18.52).- Environment: x86_64, QEMU/KVM (
-machine accel=kvm:tcg), KASAN outline, SLUB_DEBUG, DEBUG_OBJECTS(_FREE/_TIMERS) enabled.
Recommendations / Next Steps
- Backport/apply upstream
2c79d7a7b583050c9f58041465cb46fe3483ab5d("null RX pointers after free") — already in 6.12.111 / 6.18.53 / 7.2. - Upgrade affected kernels to >= 6.12.111 / >= 6.18.53 / >= 7.2.
- Regression test: an in-tree KUnit or error-injection test that forces
iwl_pcie_tx_init()failure and then re-runs teardown would lock the invariant "rx free is idempotent" in.
Additional Notes
- Idempotency: the script is re-runnable; it restores
rx.cto the vulnerable state, caches the bzImage and both module builds in the project cache, and re-executes fresh QEMU proofs on every run. - Evidence scope: QEMU exposes no Intel WiFi PCIe device, so the trigger is a
patched-in selftest that executes the real vulnerable function with
faithful allocator state — matching the ticket's explicit fallback
("reproduce the unwind/retry sequence with a patched-in test trigger … state
the scope honestly"). The double-free/UAF depends only on the pointer
invalidation bug in
iwl_pcie_rx_free(), not on hardware behavior: on real hardware the same two calls are reached via nic-init unwind + teardown/retry. - Scope: memory corruption only; RCE is not claimed and was not attempted.
CVE-2026-93796 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
nproc; free -g | head -3; ls -l /dev/kvm 2>/dev/null || echo "no kvm"; df -h /data /tmp | tail -3; ls /data/pruva/project-cache/.run-views/46a9f9d7faa0668cd1d746dcecf0182198c4d82681b334903195a1e1072ebad4/ 2>/dev/null; ls /data/pruva/project-cache/.run-views/46a9f9d7faa0668cd1d746dcecf0182198c4d82681b334903195a1e1072ebad4/repo-mirrors/ 2>/dev/null8
total used free shared buff/cache available
Mem: 31 0 24 0 6 30
Swap: 0 0 0
crw-rw---- 1 root kvm 10, 232 Sep 23 14:39 /dev/kvm
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 145G 81G 64G 56% /
/dev/vda1 145G 81G 64G 56% /tmpid; groups; ls /data/pruva/project-cache/.run-views/46a9f9d7faa0668cd1d746dcecf0182198c4d82681b334903195a1e1072ebad4/repo-mirrors/; apt list --installed 2>/dev/null | grep -Ei 'flex|bison|libelf-dev|libssl-dev|bc |busybox|qemu-system' | head -20; which qemu-system-x86_64 gcc make bc flex bison busybox 2>/dev/null{"cwd":"/data/pruva/runs/d4ca1255-f7a4-4d7a-afc0-26790bf36658","duration_ms":342,"exit_code":0,"stderr":"","stdout":"uid=1000(pruva) gid=1001(pruva) groups=1001(pruva),27(sudo),993(kvm),1000(docker)\npruva sudo kvm docker\nbison/noble-updates,noble-security,now 2:3.8.2+dfsg-1ubuntu0.24.04.1 amd64 [installed]\nbusybox-initramfs/noble-updates,noble-security,now 1:1.36.1-6ubuntu3.1 amd64 [installed,automatic]\nbusybox-static/noble-updates,noble-security,now 1:1.36.1-6ubuntu3.1 amd64 [installed,automatic]\nflex/noble,now 2.6.4-8.2build1 amd64 [installed]\nlibelf-dev/noble-updates,noble-security,now 0.190-1.1ubuntu0.1 amd64 [installed]\nlibssl-dev/noble-updates,noble-security,now 3.0.13-0ubuntu3.15 amd64 [installed]\nqemu-system-common/now 1:8.2.2+ds-0ubuntu1.17 amd64 [installed,upgradable to: 1:8.2.2+ds-0ubuntu1.18]\nqemu-system-data/now 1:8.2.2+ds-0ubuntu1.17 all [installed,upgradable to: 1:8.2.2+ds-0ubuntu1.18]\nqemu-system-gui/now 1:8.2.2+ds-0ubuntu1.17 amd64 [installed,upgradable to: 1:8.2.2+ds-0ubuntu1.18]\nqemu-system-modules-opengl/now 1:8.2.2+ds-0ubuntu1.17 amd64 [installed,upgradable to: 1:8.2.2+ds-0ubuntu1.18]\nqemu-system-modules-spice/now 1:8.2.2+ds-0ubuntu1.17 amd64 [insta… [truncated]CACHE=/data/pruva/project-cache/.run-views/46a9f9d7faa0668cd1d746dcecf0182198c4d82681b334903195a1e1072ebad4; ls -la $CACHE/ $CACHE/repo-mirrors/ 2>&1; cat $CACHE/.pruva/cache_manifest.json 2>/dev/null || echo "no manifest"/data/pruva/project-cache/.run-views/46a9f9d7faa0668cd1d746dcecf0182198c4d82681b334903195a1e1072ebad4/: total 16 drwx------ 4 pruva pruva 4096 Sep 24 12:42 . drwx------ 5 pruva pruva 4096 Sep 24 12:42 .. drwx------ 2 pruva pruva 4096 Sep 24 12:42 .pruva drwx------ 2 pruva pruva 4096 Sep 24 12:42 repo-mirrors /data/pruva/project-cache/.run-views/46a9f9d7faa0668cd1d746dcecf0182198c4d82681b334903195a1e1072ebad4/repo-mirrors/:
mkdir -p /tmp/iwl && curl -sL "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/drivers/net/wireless/intel/iwlwifi/pcie/rx.c?h=v6.18.52" -o /tmp/iwl/rx_v61852.c; wc -l /tmp/iwl/rx_v61852.c; grep -n "iwl_pcie_rx_free\|_iwl_pcie_rx_init\|iwl_pcie_rx_init" /tmp/iwl/rx_v61852.c | head156 /tmp/iwl/rx_v61852.c
curl -sL "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/patch/?id=3456c5bcc987aeb182e50e30c9b02be8420e9953" -o /tmp/iwl/fix.patch; head -80 /tmp/iwl/fix.patch{"cwd":"/data/pruva/runs/d4ca1255-f7a4-4d7a-afc0-26790bf36658","duration_ms":97,"exit_code":0,"stderr":"","stdout":"From 3456c5bcc987aeb182e50e30c9b02be8420e9953 Mon Sep 17 00:00:00 2001\nFrom: Emmanuel Grumbach <emmanuel.grumbach@intel.com>\nDate: Tue, 14 Jul 2026 14:20:03 +0300\nSubject: wifi: iwlwifi: pcie: null RX pointers after free\n\n[ Upstream commit 2c79d7a7b583050c9f58041465cb46fe3483ab5d ]\n\nWhen iwl_pcie_tx_init() fails after RX init, nic init unwinds via\niwl_pcie_rx_free().\n\nThe freed RX members stayed non-NULL on the live transport object,\nso later teardown or retry could touch stale RX state.\nSet rx_pool, global_table, rxq, and alloc_page to NULL after free\nto make repeated cleanup and retry paths safe.\n\nAssisted-by: GitHubCopilot:gpt-5.3-codex\nSigned-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>\nLink: https://patch.msgid.link/20260714141909.33e8978d8b36.Ibaedd4b0ce01405b940de7b90223b6d2c5136ffd@changeid\nSigned-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>\nSigned-off-by: Sasha Levin <sashal@kernel.org>\n---\n drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/rx.c | 9 +++++++--\n 1 file changed, 7 insertions(+), 2 deletions(-)\n\ndiff … [truncated]head -5 /tmp/iwl/rx_v61852.c; curl -sL "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/rx.c?h=v6.18.52" -o /tmp/iwl/rx_g12_v61852.c; wc -l /tmp/iwl/rx_g12_v61852.c; grep -n "iwl_pcie_rx_free" /tmp/iwl/rx_g12_v61852.c<!DOCTYPE html> <html lang='en'> <head> <title>kernel/git/stable/linux.git - Linux kernel stable tree</title> <meta name='generator' content='cgit 1.3.1-korg'/> 2488 /tmp/iwl/rx_g12_v61852.c 1204:void iwl_pcie_rx_free(struct iwl_trans *trans)
sed -n 1160,1260p /tmp/iwl/rx_g12_v61852.c{"cwd":"/data/pruva/runs/d4ca1255-f7a4-4d7a-afc0-26790bf36658","duration_ms":3,"exit_code":0,"stderr":"","stdout":"\t\ttrans_pcie->global_table[i] = rxb;\n\t\trxb->vid = (u16)(i + 1);\n\t\trxb->invalid = true;\n\t}\n\n\tiwl_pcie_rxq_alloc_rbs(trans, GFP_KERNEL, def_rxq);\n\n\treturn 0;\n}\n\nint iwl_pcie_rx_init(struct iwl_trans *trans)\n{\n\tstruct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);\n\tint ret = _iwl_pcie_rx_init(trans);\n\n\tif (ret)\n\t\treturn ret;\n\n\tif (trans->mac_cfg->mq_rx_supported)\n\t\tiwl_pcie_rx_mq_hw_init(trans);\n\telse\n\t\tiwl_pcie_rx_hw_init(trans, trans_pcie->rxq);\n\n\tiwl_pcie_rxq_restock(trans, trans_pcie->rxq);\n\n\tspin_lock_bh(&trans_pcie->rxq->lock);\n\tiwl_pcie_rxq_inc_wr_ptr(trans, trans_pcie->rxq);\n\tspin_unlock_bh(&trans_pcie->rxq->lock);\n\n\treturn 0;\n}\n\nint iwl_pcie_gen2_rx_init(struct iwl_trans *trans)\n{\n\t/* Set interrupt coalescing timer to default (2048 usecs) */\n\tiwl_write8(trans, CSR_INT_COALESCING, IWL_HOST_INT_TIMEOUT_DEF);\n\n\t/*\n\t * We don't configure the RFH.\n\t * Restock will be done at alive, after firmware configured the RFH.\n\t */\n\treturn _iwl_pcie_rx_init(trans);\n}\n\nvoid iwl_pcie_rx_free… [truncated]Artifacts and Evidence for CVE-2026-93796
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-93796
FAQ: CVE-2026-93796
Is CVE-2026-93796 exploitable?
How severe is CVE-2026-93796?
What type of vulnerability is CVE-2026-93796?
How can I reproduce CVE-2026-93796?
Is the CVE-2026-93796 reproduction verified?
References for CVE-2026-93796
Authoritative sources for CVE-2026-93796 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.