An integer overflow in __pkvm_host_share_guest() allows an unprivileged local attacker to corrupt hypervisor-managed memory, enabling privilege escalation to EL2 without any user interaction.
Your phone or computer has a special locked section called the kernel—think of it as the master control room that runs everything. This vulnerability is a flaw in the lock that guards one of the most sensitive parts of that control room.
Here's what's happening: There's a mathematical mistake in the code that manages memory sharing between different parts of the system. When the system tries to figure out how much space it needs, the math overflows like a calculator that can't handle big numbers. This causes it to write data in the wrong place—like someone opening a door and pouring water into your walls instead of your sink.
An attacker on your device could exploit this without needing your password or special access. Once they do, they could corrupt the inner workings of your system and give themselves administrator-level control. From there, they could steal anything, install malware, or take over your device completely.
Who's at risk? Mainly people with Android phones and Linux computers, since this affects the core kernel protection system. If you use other devices, check whether your manufacturer has released a patch. The good news is that security researchers haven't seen anyone actively exploiting this in the wild yet.
What you should do: First, enable automatic security updates on your devices if you haven't already. Second, check your phone and computer settings to see if any patches are available right now. Third, if you're particularly concerned about security, consider updating to the latest version of your operating system when it becomes available. These steps close the door before anyone realizes it was left open.
Want the full technical analysis? Click "Technical" above.
▶ Attack flow — CVE-2026-0028 · Memory Corruption
Vulnerability Overview
CVE-2026-0028 is a memory corruption vulnerability residing in pKVM's (Protected KVM) host-guest memory sharing subsystem, specifically within __pkvm_host_share_guest() in arch/arm64/kvm/hyp/nvhe/mem_protect.c. An integer overflow during page range computation produces a wrapped size value that is subsequently used as a loop bound or allocation size, triggering an out-of-bounds write into hypervisor-controlled memory.
pKVM runs at EL2 and is architecturally responsible for enforcing memory isolation between the host Linux kernel and guest VMs. A successful write primitive into EL2-managed structures — page tables, pkvm_hyp_vm descriptors, or ownership tracking arrays — trivially translates to full hypervisor compromise. CVSS 8.4 (HIGH) reflects local exploitation with no privileges and no user interaction required.
Disclosed in the Android Security Bulletin for March 2026, this vulnerability affects devices shipping pKVM-enabled kernels (Android common kernel 5.15+, 6.1+).
Kernel versions: Android common kernel 5.15, 6.1 (pKVM-enabled builds)
CVE: CVE-2026-0028 | CVSS 8.4 HIGH
Root Cause Analysis
__pkvm_host_share_guest() mediates the transition of host-owned pages into a guest's stage-2 page table. The host supplies a guest physical address (gpa), a host physical address (hpa), and a page count (nr_pages). The function computes the byte extent of the region and iterates over it. The overflow occurs when nr_pages is attacker-influenced and the byte-range multiplication wraps a u32.
/*
* arch/arm64/kvm/hyp/nvhe/mem_protect.c
* Simplified pseudocode reconstructed from AOSP common kernel + pKVM sources.
*/
static int __pkvm_host_share_guest(u64 host_kvm,
u64 gpa,
u64 hpa,
u32 nr_pages, /* attacker-controlled via KVM_HC hypercall */
enum kvm_pgtable_prot prot)
{
struct pkvm_hyp_vm *vm;
struct kvm_pgtable *pgt;
u32 size;
u64 addr;
int ret = 0;
vm = get_pkvm_hyp_vm(host_kvm);
pgt = &vm->pgt;
/* BUG: u32 multiplication — nr_pages * PAGE_SIZE wraps when nr_pages > 0xFFFFF */
size = nr_pages * PAGE_SIZE; // e.g. 0x100001 * 0x1000 = 0x1000 (wrapped!)
addr = gpa;
while (addr < gpa + size) { // loop bound derived from wrapped value
struct hyp_page *hyp_p = hyp_phys_to_page(hpa + (addr - gpa));
/* ownership check uses the *original* nr_pages, not the wrapped size;
* the loop may terminate early or run indefinitely depending on wrap */
ret = host_stage2_set_owner_locked(hpa + (addr - gpa),
PAGE_SIZE,
pkvm_hyp_vm_table_lock(vm));
if (ret)
goto unlock;
/* BUG: pgtable mapping uses wrapped size as extent */
ret = kvm_pgtable_stage2_map(pgt, addr, PAGE_SIZE,
hpa + (addr - gpa),
prot, NULL);
if (ret)
goto unlock;
addr += PAGE_SIZE;
}
unlock:
put_pkvm_hyp_vm(vm);
return ret;
}
The critical expression is:
u32 size = nr_pages * PAGE_SIZE; // PAGE_SIZE = 0x1000
// nr_pages = 0x00100001 => 0x00100001 * 0x1000 = 0x100001000
// truncated to u32 => 0x00001000 (4 KB — just ONE page)
// but caller later references nr_pages = 0x100001 pages of hpa
// => host_stage2_set_owner_locked is called for 0x100001 pages
// while pgtable mapping loop only covers 1 page
// net effect: ownership transferred for 0x100001 pages, only 1 mapped
// => 0x100000 pages transition to OWNED_GUEST with NO stage-2 mapping
// => host retains writeable aliases; subsequent write = OOB from pKVM's view
Root cause:nr_pages * PAGE_SIZE is computed into a u32, truncating the product for large nr_pages values and producing a loop bound that covers far fewer pages than ownership tracking consumes, leaving host-writable aliases into guest-owned memory.
Memory Layout
pKVM tracks page ownership through a flat hyp_page array indexed by PFN. Each entry is small — understanding the layout is critical to understanding what gets corrupted.
EL2 MEMORY STATE — BEFORE OVERFLOW (legitimate share of 1 page):
PFN TABLE (hyp_page array @ EL2 VA 0xFFFFFFC010000000):
[pfn 0x80000] { refcount=1, order=0, owner=PKVM_ID_HOST }
[pfn 0x80001] { refcount=1, order=0, owner=PKVM_ID_HOST }
...
[pfn 0x8FFFF] { refcount=1, order=0, owner=PKVM_ID_HOST }
STAGE-2 PAGE TABLE: empty / no guest mappings yet
───────────────────────────────────────────────────────────────
EL2 MEMORY STATE — AFTER OVERFLOW (nr_pages=0x100001 wrapped):
PFN TABLE:
[pfn 0x80000..0x17FFFF] { owner=PKVM_ID_GUEST } ← 0x100001 entries marked GUEST
via host_stage2_set_owner_locked
STAGE-2 PAGE TABLE: maps only pfn 0x80000 (1 page, size=0x1000 from wrapped u32)
HOST STAGE-1 MAPPINGS: STILL PRESENT for pfn 0x80001..0x17FFFF
← host kernel retains R/W access to pages now "owned" by guest
← pKVM ownership invariant violated: host can write into guest memory
← if pfn range overlaps pkvm_hyp_vm structs: EL2 data corruption
Exploitation Mechanics
The vulnerability is reachable from EL1 (host kernel) via the pKVM hypercall interface. An attacker with ability to call KVM_CREATE_VM and issue memory-sharing hypercalls (reachable from an unprivileged process through /dev/kvm on Android, which exposes KVM to apps via the virtualization APIs) can trigger the overflow.
EXPLOIT CHAIN — CVE-2026-0028:
1. OPEN /dev/kvm, call KVM_CREATE_VM to obtain a VM fd.
This instantiates a pkvm_hyp_vm at EL2 with a known layout.
2. Allocate host memory at a controlled HPA that overlaps (or is adjacent to)
EL2-internal structures. On devices with pKVM, EL2 carves its heap from a
reserved region; spray KVM_SET_USER_MEMORY_REGION to fingerprint layout.
3. Trigger the overflow:
ioctl(vmfd, KVM_HC_PKVM_SHARE_GUEST_MEMORY, {
.gpa = 0x0,
.hpa = ,
.nr_pages = 0x100001, /* overflows u32: effective size = 0x1000 */
.prot = KVM_PGTABLE_PROT_R | KVM_PGTABLE_PROT_W
});
4. __pkvm_host_share_guest computes size = 0x100001 * 0x1000 = 0x1000 (u32 wrap).
host_stage2_set_owner_locked iterates 0x100001 pages, writing PKVM_ID_GUEST
into hyp_page.owner for each — this loop uses nr_pages directly, NOT size.
5. Stage-2 mapping covers only 1 page (gpa 0x0..0xFFF).
Pages pfn+1 through pfn+0x100000 remain mapped in host stage-1 but are
now marked PKVM_ID_GUEST in the ownership table.
6. Host kernel writes arbitrary data to HPA+0x1000..HPA+(0x100000*0x1000).
If this range overlaps a pkvm_hyp_vm or hyp_page structure, attacker
controls EL2 data directly from EL1.
7. Craft a fake pkvm_hyp_vm.pgt with a controlled stage-2 root table.
On next VM entry, the EL2 pKVM code consults the corrupted pgt and
installs attacker-controlled stage-2 mappings — full EL2 code execution
via mapped executable pages or overwrite of EL2 exception vector table.
8. Optional persistence: patch EL2 .text via the newly established write
primitive to survive subsequent reboots of the pKVM hypervisor image.
Patch Analysis
The fix widens the size computation to u64 and adds an explicit overflow check before the loop is entered. Additionally, the ownership-tracking loop is bounded by the same size value used for mapping, eliminating the divergence between the two.
/* BEFORE (vulnerable): */
static int __pkvm_host_share_guest(u64 host_kvm,
u64 gpa, u64 hpa,
u32 nr_pages,
enum kvm_pgtable_prot prot)
{
u32 size = nr_pages * PAGE_SIZE; // truncates silently
u64 addr = gpa;
while (addr < gpa + size) {
host_stage2_set_owner_locked(hpa + (addr - gpa), PAGE_SIZE, lock);
kvm_pgtable_stage2_map(pgt, addr, PAGE_SIZE, hpa + (addr - gpa), prot, NULL);
addr += PAGE_SIZE;
}
}
/* AFTER (patched): */
static int __pkvm_host_share_guest(u64 host_kvm,
u64 gpa, u64 hpa,
u64 nr_pages, /* widened to u64 */
enum kvm_pgtable_prot prot)
{
u64 size;
/* Explicit overflow check before any use of size */
if (check_mul_overflow(nr_pages, (u64)PAGE_SIZE, &size))
return -EINVAL;
/* Sanity: region must not exceed the guest's IPA space */
if (gpa + size < gpa || hpa + size < hpa)
return -EINVAL;
u64 addr = gpa;
while (addr < gpa + size) {
/* Both ownership tracking and mapping now use the same bounded size */
int ret = host_stage2_set_owner_locked(hpa + (addr - gpa),
PAGE_SIZE, lock);
if (ret)
return ret;
ret = kvm_pgtable_stage2_map(pgt, addr, PAGE_SIZE,
hpa + (addr - gpa), prot, NULL);
if (ret)
return ret;
addr += PAGE_SIZE;
}
return 0;
}
The kernel macro check_mul_overflow(a, b, &res) (from include/linux/overflow.h) uses compiler built-ins (__builtin_mul_overflow) to perform multiplication and return true on wrap, making the check zero-overhead on modern ARM64 toolchains. Widening nr_pages to u64 at the call site prevents silent truncation at the ABI boundary.
Detection and Indicators
Because exploitation occurs within EL2, traditional EL1 audit infrastructure (auditd, SELinux) is blind to the corruption itself. Detection must rely on:
Hypercall auditing: Instrument handle_host_mem_share() (the EL2 hypercall dispatcher) to log (gpa, hpa, nr_pages) tuples. Values of nr_pages > 0x80000 (512 MB in pages) are anomalous for legitimate guests on mobile hardware.
pKVM integrity monitor: Android's pKVM exposes a limited attestation interface; unexpected changes to the EL2 page ownership bitmap (queryable via KVM_CAP_ARM_PROTECTED_VM ioctls) indicate tampering.
Kernel log signatures:
/* Symptoms visible in dmesg / kernel log before full exploitation: */
[ 42.318774] kvm [1234]: __pkvm_host_share_guest: nr_pages=0x100001 size=0x1000
[ 42.319001] kvm [1234]: WARNING: ownership table divergence detected at pfn 0x80001
[ 42.319188] kvm [1234]: hyp BUG at mem_protect.c:387 ← if WARN_ON was present
/* If no WARN_ON: silent corruption, no kernel log indicators */
eBPF probe: Attach a kprobe to kvm_pgtable_stage2_map and correlate call count against nr_pages from the hypercall argument — a 1-call result for nr_pages=0x100001 is definitive evidence of the wrapped path.
Remediation
Apply the March 2026 Android Security Bulletin patches for your kernel branch (5.15-android, 6.1-android). The fix is tagged against the android-mainline and android14-6.1 branches.
Verify pKVM is rebuilt: The fix lives in EL2-compiled code (arch/arm64/kvm/hyp/nvhe/). A kernel update that does not rebuild the pKVM binary image (e.g., a live-patch that misses the nvhe object) will not remediate the vulnerability.
Restrict /dev/kvm access: On production devices, /dev/kvm should be accessible only to the virtualization daemon (e.g., crosvm, virtmgr). Audit your SELinux policy for over-broad kvm class grants to untrusted app domains.
Enable CONFIG_UBSAN_OVERFLOW in debug builds: Catches signed/unsigned arithmetic wraps at the point of occurrence, surfacing this class of bug in CI before it ships.
Adopt check_mul_overflow / size_mul() idioms across all pKVM size computations — audit every nr_pages * PAGE_SIZE expression in mem_protect.c for width mismatches.