Skip to contentSkip to conversation

KVM: arm64: Reject the stage-2 MMU pointer hypercalls under pKVM

PATCH In review Fuad Tabba 3 participants 6 messages Sep 14 – Sep 15

Subsystems (2)
lore
Share & tools
lore

Fuad Tabba

author reviewer
#0 lore raw

PATCH 1/1 KVM: arm64: Reject the stage-2 MMU pointer hypercalls under pKVM

The five hypercalls that take a struct kvm_s2_mmu pointer remain callable after pKVM finalises, and each dereferences the host pointer in __load_stage2(). Under pKVM none of them has a valid caller: the host walks no guest stage-2, its TLB flushes go through the handle-based __pkvm_tlb_flush_vmid, and kvm_arch_vcpu_load() skips the CPU context flush.

Reject all five when protected mode is enabled.

---

Notes:
No in-tree host path reaches these, so no Fixes: tag.

 arch/arm64/kvm/hyp/nvhe/hyp-main.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
arch/arm64/kvm/hyp/nvhe/hyp-main.c +15 -0
index 9a3b92e626adb..4b0d00e55e2ea 100644
@@ -557,6 +557,9 @@ static void handle___kvm_tlb_flush_vmid_ipa(struct kvm_cpu_context *host_ctxt)
 	DECLARE_REG(phys_addr_t, ipa, host_ctxt, 2);
 	DECLARE_REG(int, level, host_ctxt, 3);
 
+	if (unlikely(is_protected_kvm_enabled()))
+		return;
+
 	__kvm_tlb_flush_vmid_ipa(kern_hyp_va(mmu), ipa, level);
 }
 
@@ -566,6 +569,9 @@ static void handle___kvm_tlb_flush_vmid_ipa_nsh(struct kvm_cpu_context *host_ctx
 	DECLARE_REG(phys_addr_t, ipa, host_ctxt, 2);
 	DECLARE_REG(int, level, host_ctxt, 3);
 
+	if (unlikely(is_protected_kvm_enabled()))
+		return;
+
 	__kvm_tlb_flush_vmid_ipa_nsh(kern_hyp_va(mmu), ipa, level);
 }
 
@@ -576,6 +582,9 @@ handle___kvm_tlb_flush_vmid_range(struct kvm_cpu_context *host_ctxt)
 	DECLARE_REG(phys_addr_t, start, host_ctxt, 2);
 	DECLARE_REG(unsigned long, pages, host_ctxt, 3);
 
+	if (unlikely(is_protected_kvm_enabled()))
+		return;
+
 	__kvm_tlb_flush_vmid_range(kern_hyp_va(mmu), start, pages);
 }
 
@@ -583,6 +592,9 @@ static void handle___kvm_tlb_flush_vmid(struct kvm_cpu_context *host_ctxt)
 {
 	DECLARE_REG(struct kvm_s2_mmu *, mmu, host_ctxt, 1);
 
+	if (unlikely(is_protected_kvm_enabled()))
+		return;
+
 	__kvm_tlb_flush_vmid(kern_hyp_va(mmu));
 }
 
@@ -602,6 +614,9 @@ static void handle___kvm_flush_cpu_context(struct kvm_cpu_context *host_ctxt)
 {
 	DECLARE_REG(struct kvm_s2_mmu *, mmu, host_ctxt, 1);
 
+	if (unlikely(is_protected_kvm_enabled()))
+		return;
+
 	__kvm_flush_cpu_context(kern_hyp_va(mmu));
 }
 

base-commit: fd73f4a6659897191fa0d40695fe370925dd3780

Signature · 1 line
2.39.5
#1 lore raw
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [Critical] Hypercall handlers handle___kvm_adjust_pc, handle___vgic_v5_save_apr, and handle___vgic_v5_restore_vmcr_apr dereference host-provided pointers via kern_hyp_va() without validation, allowing a malicious host to corrupt EL2-private memory under pKVM. --

commit 6af3790e411879ef8c99b26ae2afdc619730e489
Author: Fuad Tabba <[email protected]>

KVM: arm64: Reject the stage-2 MMU pointer hypercalls under pKVM

This patch updates five stage-2 MMU pointer hypercalls to reject execution when pKVM is enabled. It adds an is_protected_kvm_enabled() check to prevent invalid execution paths.

Quoted 4 lines
arch/arm64/kvm/hyp/nvhe/hyp-main.c +0 -0
index 9a3b92e626adb..4b0d00e55e2ea 100644

[ ... ]

