Skip to contentSkip to conversation

KVM: arm64: Validate host pointers in __kvm_adjust_pc() under pKVM

PATCH In review Fuad Tabba 3 participants 10 messages Sep 15

Subsystems (2)
lore
Share & tools
lore

Fuad Tabba

author reviewer
#0 lore raw

v2 cover KVM: arm64: Validate host pointers in __kvm_adjust_pc() under pKVM

Hi folks,

Changes since v1 [1]:
- New patch 1: enter_exception64() reads the VM's MTE flag through a host-writable vcpu->kvm that nothing validates. It goes first, since pinning the vCPU alone leaves that read on an unmapped VM. (sashiko)
- Dropped the VM pin the fix carried in v1, patch 1 validating that read at its site.

Two host pointers reach EL2 unvalidated on the __kvm_adjust_pc() path under pKVM, and this series fixes both. The host vCPU isn't mapped at EL2 until its first KVM_RUN pins it, and KVM_SET_VCPU_EVENTS with ext_dabt_pending reaches the hypercall before that. The vcpu->kvm the exception entry reads the MTE flag from is host-writable, and nothing checks it. Either one panics the hypervisor, and on MTE-capable hardware the flag read leaks one bit of hyp memory the host chose, through PSR_TCO.

These are patches 2 and 3 of the pKVM core series [2], which carries them so that it applies as is. They're respun here so they can be applied on their own.

Based on v7.3-rc3 (fd73f4a665989).

Cheers,
/fuad

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

Fuad Tabba (2):
KVM: arm64: Validate the host vCPU's VM before reading it under pKVM
KVM: arm64: Pin the host vCPU before adjusting its PC under pKVM

 arch/arm64/kvm/hyp/exception.c             |  5 ++-
 arch/arm64/kvm/hyp/include/hyp/adjust_pc.h | 18 ++++++++++
 arch/arm64/kvm/hyp/nvhe/hyp-main.c         | 19 ++++++++++-
 arch/arm64/kvm/hyp/nvhe/pkvm.c             | 39 ++++++++++++++++++++++
 4 files changed, 79 insertions(+), 2 deletions(-)

base-commit: fd73f4a6659897191fa0d40695fe370925dd3780

Signature · 1 line
2.39.5
author reviewer
#1 lore raw

PATCH v2 1/2 KVM: arm64: Validate the host vCPU's VM before reading it under pKVM

On an MTE-capable host under pKVM, enter_exception64() reads the VM's MTE flag through vcpu->kvm, which for a host vCPU is a host-writable pointer nothing validates. The host can point it at any address in the hyp linear map and read back bit 1 of that word through PSR_TCO in the vCPU's CPSR, or panic the hypervisor with an unmapped one.

Get the VM through a get/put pair around the read: a loaded vCPU's is the hyp VM, an unloaded host vCPU's is read once and pinned, and a pointer the host never shared leaves TCO clear.

Fixes ea7fc1bb1cd1b ("KVM: arm64: Introduce MTE VM feature")
---
 arch/arm64/kvm/hyp/exception.c             |  5 ++-
 arch/arm64/kvm/hyp/include/hyp/adjust_pc.h | 18 ++++++++++
 arch/arm64/kvm/hyp/nvhe/pkvm.c             | 39 ++++++++++++++++++++++
 3 files changed, 61 insertions(+), 1 deletion(-)
