ITScape — KVM/arm64 vGIC-ITS Guest-to-Host VM Escape (CVE-2026-46316)
Published: 2026-07-27 • Researcher: Hyunwoo Kim (@V4bel)
- Severity
- Critical
- CVE
- CVE-2026-46316 (GHSA-qcxh-2cm7-9fcc)
- Category
- binary
- Affected product
- Linux kernel, KVM/arm64 in-kernel vGIC-ITS (Interrupt Translation Service) emulation (arch/arm64/kvm/vgic/vgic-its.c)
- Affected versions
- Commit 8201d1028caa (2024-04-25, introduction of the ITS translation cache) through commit 13031fb6b835 (2026-06-05, the fix). Confirmed against Linux v7.1-rc6 (the release immediately preceding the patch).
- Disclosed
- 2026-07-27
- Patch status
- Patched
Tags
References
- https://github.com/advisories/GHSA-qcxh-2cm7-9fcc
- https://www.cve.org/CVERecord?id=CVE-2026-46316
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=13031fb6b8357fbbcded2a7f4cba73e4781ee594
- https://www.openwall.com/lists/oss-security/
- https://github.com/V4bel/ITScape
- https://github.com/V4bel/dirtyfrag
- https://projectzero.google/2021/06/an-epyc-escape-case-study-of-kvm.html
Archive entry
intelseclab/poc-archiveOn this page
Metadata
| Field | Value |
|---|---|
| Date Added | 2026-07-27 |
| Last Updated | 2026-07-27 |
| Author / Researcher | Hyunwoo Kim (@V4bel) |
| CVE / Advisory | CVE-2026-46316 (GHSA-qcxh-2cm7-9fcc) |
| Category | binary |
| Severity | Critical |
| CVSS Score | 9.3 (CVSSv3.1, CNA/kernel.org: AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) — the Red Hat ADP scoring rates it lower, 7.0 High (AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H); NVD had not published its own v4.0 assessment at verification time |
| Status | Weaponized |
| Tags | linux-kernel, kvm, arm64, vgic-its, guest-to-host-escape, vm-escape, double-free, use-after-free, kaslr-bypass, heap-grooming, virtualization |
| Related | N/A (author’s prior work: “Dirty Frag” Linux LPE family; “Januscape” / CVE-2026-53359, an x86 KVM guest-to-host escape via Google kvmCTF) |
Affected Target
| Field | Value |
|---|---|
| Software / System | Linux kernel, KVM/arm64 in-kernel vGIC-ITS (Interrupt Translation Service) emulation (arch/arm64/kvm/vgic/vgic-its.c) |
| Versions Affected | Commit 8201d1028caa (2024-04-25, introduction of the ITS translation cache) through commit 13031fb6b835 (2026-06-05, the fix). Confirmed against Linux v7.1-rc6 (the release immediately preceding the patch). |
| Language / Platform | C, Linux kernel, arm64, KVM (in-kernel emulation — independent of QEMU userspace) |
| Authentication Required | Yes — requires guest kernel (EL1) privilege inside an existing, otherwise unprivileged, guest VM |
| Network Access Required | Local only — no network access needed; entirely guest-VM-local MMIO/hypercall activity against the host’s KVM |
Summary
ITScape (CVE-2026-46316) is a use-after-free in the KVM/arm64 in-kernel vGIC-ITS (Interrupt Translation Service) emulation that lets an unprivileged-but-rooted guest VM escape to the host and execute code as the host kernel (i.e., as root on the host), on any arm64 KVM host that exposes GICv3 ITS to guests. Because the bug lives in arch/arm64/kvm/vgic/vgic-its.c inside the host kernel itself — not in QEMU userspace — it is triggered purely by guest-side MMIO/ITS-command activity, is independent of the VMM’s emulation code, and grants host kernel privilege rather than the privilege of a userspace VMM process. To the best of public knowledge this is the first published guest-to-host escape exploit that directly targets in-kernel KVM/arm64, making it a serious threat to any multi-tenant arm64 public cloud or virtualization host that accepts untrusted guest workloads.
Vulnerability Details
Root Cause
KVM/arm64 caches the result of translating a guest’s (DeviceID, EventID) MSI pair into a vgic_irq object in a per-ITS translation cache backed by an xarray. vgic_its_invalidate_cache(), which drains that cache, is:
| |
Two problems compound here. First, this drain path is not serialized against the ITS command handlers (its_lock) or the GITS_CTLR write path (cmd_lock) — it only holds rcu_read_lock. Second, and decisively, vgic_put_irq() is called on irq, the xa_for_each iteration variable, instead of on the value actually returned by xa_erase(). xa_erase() is atomic — when two contexts race to erase the same index, only one gets the real object back and the other gets NULL — but this code drops a reference on the iteration variable unconditionally regardless of which context actually removed the entry. When two vCPUs concurrently drain the same VM-global ITS cache, vgic_put_irq() fires twice on the same vgic_irq, its refcount underflows, and the object (a kmalloc-cg-96 slab object) is freed early via kfree_rcu while still reachable — a textbook double-put-turned-use-after-free.
The drain is reached whenever a guest clears GICR_CTLR.EnableLPIs on its redistributor (vgic_mmio_write_v3r_ctlr() → vgic_its_invalidate_all_caches()). Because the enable/disable transition is gated per-vCPU, the race requires two or more different vCPUs clearing their own EnableLPIs at the same moment so both enter the drain concurrently against the same VM-global cache. A precondition is that the cache must already be populated (entries are created when vgic_its_resolve_lpi() resolves an LPI via xa_store), so the guest first primes the cache with ITS commands (MAPD/MAPTI/INT) before racing the disable.
Attack Vector
Entirely guest-driven, requiring only guest kernel (EL1) privilege inside the target VM (no host-side or QEMU-side cooperation, no network access):
- Prime the translation cache — from the guest, issue ITS commands (MAPD, MAPTI, INT) across two ITS instances mapped to the same intids, so both ITS’s Interrupt Translation Table entries alias the same
vgic_irqobject once resolved. A large device/event-ID matrix (the PoC uses 128 devices × 1024 events ≈ 130,000 keys) deepens the xarray to widen the race window. - Trigger the race — align 4 guest vCPUs on a barrier and have them clear
GICR_CTLR.EnableLPIssimultaneously, driving multiple vCPUs intovgic_its_invalidate_all_caches()concurrently against the shared cache, producing the double-put and an earlykfree_rcufree of thevgic_irqslab object (many guest exits are used to flush the per-CPUkfree_rcusheaf without any host-side sleep). - Guest-driven cross-cache reclaim — fault in a 64MB
guest_memfdregion (mappedMMAP | INIT_SHARED) so the guest directly authors the reclaimedkmalloc-cg-96slab page byte-for-byte as a fully guest-controlled fakevgic_irq. - Two-stage KASLR bypass — abuse the fake object’s
ap_list/target_vcpufields viavgic_queue_irq_unlock()’s native list-insertion path to first leak the real vCPU0 kernel address (via an ITSMOVIaffinity update), then leak an image-relative pointer (the vCPU’spmu.overflow_work.func, seeded during vCPU setup viaKVM_ARM_VCPU_PMU_V3init) to computekimage_voffset. - Arbitrary write via a function-pointer gadget — set the fake object’s
opspointer to a guest-controlledguest_memfdaddress holding a fakeirq_opsstruct whosequeue_irq_unlockslot points at a store gadget (ed_deschedule+0xd4), giving awrite(address, value)primitive used to overwritepoweroff_cmd(/sbin/poweroff→/bin/touch /ITScape) andarp_tbl.gc_work.work.func(neigh_periodic_work→orderly_poweroff). - Delayed trigger for guest-to-host code exec — the triggering vCPU exits cleanly (
do_exit) to heal a preempt-count leak left by the ops-path call, avoiding an RCU deadlock; roughly 15 seconds later the kernel’s own periodic neighbor-GC work fires the hijackedarp_tbl.gc_work.work.func, runningorderly_poweroff()→call_usermodehelper()— executing/bin/touch /ITScapeas root in host kernel/UMH context, on the host filesystem.
Impact
Full guest-to-host virtual machine escape with host kernel-level code execution (not merely userspace-VMM-level), triggerable by an unprivileged-but-guest-rooted tenant with no host cooperation and no network access. On any arm64 KVM host that exposes GICv3 ITS to guests — the default configuration for most arm64 KVM/QEMU deployments — a malicious or compromised guest can fully compromise the hypervisor host, breaking the isolation guarantee that multi-tenant arm64 clouds depend on. Because the vulnerability lives in in-kernel KVM rather than QEMU, it is unaffected by QEMU-side hardening or sandboxing (seccomp, privilege separation, etc.) that mitigates conventional QEMU-escape CVEs.
Environment / Lab Setup
Host: arm64 KVM host, kernel between 8201d1028caa and 13031fb6b835 (e.g. Linux v7.1-rc6)
Emulation: QEMU TCG emulating an arm64 CPU including EL2, so the arm64 host kernel under
test runs as the KVM host inside the emulator (safe, disposable lab setup —
triggering the bug itself has nothing to do with QEMU)
Guest: 1 guest VM "G", 4 vCPUs, 2 vGIC-ITS instances, 64MB guest_memfd region
Tools: gcc / kernel build toolchain, the bundled `kconfig`, KVM selftest framework
(poc.c is built as a KVM selftest binary)Setup Steps
| |
Proof of Concept
See
poc.c,write-up.md(inassets/),build.sh,qemu.sh, andkconfigin this folder — mirrored unmodified from V4bel/ITScape (148 stars, 19 forks at verification time). Verified before ingestion: the full ~1000-linepoc.c(a KVM-selftest-based exploit) and the 375-linewrite-up.mdroot-cause/exploit writeup were read directly and cross-corroborated against GHSA-qcxh-2cm7-9fcc, the oss-security disclosure post, and the AlmaLinux/Rocky kernel patch commit13031fb6b835. No obfuscation, no unrelated network calls, no dropper/scam patterns — this is a genuine, technically deep exploit chain (double-put race → UAF → guest-driven cross-cache reclaim → two-stage KASLR bypass → function-pointer write-what-where → delayed usermode-helper trigger), not a template or a phantom PoC.
Step-by-Step Reproduction
Build the PoC against a v7.1-rc6 kernel’s KVM selftest tree
Shell script1./build.sh <linux>/tools/testing/selftests/kvmBoot the vulnerable arm64 host kernel under QEMU TCG
Shell script1./qemu.sh <kernel-image> <initramfs>Run the PoC binary inside the emulated host (the PoC itself opens
/dev/kvmon that host and creates the guest VM that performs the escape)Shell script1./pocVerify the escape — a successful run creates
/ITScape, owned by root, on the HOST filesystem (i.e., outside the guest VM the PoC created):Shell script1ls -la /ITScape
Exploit Code
Full exploit in
poc.c(~1000 lines, built on the Linux kernel’s KVM selftest framework) — not reproduced/paraphrased here; see the file directly. High-level structure per the upstream write-up:
QEMU TCG: emulates an arm64 CPU (including EL2) so an arm64 kernel runs as the KVM host
└─ arm64 Host Kernel: the KVM host and the escape target
└─ poc: opens the HOST's /dev/kvm and creates one guest VM "G" (uid=1000)
└─ 1. G's guest code (run by poc via KVM_RUN) performs GIC/ITS MMIO
2. traps into the HOST's in-kernel KVM -> double-put -> HOST kernel code-execExpected Output
[*] ...
[+] /ITScape created by the host kernel (owner uid=0). verify: ls -la /ITScape
-rw-r--r-- 1 0 0 0 Jun 9 00:02 /ITScapeScreenshots / Evidence
assets/demo.gif— upstream animated demo of the exploit running end-to-end, culminating in/ITScapeappearing on the host.assets/write-up.md— full 375-line technical root-cause and exploit-chain writeup (mirrored unmodified from upstream).
Detection & Indicators of Compromise
Remediation
| Action | Detail |
|---|---|
| Patch | Apply commit 13031fb6b835 — changes vgic_put_irq() to drop its reference only on the value actually returned by xa_erase() for that context, rather than on the shared xa_for_each iteration variable, eliminating the double-put. Merged into the KVM tree and mainline Linux on 2026-06-05. |
| Workaround | None short of the patch — since the bug is purely guest-triggerable in-kernel KVM code with no QEMU dependency, no VMM-level sandboxing or seccomp policy mitigates it. If patching is not immediately possible, avoid running untrusted/multi-tenant guests on affected arm64 KVM hosts, or disable GICv3 ITS exposure to guests if your workloads do not require it. |
| Verification | Confirm the host kernel includes commit 13031fb6b835 (arch/arm64/kvm/vgic/vgic-its.c) via git log or distro changelog/erratum (e.g. AlmaLinux/Rocky kernel advisories referencing this commit). |
References
- GitHub Security Advisory — GHSA-qcxh-2cm7-9fcc
- CVE Record — CVE-2026-46316
- Fix commit — 13031fb6b835 (arch/arm64/kvm/vgic/vgic-its.c)
- oss-security disclosure post
- Public PoC — V4bel/ITScape
- Author’s prior work — V4bel/dirtyfrag (Dirty Frag LPE)
- Project Zero — An EPYC Escape: Case Study of KVM (source of the delayed-UMH-trigger technique used here)
Notes
Verified before ingestion per this archive’s standing verify-before-ingest policy: the full ~1000-line poc.c exploit source and the 375-line write-up.md technical writeup were read directly in their entirety (not skimmed or taken on the strength of the repo’s README claims alone), and the root cause, patch commit, and disclosure timeline were cross-corroborated against GHSA-qcxh-2cm7-9fcc, the oss-security disclosure post, and the AlmaLinux/Rocky kernel patch commit 13031fb6b835. No obfuscated code, unrelated network calls, or phantom-PoC/dropper patterns were present — the exploit chain (guest-triggered double-put race → UAF → guest-driven cross-cache reclaim via guest_memfd → two-stage KASLR bypass → function-pointer write-what-where via a queue_irq_unlock gadget → delayed usermode-helper trigger through the kernel’s own neighbor-GC workqueue) is internally consistent, technically deep, and matches independent third-party CVE writeups (Tenable, SentinelOne).
The author, Hyunwoo Kim (@V4bel), has a credible and consistent responsible-disclosure track record: this vulnerability was reported to security@kernel.org and the kvmarm mailing list on 2026-06-01, then submitted under a 5-day embargo to linux-distros@openwall.org on 2026-06-05 (per the upstream write-up’s disclosure timeline) before public posting to oss-security on 2026-06-10, following the fix landing in mainline. The same author previously published the “Dirty Frag” Linux kernel LPE family and, per this archive’s own tracking, “Januscape” (CVE-2026-53359), an x86 KVM guest-to-host escape demonstrated via Google’s kvmCTF program — both consistent with genuine, disclosed-responsibly kernel security research rather than opportunistic or malicious publishing.
CVSS scoring is split between sources: the CNA (kernel.org) rates it 9.3 Critical (AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H), while Red Hat’s ADP rates it lower at 7.0 High (AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H) — the difference chiefly reflects differing views on attack complexity (AC) and scope (S) for a guest-to-host escape. NVD had not published its own CVSS v4.0 assessment at verification time. The Severity field above follows the CNA’s Critical rating, consistent with the practical impact of a guest-to-host escape to host kernel privilege.
| |