arch/arm64/kvm/hyp/nvhe/hyp-main.c:handle___kvm_adjust_pc() {
	DECLARE_REG(struct kvm_vcpu *, vcpu, host_ctxt, 1);

	__kvm_adjust_pc(kern_hyp_va(vcpu));
}

[Severity: Critical]
This is a pre-existing issue, but does this code allow a malicious host to corrupt EL2-private memory?

The unvalidated host pointer vcpu is passed directly to __kvm_adjust_pc which mutates it. An untrusted host could invoke the __KVM_HOST_SMCCC_FUNC___kvm_adjust_pc hypercall with a crafted pointer (e.g., an EL2 private-memory pointer which is idempotent under kern_hyp_va()). The handler handle___kvm_adjust_pc directly passes this unvalidated pointer to the underlying function, which dereferences it. This could allow the host to overwrite arbitrary EL2 memory or leak its contents into system registers.

arch/arm64/kvm/hyp/nvhe/hyp-main.c:handle___vgic_v5_save_apr() {
	DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);

	__vgic_v5_save_apr(kern_hyp_va(cpu_if));
}
arch/arm64/kvm/hyp/nvhe/hyp-main.c:handle___vgic_v5_restore_vmcr_apr() {
	DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);

	__vgic_v5_restore_vmcr_apr(kern_hyp_va(cpu_if));
}

[Severity: Critical]
This is a pre-existing issue, but can these handlers corrupt EL2 memory under pKVM?

Similar to the above, the unvalidated host pointer cpu_if is dereferenced and written to by __vgic_v5_save_apr and __vgic_v5_restore_vmcr_apr. An untrusted host could invoke the corresponding hypercalls with a crafted pointer, allowing a malicious host to corrupt EL2-private memory.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
author reviewer
#2 lore raw
Quoted 5 lines · On Mon, 14 Sept 2026 at 18:58, <[email protected]> wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [Critical] Hypercall handlers handle___kvm_adjust_pc, handle___vgic_v5_save_apr, and handle___vgic_v5_restore_vmcr_apr dereference host-provided pointers via kern_hyp_va() without validation, allowing a malicious host to corrupt EL2-private memory under pKVM.

All three are correct, and all three are already being fixed. handle___kvm_adjust_pc in [1], the GICv5 handlers in [2], and the rest in this patch.

Cheers,
/fuad

[1] https://lore.kernel.org/all/[email protected]/
[2] https://lore.kernel.org/all/[email protected]/

Quoted 61 lines

--

commit 6af3790e411879ef8c99b26ae2afdc619730e489
Author: Fuad Tabba <[email protected]>

KVM: arm64: Reject the stage-2 MMU pointer hypercalls under pKVM

This patch updates five stage-2 MMU pointer hypercalls to reject execution when pKVM is enabled. It adds an is_protected_kvm_enabled() check to prevent invalid execution paths.

Quoted 4 lines
arch/arm64/kvm/hyp/nvhe/hyp-main.c +0 -0
index 9a3b92e626adb..4b0d00e55e2ea 100644

[ ... ]

arch/arm64/kvm/hyp/nvhe/hyp-main.c:handle___kvm_adjust_pc() {
        DECLARE_REG(struct kvm_vcpu *, vcpu, host_ctxt, 1);

        __kvm_adjust_pc(kern_hyp_va(vcpu));
}

[Severity: Critical]
This is a pre-existing issue, but does this code allow a malicious host to corrupt EL2-private memory?

The unvalidated host pointer vcpu is passed directly to __kvm_adjust_pc which mutates it. An untrusted host could invoke the __KVM_HOST_SMCCC_FUNC___kvm_adjust_pc hypercall with a crafted pointer (e.g., an EL2 private-memory pointer which is idempotent under kern_hyp_va()). The handler handle___kvm_adjust_pc directly passes this unvalidated pointer to the underlying function, which dereferences it. This could allow the host to overwrite arbitrary EL2 memory or leak its contents into system registers.

arch/arm64/kvm/hyp/nvhe/hyp-main.c:handle___vgic_v5_save_apr() {
        DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);

        __vgic_v5_save_apr(kern_hyp_va(cpu_if));
}
arch/arm64/kvm/hyp/nvhe/hyp-main.c:handle___vgic_v5_restore_vmcr_apr() {
        DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);

        __vgic_v5_restore_vmcr_apr(kern_hyp_va(cpu_if));
}

[Severity: Critical]
This is a pre-existing issue, but can these handlers corrupt EL2 memory under pKVM?

Similar to the above, the unvalidated host pointer cpu_if is dereferenced and written to by __vgic_v5_save_apr and __vgic_v5_restore_vmcr_apr. An untrusted host could invoke the corresponding hypercalls with a crafted pointer, allowing a malicious host to corrupt EL2-private memory.