arch/arm64/kvm/hyp/exception.c +4 -1
index 754e2dc1df54a..6e60d890afa4a 100644
@@ -70,6 +70,7 @@ static void enter_exception64(struct kvm_vcpu *vcpu, unsigned long target_mode,
 			      enum exception_type type)
 {
 	unsigned long sctlr, vbar, old, new, mode;
+	struct kvm *kvm;
 	u64 exc_offset;
 
 	mode = *vcpu_cpsr(vcpu) & (PSR_MODE_MASK | PSR_MODE32_BIT);
@@ -109,8 +110,10 @@ static void enter_exception64(struct kvm_vcpu *vcpu, unsigned long target_mode,
 	new |= (old & PSR_C_BIT);
 	new |= (old & PSR_V_BIT);
 
-	if (kvm_has_mte(kern_hyp_va(vcpu->kvm)))
+	kvm = vcpu_get_kvm(vcpu);
+	if (kvm && kvm_has_mte(kvm))
 		new |= PSR_TCO_BIT;
+	vcpu_put_kvm(vcpu, kvm);
 
 	new |= (old & PSR_DIT_BIT);
 
arch/arm64/kvm/hyp/include/hyp/adjust_pc.h +18 -0
index 4fdfeabefeb43..a4fb04faa7d09 100644
@@ -13,6 +13,24 @@
 #include <asm/kvm_emulate.h>
 #include <asm/kvm_host.h>
 
+/*
+ * Under pKVM a host vCPU's ->kvm is host-writable: the nVHE pair
+ * validates it.
+ */
+#ifdef __KVM_NVHE_HYPERVISOR__
+struct kvm *vcpu_get_kvm(struct kvm_vcpu *vcpu);
+void vcpu_put_kvm(struct kvm_vcpu *vcpu, struct kvm *kvm);
+#else
+static inline struct kvm *vcpu_get_kvm(struct kvm_vcpu *vcpu)
+{
+	return vcpu->kvm;
+}
+
+static inline void vcpu_put_kvm(struct kvm_vcpu *vcpu, struct kvm *kvm)
+{
+}
+#endif
+
 static inline void kvm_skip_instr(struct kvm_vcpu *vcpu)
 {
 	if (vcpu_mode_is_32bit(vcpu)) {
arch/arm64/kvm/hyp/nvhe/pkvm.c +39 -0
index 459bd9eb7e4bc..9bdc7a9b84b8c 100644
@@ -11,6 +11,8 @@
 
 #include <asm/kvm_emulate.h>
 
+#include <hyp/adjust_pc.h>
+
 #include <nvhe/mem_protect.h>
 #include <nvhe/memory.h>
 #include <nvhe/pkvm.h>
@@ -304,6 +306,43 @@ struct pkvm_hyp_vcpu *pkvm_get_loaded_hyp_vcpu(void)
 
 }
 
+static struct pkvm_hyp_vm *loaded_hyp_vm_of(struct kvm_vcpu *vcpu)
+{
+	struct pkvm_hyp_vcpu *hyp_vcpu = pkvm_get_loaded_hyp_vcpu();
+
+	if (hyp_vcpu &&
+	    (vcpu == &hyp_vcpu->vcpu || vcpu == hyp_vcpu->host_vcpu))
+		return pkvm_hyp_vcpu_to_hyp_vm(hyp_vcpu);
+
+	return NULL;
+}
+
+/* An unloaded host vCPU's VM is mapped at EL2 only while pinned. */
+struct kvm *vcpu_get_kvm(struct kvm_vcpu *vcpu)
+{
+	struct pkvm_hyp_vm *hyp_vm;
+	struct kvm *kvm;
+
+	if (!is_protected_kvm_enabled())
+		return kern_hyp_va(vcpu->kvm);
+
+	hyp_vm = loaded_hyp_vm_of(vcpu);
+	if (hyp_vm)
+		return &hyp_vm->kvm;
+
+	kvm = kern_hyp_va(READ_ONCE(vcpu->kvm));
+	if (hyp_pin_shared_mem(kvm, kvm + 1))
+		return NULL;
+
+	return kvm;
+}
+
+void vcpu_put_kvm(struct kvm_vcpu *vcpu, struct kvm *kvm)
+{
+	if (kvm && is_protected_kvm_enabled() && !loaded_hyp_vm_of(vcpu))
+		hyp_unpin_shared_mem(kvm, kvm + 1);
+}
+
 struct pkvm_hyp_vm *get_pkvm_hyp_vm(pkvm_handle_t handle)
 {
 	struct pkvm_hyp_vm *hyp_vm;
Signature · 1 line
2.39.5
Reviewed-by on v2 1/2
#2 lore raw

Re: [PATCH v2 1/2] KVM: arm64: Validate the host vCPU's VM before reading it under pKVM

Quoted 88 lines · Fuad Tabba

On an MTE-capable host under pKVM, enter_exception64() reads the VM's MTE flag through vcpu->kvm, which for a host vCPU is a host-writable pointer nothing validates. The host can point it at any address in the hyp linear map and read back bit 1 of that word through PSR_TCO in the vCPU's CPSR, or panic the hypervisor with an unmapped one.

Get the VM through a get/put pair around the read: a loaded vCPU's is the hyp VM, an unloaded host vCPU's is read once and pinned, and a pointer the host never shared leaves TCO clear.

Fixes ea7fc1bb1cd1b ("KVM: arm64: Introduce MTE VM feature")
---
 arch/arm64/kvm/hyp/exception.c             |  5 ++-
 arch/arm64/kvm/hyp/include/hyp/adjust_pc.h | 18 ++++++++++
 arch/arm64/kvm/hyp/nvhe/pkvm.c             | 39 ++++++++++++++++++++++
 3 files changed, 61 insertions(+), 1 deletion(-)
arch/arm64/kvm/hyp/exception.c +4 -1
index 754e2dc1df54a..6e60d890afa4a 100644
@@ -70,6 +70,7 @@ static void enter_exception64(struct kvm_vcpu *vcpu, unsigned long target_mode,
 			      enum exception_type type)
 {
 	unsigned long sctlr, vbar, old, new, mode;
+	struct kvm *kvm;
 	u64 exc_offset;
 
 	mode = *vcpu_cpsr(vcpu) & (PSR_MODE_MASK | PSR_MODE32_BIT);
@@ -109,8 +110,10 @@ static void enter_exception64(struct kvm_vcpu *vcpu, unsigned long target_mode,
 	new |= (old & PSR_C_BIT);
 	new |= (old & PSR_V_BIT);
 
-	if (kvm_has_mte(kern_hyp_va(vcpu->kvm)))
+	kvm = vcpu_get_kvm(vcpu);
+	if (kvm && kvm_has_mte(kvm))
 		new |= PSR_TCO_BIT;
+	vcpu_put_kvm(vcpu, kvm);
 
 	new |= (old & PSR_DIT_BIT);
 
arch/arm64/kvm/hyp/include/hyp/adjust_pc.h +18 -0
index 4fdfeabefeb43..a4fb04faa7d09 100644
@@ -13,6 +13,24 @@
 #include <asm/kvm_emulate.h>
 #include <asm/kvm_host.h>
 
+/*
+ * Under pKVM a host vCPU's ->kvm is host-writable: the nVHE pair
+ * validates it.
+ */
+#ifdef __KVM_NVHE_HYPERVISOR__
+struct kvm *vcpu_get_kvm(struct kvm_vcpu *vcpu);
+void vcpu_put_kvm(struct kvm_vcpu *vcpu, struct kvm *kvm);
+#else
+static inline struct kvm *vcpu_get_kvm(struct kvm_vcpu *vcpu)
+{
+	return vcpu->kvm;
+}
+
+static inline void vcpu_put_kvm(struct kvm_vcpu *vcpu, struct kvm *kvm)
+{
+}
+#endif
+
 static inline void kvm_skip_instr(struct kvm_vcpu *vcpu)
 {
 	if (vcpu_mode_is_32bit(vcpu)) {
arch/arm64/kvm/hyp/nvhe/pkvm.c +2 -0
index 459bd9eb7e4bc..9bdc7a9b84b8c 100644
@@ -11,6 +11,8 @@
 
 #include <asm/kvm_emulate.h>
 
+#include <hyp/adjust_pc.h>
+
 #include <nvhe/mem_protect.h>
 #include <nvhe/memory.h>
 #include <nvhe/pkvm.h>

arch/arm64/kvm/hyp/nvhe/pkvm.c struct pkvm_hyp_vcpu *pkvm_get_loaded_hyp_vcpu(void) · line 309

Quoted 4 lines
arch/arm64/kvm/hyp/nvhe/pkvm.c +1 -0
@@ -304,6 +306,43 @@ struct pkvm_hyp_vcpu *pkvm_get_loaded_hyp_vcpu(void)
 
 }
 
+static struct pkvm_hyp_vm *loaded_hyp_vm_of(struct kvm_vcpu *vcpu)

pkvm_get_loaded_hyp_vm()? to align with the other.

arch/arm64/kvm/hyp/nvhe/pkvm.c struct pkvm_hyp_vcpu *pkvm_get_loaded_hyp_vcpu(void) · line 320

Quoted 11 lines
arch/arm64/kvm/hyp/nvhe/pkvm.c +11 -0
+{
+	struct pkvm_hyp_vcpu *hyp_vcpu = pkvm_get_loaded_hyp_vcpu();
+
+	if (hyp_vcpu &&
+	    (vcpu == &hyp_vcpu->vcpu || vcpu == hyp_vcpu->host_vcpu))
+		return pkvm_hyp_vcpu_to_hyp_vm(hyp_vcpu);
+
+	return NULL;
+}
+
+/* An unloaded host vCPU's VM is mapped at EL2 only while pinned. */

Could we get the last paragraph of the commit here? As this describes quite well what this is doing.

Quoted 15 lines
arch/arm64/kvm/hyp/nvhe/pkvm.c +15 -0
+struct kvm *vcpu_get_kvm(struct kvm_vcpu *vcpu)
+{
+	struct pkvm_hyp_vm *hyp_vm;
+	struct kvm *kvm;
+
+	if (!is_protected_kvm_enabled())
+		return kern_hyp_va(vcpu->kvm);
+
+	hyp_vm = loaded_hyp_vm_of(vcpu);
+	if (hyp_vm)
+		return &hyp_vm->kvm;
+
+	kvm = kern_hyp_va(READ_ONCE(vcpu->kvm));
+	if (hyp_pin_shared_mem(kvm, kvm + 1))
+		return NULL;

arch/arm64/kvm/hyp/nvhe/pkvm.c struct pkvm_hyp_vcpu *pkvm_get_loaded_hyp_vcpu(void) · line 345

Quoted 10 lines
arch/arm64/kvm/hyp/nvhe/pkvm.c +10 -0
+
+	return kvm;
+}
+
+void vcpu_put_kvm(struct kvm_vcpu *vcpu, struct kvm *kvm)
+{
+	if (kvm && is_protected_kvm_enabled() && !loaded_hyp_vm_of(vcpu))
+		hyp_unpin_shared_mem(kvm, kvm + 1);
+}
+

It seems strange for pkvm.c to provides function that aren't prefixed with pkvm_ and declared into adjust_pc.h and not pkvm.h. I wonder if adjust_pc.h should't just

struct kvm *vcpu_get_kvm(struct kvm_vcpu *vcpu)

{
	if (is_protected_kvm_enabled())
		return pkvm_vcpu_get_kvm()
	else
		kern_hyp_va(vcpu->kvm);
}
Quoted 6 lines
arch/arm64/kvm/hyp/nvhe/pkvm.c +0 -0
 struct pkvm_hyp_vm *get_pkvm_hyp_vm(pkvm_handle_t handle)
 {
 	struct pkvm_hyp_vm *hyp_vm;
Signature · 1 line
2.39.5

Beside those nits:

Signature · 1 line
Vincent
author reviewer
#3 lore raw

Re: [PATCH v2 1/2] KVM: arm64: Validate the host vCPU's VM before reading it under pKVM

On Tue, 15 Sept 2026 at 08:34, Vincent Donnefort <[email protected]> wrote:
[...]

Quoted 3 lines

Beside those nits:

Nits addressed, and I'll respin this as part of a consolidated series, as requested by Marc.

Thanks for the review!
/fuad

Quoted 2 lines

--
Vincent

reviewer Reviewed-by on v2 1/2
#4 lore raw

Re: [PATCH v2 1/2] KVM: arm64: Validate the host vCPU's VM before reading it under pKVM

In reply to Fuad Tabba

Hi,

Quoted 34 lines · Fuad Tabba

On an MTE-capable host under pKVM, enter_exception64() reads the VM's MTE flag through vcpu->kvm, which for a host vCPU is a host-writable pointer nothing validates. The host can point it at any address in the hyp linear map and read back bit 1 of that word through PSR_TCO in the vCPU's CPSR, or panic the hypervisor with an unmapped one.

Get the VM through a get/put pair around the read: a loaded vCPU's is the hyp VM, an unloaded host vCPU's is read once and pinned, and a pointer the host never shared leaves TCO clear.

Fixes ea7fc1bb1cd1b ("KVM: arm64: Introduce MTE VM feature")
---
 arch/arm64/kvm/hyp/exception.c             |  5 ++-
 arch/arm64/kvm/hyp/include/hyp/adjust_pc.h | 18 ++++++++++
 arch/arm64/kvm/hyp/nvhe/pkvm.c             | 39 ++++++++++++++++++++++
 3 files changed, 61 insertions(+), 1 deletion(-)
arch/arm64/kvm/hyp/exception.c +1 -0
index 754e2dc1df54a..6e60d890afa4a 100644
@@ -70,6 +70,7 @@ static void enter_exception64(struct kvm_vcpu *vcpu, unsigned long target_mode,
 			      enum exception_type type)
 {
 	unsigned long sctlr, vbar, old, new, mode;
+	struct kvm *kvm;
 	u64 exc_offset;
 
 	mode = *vcpu_cpsr(vcpu) & (PSR_MODE_MASK | PSR_MODE32_BIT);

arch/arm64/kvm/hyp/exception.c static void enter_exception64(struct kvm_vcpu *vcpu, unsigned long target_mode, · line 114

Quoted 6 lines
arch/arm64/kvm/hyp/exception.c +2 -1
@@ -109,8 +110,10 @@ static void enter_exception64(struct kvm_vcpu *vcpu, unsigned long target_mode,
 	new |= (old & PSR_C_BIT);
 	new |= (old & PSR_V_BIT);
 
-	if (kvm_has_mte(kern_hyp_va(vcpu->kvm)))
+	kvm = vcpu_get_kvm(vcpu);
+	if (kvm && kvm_has_mte(kvm))

It's fine to use the "host->kvm" here because even if sets this bit on the host vCPU, the per-entry handlers in the other series won't copy across the TCO bit.
Right?

arch/arm64/kvm/hyp/exception.c static void enter_exception64(struct kvm_vcpu *vcpu, unsigned long target_mode, · line 119

Quoted 5 lines
arch/arm64/kvm/hyp/exception.c +1 -0
 		new |= PSR_TCO_BIT;
+	vcpu_put_kvm(vcpu, kvm);
 
 	new |= (old & PSR_DIT_BIT);
 

[snip]

Agree with the nits that Vincent suggested!

Let's see if this sticks this time:

Thanks,
Joey

author reviewer
#5 lore raw

Re: [PATCH v2 1/2] KVM: arm64: Validate the host vCPU's VM before reading it under pKVM

Hi Joey,

On Tue, 15 Sept 2026 at 11:17, Joey Gouly <[email protected]> wrote:
[...]

Quoted 8 lines

arch/arm64/kvm/hyp/exception.c static void enter_exception64(struct kvm_vcpu *vcpu, unsigned long target_mode, · line 114

Quoted 3 lines
arch/arm64/kvm/hyp/exception.c +2 -1
-     if (kvm_has_mte(kern_hyp_va(vcpu->kvm)))
+     kvm = vcpu_get_kvm(vcpu);
+     if (kvm && kvm_has_mte(kvm))

It's fine to use the "host->kvm" here because even if sets this bit on the host vCPU, the per-entry handlers in the other series won't copy across the TCO bit.
Right?

Yes.

Quoted 6 lines
Quoted 5 lines
              new |= PSR_TCO_BIT;

+ vcpu_put_kvm(vcpu, kvm);

      new |= (old & PSR_DIT_BIT);
Quoted 6 lines

[snip]

Agree with the nits that Vincent suggested!

Let's see if this sticks this time:

Carrying it over for the respin, thanks again!
/fuad

Quoted 2 lines

Thanks,
Joey

author reviewer
#6 lore raw

PATCH v2 2/2 KVM: arm64: Pin the host vCPU before adjusting its PC under pKVM

In reply to Fuad Tabba

Under pKVM, a page the host shares with EL2 is mapped at EL2 only while it's pinned, and the host vCPU is pinned at its first KVM_RUN. KVM_SET_VCPU_EVENTS with ext_dabt_pending reaches __kvm_adjust_pc() at EL2 before that, and dereferencing the unmapped host vCPU panics the hypervisor. Any process holding /dev/kvm on a pKVM host can trigger it.

Pin the host vCPU around the adjustment when no hyp vCPU is loaded for it. A loaded hyp vCPU implies it's pinned. A pin fails only for memory the host never shared, and the request is then dropped like any other bad host pointer.

Fixes efa1368ba9f4b ("KVM: arm64: Commit exceptions from KVM_SET_VCPU_EVENTS immediately")
---
 arch/arm64/kvm/hyp/nvhe/hyp-main.c | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)
arch/arm64/kvm/hyp/nvhe/hyp-main.c +18 -1
index 9a3b92e626adb..4cb7347db2514 100644
@@ -542,8 +542,25 @@ static void handle___pkvm_host_mkyoung_guest(struct kvm_cpu_context *host_ctxt)
 static void handle___kvm_adjust_pc(struct kvm_cpu_context *host_ctxt)
 {
 	DECLARE_REG(struct kvm_vcpu *, vcpu, host_ctxt, 1);
+	struct pkvm_hyp_vcpu *hyp_vcpu;
+	struct kvm_vcpu *host_vcpu;
 
-	__kvm_adjust_pc(kern_hyp_va(vcpu));
+	host_vcpu = __get_host_hyp_vcpus(vcpu, &hyp_vcpu);
+	if (host_vcpu) {
+		__kvm_adjust_pc(host_vcpu);
+		return;
+	}
+
+	/*
+	 * With no hyp vCPU loaded for it, the host vCPU may be unpinned,
+	 * and so unmapped at EL2: its first run pins it.
+	 */
+	host_vcpu = kern_hyp_va(vcpu);
+	if (hyp_pin_shared_mem(host_vcpu, host_vcpu + 1))
+		return;
+
+	__kvm_adjust_pc(host_vcpu);
+	hyp_unpin_shared_mem(host_vcpu, host_vcpu + 1);
 }
 
 static void handle___kvm_flush_vm_context(struct kvm_cpu_context *host_ctxt)
Signature · 1 line
2.39.5
Reviewed-by on v2 2/2
#7 lore raw

Re: [PATCH v2 2/2] KVM: arm64: Pin the host vCPU before adjusting its PC under pKVM

Quoted 52 lines · Fuad Tabba

Under pKVM, a page the host shares with EL2 is mapped at EL2 only while it's pinned, and the host vCPU is pinned at its first KVM_RUN. KVM_SET_VCPU_EVENTS with ext_dabt_pending reaches __kvm_adjust_pc() at EL2 before that, and dereferencing the unmapped host vCPU panics the hypervisor. Any process holding /dev/kvm on a pKVM host can trigger it.

Pin the host vCPU around the adjustment when no hyp vCPU is loaded for it. A loaded hyp vCPU implies it's pinned. A pin fails only for memory the host never shared, and the request is then dropped like any other bad host pointer.

Fixes efa1368ba9f4b ("KVM: arm64: Commit exceptions from KVM_SET_VCPU_EVENTS immediately")
---
 arch/arm64/kvm/hyp/nvhe/hyp-main.c | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)
arch/arm64/kvm/hyp/nvhe/hyp-main.c +18 -1
index 9a3b92e626adb..4cb7347db2514 100644
@@ -542,8 +542,25 @@ static void handle___pkvm_host_mkyoung_guest(struct kvm_cpu_context *host_ctxt)
 static void handle___kvm_adjust_pc(struct kvm_cpu_context *host_ctxt)
 {
 	DECLARE_REG(struct kvm_vcpu *, vcpu, host_ctxt, 1);
+	struct pkvm_hyp_vcpu *hyp_vcpu;
+	struct kvm_vcpu *host_vcpu;
 
-	__kvm_adjust_pc(kern_hyp_va(vcpu));
+	host_vcpu = __get_host_hyp_vcpus(vcpu, &hyp_vcpu);
+	if (host_vcpu) {
+		__kvm_adjust_pc(host_vcpu);
+		return;
+	}
+
+	/*
+	 * With no hyp vCPU loaded for it, the host vCPU may be unpinned,
+	 * and so unmapped at EL2: its first run pins it.
+	 */
+	host_vcpu = kern_hyp_va(vcpu);
+	if (hyp_pin_shared_mem(host_vcpu, host_vcpu + 1))
+		return;
+
+	__kvm_adjust_pc(host_vcpu);
+	hyp_unpin_shared_mem(host_vcpu, host_vcpu + 1);
 }
 
 static void handle___kvm_flush_vm_context(struct kvm_cpu_context *host_ctxt)
Signature · 1 line
2.39.5
Signature · 1 line
Vincent
reviewer Reviewed-by on v2 2/2
#8 lore raw

Re: [PATCH v2 2/2] KVM: arm64: Pin the host vCPU before adjusting its PC under pKVM

In reply to Fuad Tabba

Hi,

Quoted 29 lines · Fuad Tabba

Under pKVM, a page the host shares with EL2 is mapped at EL2 only while it's pinned, and the host vCPU is pinned at its first KVM_RUN. KVM_SET_VCPU_EVENTS with ext_dabt_pending reaches __kvm_adjust_pc() at EL2 before that, and dereferencing the unmapped host vCPU panics the hypervisor. Any process holding /dev/kvm on a pKVM host can trigger it.

Pin the host vCPU around the adjustment when no hyp vCPU is loaded for it. A loaded hyp vCPU implies it's pinned. A pin fails only for memory the host never shared, and the request is then dropped like any other bad host pointer.

Fixes efa1368ba9f4b ("KVM: arm64: Commit exceptions from KVM_SET_VCPU_EVENTS immediately")
---
 arch/arm64/kvm/hyp/nvhe/hyp-main.c | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)
arch/arm64/kvm/hyp/nvhe/hyp-main.c +2 -0
index 9a3b92e626adb..4cb7347db2514 100644
@@ -542,8 +542,25 @@ static void handle___pkvm_host_mkyoung_guest(struct kvm_cpu_context *host_ctxt)
 static void handle___kvm_adjust_pc(struct kvm_cpu_context *host_ctxt)
 {
 	DECLARE_REG(struct kvm_vcpu *, vcpu, host_ctxt, 1);
+	struct pkvm_hyp_vcpu *hyp_vcpu;
+	struct kvm_vcpu *host_vcpu;
 

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

Quoted 10 lines
arch/arm64/kvm/hyp/nvhe/hyp-main.c +9 -1
-	__kvm_adjust_pc(kern_hyp_va(vcpu));
+	host_vcpu = __get_host_hyp_vcpus(vcpu, &hyp_vcpu);
+	if (host_vcpu) {
+		__kvm_adjust_pc(host_vcpu);
+		return;
+	}
+
+	/*
+	 * With no hyp vCPU loaded for it, the host vCPU may be unpinned,
+	 * and so unmapped at EL2: its first run pins it.

* A pin only fails if the memory was never shared, drop the request if
* it is a bad host pointer.

Or something like the commit message says.

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

Quoted 10 lines
arch/arm64/kvm/hyp/nvhe/hyp-main.c +7 -0
+	 */
+	host_vcpu = kern_hyp_va(vcpu);
+	if (hyp_pin_shared_mem(host_vcpu, host_vcpu + 1))
+		return;
+
+	__kvm_adjust_pc(host_vcpu);
+	hyp_unpin_shared_mem(host_vcpu, host_vcpu + 1);
 }
 
 static void handle___kvm_flush_vm_context(struct kvm_cpu_context *host_ctxt)

Thanks,
Joey

author reviewer
#9 lore raw

Re: [PATCH v2 2/2] KVM: arm64: Pin the host vCPU before adjusting its PC under pKVM

Hi Joey,

On Tue, 15 Sept 2026 at 11:48, Joey Gouly <[email protected]> wrote:
[...]

Quoted 4 lines

* A pin only fails if the memory was never shared, drop the request if
* it is a bad host pointer.

Or something like the commit message says.

Ack.

Quoted 9 lines
Quoted 9 lines
arch/arm64/kvm/hyp/nvhe/hyp-main.c +7 -0
+      */
+     host_vcpu = kern_hyp_va(vcpu);
+     if (hyp_pin_shared_mem(host_vcpu, host_vcpu + 1))
+             return;
+
+     __kvm_adjust_pc(host_vcpu);
+     hyp_unpin_shared_mem(host_vcpu, host_vcpu + 1);
 }
Quoted 3 lines
Quoted 1 line

static void handle___kvm_flush_vm_context(struct kvm_cpu_context *host_ctxt)

Thanks!
/fuad

Quoted 3 lines

Thanks,
Joey