--
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1

maintainer
#3 lore raw
Quoted 11 lines · Fuad Tabba
Quoted 5 lines · On Mon, 14 Sept 2026 at 18:58, <[email protected]> wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [Critical] Hypercall handlers handle___kvm_adjust_pc, handle___vgic_v5_save_apr, and handle___vgic_v5_restore_vmcr_apr dereference host-provided pointers via kern_hyp_va() without validation, allowing a malicious host to corrupt EL2-private memory under pKVM.

All three are correct, and all three are already being fixed. handle___kvm_adjust_pc in [1], the GICv5 handlers in [2], and the rest in this patch.

But that's part of the problem. I see a bunch of random patches addressing similar problems, sent without any logic.

You really need to group related patches together, because the only possible outcome of your current way of posting things is that I'll drop something on the floor.

	M.
Signature · 1 line
Without deviation from the norm, progress is not possible.
maintainer
#4 lore raw
In reply to Fuad Tabba
Quoted 66 lines · Fuad Tabba

The five hypercalls that take a struct kvm_s2_mmu pointer remain callable after pKVM finalises, and each dereferences the host pointer in __load_stage2(). Under pKVM none of them has a valid caller: the host walks no guest stage-2, its TLB flushes go through the handle-based __pkvm_tlb_flush_vmid, and kvm_arch_vcpu_load() skips the CPU context flush.

Reject all five when protected mode is enabled.

---

Notes:
No in-tree host path reaches these, so no Fixes: tag.

 arch/arm64/kvm/hyp/nvhe/hyp-main.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
arch/arm64/kvm/hyp/nvhe/hyp-main.c +12 -0
index 9a3b92e626adb..4b0d00e55e2ea 100644
@@ -557,6 +557,9 @@ static void handle___kvm_tlb_flush_vmid_ipa(struct kvm_cpu_context *host_ctxt)
 	DECLARE_REG(phys_addr_t, ipa, host_ctxt, 2);
 	DECLARE_REG(int, level, host_ctxt, 3);
 
+	if (unlikely(is_protected_kvm_enabled()))
+		return;
+
 	__kvm_tlb_flush_vmid_ipa(kern_hyp_va(mmu), ipa, level);
 }
 
@@ -566,6 +569,9 @@ static void handle___kvm_tlb_flush_vmid_ipa_nsh(struct kvm_cpu_context *host_ctx
 	DECLARE_REG(phys_addr_t, ipa, host_ctxt, 2);
 	DECLARE_REG(int, level, host_ctxt, 3);
 
+	if (unlikely(is_protected_kvm_enabled()))
+		return;
+
 	__kvm_tlb_flush_vmid_ipa_nsh(kern_hyp_va(mmu), ipa, level);
 }
 
@@ -576,6 +582,9 @@ handle___kvm_tlb_flush_vmid_range(struct kvm_cpu_context *host_ctxt)
 	DECLARE_REG(phys_addr_t, start, host_ctxt, 2);
 	DECLARE_REG(unsigned long, pages, host_ctxt, 3);
 
+	if (unlikely(is_protected_kvm_enabled()))
+		return;
+
 	__kvm_tlb_flush_vmid_range(kern_hyp_va(mmu), start, pages);
 }
 
@@ -583,6 +592,9 @@ static void handle___kvm_tlb_flush_vmid(struct kvm_cpu_context *host_ctxt)
 {
 	DECLARE_REG(struct kvm_s2_mmu *, mmu, host_ctxt, 1);
 
+	if (unlikely(is_protected_kvm_enabled()))
+		return;
+
 	__kvm_tlb_flush_vmid(kern_hyp_va(mmu));
 }
 

arch/arm64/kvm/hyp/nvhe/hyp-main.c static void handle___kvm_flush_cpu_context(struct kvm_cpu_context *host_ctxt) · line 622

Quoted 9 lines
arch/arm64/kvm/hyp/nvhe/hyp-main.c +3 -0
@@ -602,6 +614,9 @@ static void handle___kvm_flush_cpu_context(struct kvm_cpu_context *host_ctxt)
 {
 	DECLARE_REG(struct kvm_s2_mmu *, mmu, host_ctxt, 1);
 
+	if (unlikely(is_protected_kvm_enabled()))
+		return;
+
 	__kvm_flush_cpu_context(kern_hyp_va(mmu));
 }
 

Anf why not __kvm_flush_vm_context()? It doesn't take a S2 MMU pointer, but it is still logically wrong to let this being called.

And the whole sprinkling of guards like this is just horrible. We already have a filtering mechanism, just make use of it:

arch/arm64/include/asm/kvm_asm.h +5 -5
index bffdeb36da1d2..51bf30b671fe4 100644
@@ -62,6 +62,11 @@ enum __kvm_host_smccc_func {
 	__KVM_HOST_SMCCC_FUNC___kvm_enable_ssbs,
 	__KVM_HOST_SMCCC_FUNC___vgic_v3_init_lrs,
 	__KVM_HOST_SMCCC_FUNC___vgic_v3_get_gic_config,
+	__KVM_HOST_SMCCC_FUNC___kvm_flush_vm_context,
+	__KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid_ipa,
+	__KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid_ipa_nsh,
+	__KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid,
+	__KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid_range,
 
 	MARKER(__KVM_HOST_SMCCC_FUNC_MIN_PKVM),
 
@@ -70,11 +75,6 @@ enum __kvm_host_smccc_func {
 	/* Hypercalls that are always available and common to [nh]VHE/pKVM. */
 	__KVM_HOST_SMCCC_FUNC___kvm_adjust_pc,
 	__KVM_HOST_SMCCC_FUNC___kvm_vcpu_run,
-	__KVM_HOST_SMCCC_FUNC___kvm_flush_vm_context,
-	__KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid_ipa,
-	__KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid_ipa_nsh,
-	__KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid,
-	__KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid_range,
 	__KVM_HOST_SMCCC_FUNC___kvm_flush_cpu_context,
 	__KVM_HOST_SMCCC_FUNC___kvm_timer_set_cntvoff,
 	__KVM_HOST_SMCCC_FUNC___tracing_load,

Thanks,

	M.
Signature · 1 line
Without deviation from the norm, progress is not possible.
author reviewer
#5 lore raw

Hi Marc,

On Tue, 15 Sept 2026 at 10:24, Marc Zyngier <[email protected]> wrote:
[...]

Quoted 4 lines
Quoted 4 lines
(quoted diff) +0 -0
@@ -602,6 +614,9 @@ static void handle___kvm_flush_cpu_context(struct kvm_cpu_context *host_ctxt)
 {
      DECLARE_REG(struct kvm_s2_mmu *, mmu, host_ctxt, 1);
 
Quoted 9 lines

Review static void handle___kvm_flush_cpu_context(struct kvm_cpu_context *host_ctxt) · line 622

Quoted 6 lines
(quoted diff) +3 -0
+     if (unlikely(is_protected_kvm_enabled()))
+             return;
+
      __kvm_flush_cpu_context(kern_hyp_va(mmu));
 }
 

Anf why not __kvm_flush_vm_context()? It doesn't take a S2 MMU pointer, but it is still logically wrong to let this being called.

I'll respin these, as you suggest, and consolidate them. I'll mention in the cover letter which series/patches the new one supersedes.

Sorry for the noise,
/fuad

Quoted 40 lines

And the whole sprinkling of guards like this is just horrible. We already have a filtering mechanism, just make use of it:

arch/arm64/include/asm/kvm_asm.h +5 -5
index bffdeb36da1d2..51bf30b671fe4 100644
@@ -62,6 +62,11 @@ enum __kvm_host_smccc_func {
        __KVM_HOST_SMCCC_FUNC___kvm_enable_ssbs,
        __KVM_HOST_SMCCC_FUNC___vgic_v3_init_lrs,
        __KVM_HOST_SMCCC_FUNC___vgic_v3_get_gic_config,
+       __KVM_HOST_SMCCC_FUNC___kvm_flush_vm_context,
+       __KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid_ipa,
+       __KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid_ipa_nsh,
+       __KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid,
+       __KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid_range,
 
        MARKER(__KVM_HOST_SMCCC_FUNC_MIN_PKVM),
 
@@ -70,11 +75,6 @@ enum __kvm_host_smccc_func {
        /* Hypercalls that are always available and common to [nh]VHE/pKVM. */
        __KVM_HOST_SMCCC_FUNC___kvm_adjust_pc,
        __KVM_HOST_SMCCC_FUNC___kvm_vcpu_run,
-       __KVM_HOST_SMCCC_FUNC___kvm_flush_vm_context,
-       __KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid_ipa,
-       __KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid_ipa_nsh,
-       __KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid,
-       __KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid_range,
        __KVM_HOST_SMCCC_FUNC___kvm_flush_cpu_context,
        __KVM_HOST_SMCCC_FUNC___kvm_timer_set_cntvoff,
        __KVM_HOST_SMCCC_FUNC___tracing_load,

Thanks,

        M.

--
Without deviation from the norm, progress is not possible.