[PATCH 16/17] KVM: arm64: Reject host power-on of a vCPU that EL2 holds powered off
Fuad Tabba
fuad.tabba at linux.dev
Mon Aug 31 09:34:20 PDT 2026
A protected vCPU's power state belongs to EL2, which changes it only
on the guest's own PSCI calls. KVM_SET_MP_STATE(RUNNABLE) on a vCPU
EL2 holds powered off changes the host's copy alone, and the guest's
next CPU_ON of that vCPU then fails: mp_state is no longer STOPPED, so
the host returns ALREADY_ON, which EL2 turns into INTERNAL_FAILURE.
SUSPENDED has the same effect.
Track at the host whether EL2 holds a protected vCPU powered off and
return -EPERM for both writes in that state. STOPPED stays permitted,
so a VMM can pause a vCPU, and RUNNABLE on a vCPU EL2 has powered on,
so it can resume one.
Signed-off-by: Fuad Tabba <fuad.tabba at linux.dev>
---
arch/arm64/include/asm/kvm_host.h | 2 ++
arch/arm64/kvm/arm.c | 20 ++++++++++++++++++++
arch/arm64/kvm/pkvm.c | 21 +++++++++++++++++----
arch/arm64/kvm/psci.c | 3 +++
4 files changed, 42 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 78a4d387f9fd4..39788c4331260 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -922,6 +922,8 @@ struct kvm_vcpu_arch {
/* vcpu power state */
struct kvm_mp_state mp_state;
spinlock_t mp_state_lock;
+ /* EL2 holds the protected vCPU powered off. Under mp_state_lock. */
+ bool pkvm_powered_off;
/* Cache some mmu pages needed inside spinlock regions */
struct kvm_mmu_memory_cache mmu_page_cache;
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 8caacdd4de020..a96d01b2c5e86 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -777,9 +777,12 @@ static void __kvm_arm_vcpu_power_off(struct kvm_vcpu *vcpu)
kvm_vcpu_kick(vcpu);
}
+/* The guest's own CPU_OFF: EL2 has already powered the vCPU off. */
void kvm_arm_vcpu_power_off(struct kvm_vcpu *vcpu)
{
spin_lock(&vcpu->arch.mp_state_lock);
+ if (kvm_vm_is_protected(vcpu->kvm))
+ vcpu->arch.pkvm_powered_off = true;
__kvm_arm_vcpu_power_off(vcpu);
spin_unlock(&vcpu->arch.mp_state_lock);
}
@@ -801,6 +804,13 @@ static bool kvm_arm_vcpu_suspended(struct kvm_vcpu *vcpu)
return READ_ONCE(vcpu->arch.mp_state.mp_state) == KVM_MP_STATE_SUSPENDED;
}
+/* Only the guest's CPU_ON may start a vCPU EL2 holds powered off. */
+static bool kvm_pkvm_vcpu_is_powered_off(struct kvm_vcpu *vcpu)
+{
+ return kvm_vm_is_protected(vcpu->kvm) &&
+ vcpu->arch.pkvm_powered_off;
+}
+
int kvm_arch_vcpu_ioctl_get_mpstate(struct kvm_vcpu *vcpu,
struct kvm_mp_state *mp_state)
{
@@ -818,12 +828,22 @@ int kvm_arch_vcpu_ioctl_set_mpstate(struct kvm_vcpu *vcpu,
switch (mp_state->mp_state) {
case KVM_MP_STATE_RUNNABLE:
+ if (kvm_pkvm_vcpu_is_powered_off(vcpu)) {
+ ret = -EPERM;
+ break;
+ }
+
WRITE_ONCE(vcpu->arch.mp_state, *mp_state);
break;
case KVM_MP_STATE_STOPPED:
__kvm_arm_vcpu_power_off(vcpu);
break;
case KVM_MP_STATE_SUSPENDED:
+ if (kvm_pkvm_vcpu_is_powered_off(vcpu)) {
+ ret = -EPERM;
+ break;
+ }
+
kvm_arm_vcpu_suspend(vcpu);
break;
default:
diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
index 8e4c6e4bec123..8ae974fec42ba 100644
--- a/arch/arm64/kvm/pkvm.c
+++ b/arch/arm64/kvm/pkvm.c
@@ -118,12 +118,25 @@ static int __pkvm_create_hyp_vcpu(struct kvm_vcpu *vcpu)
return -ENOMEM;
ret = kvm_call_hyp_nvhe(__pkvm_init_vcpu, handle, vcpu, hyp_vcpu);
- if (!ret)
- vcpu_set_flag(vcpu, VCPU_PKVM_FINALIZED);
- else
+ if (ret) {
free_pages_exact(hyp_vcpu, hyp_vcpu_sz);
+ return ret;
+ }
- return ret;
+ /*
+ * Mirror EL2's seeding of power_state from mp_state. The hyp vCPU is
+ * published, so take mp_state_lock against kvm_psci_vcpu_on().
+ */
+ if (kvm_vm_is_protected(vcpu->kvm)) {
+ spin_lock(&vcpu->arch.mp_state_lock);
+ if (kvm_arm_vcpu_stopped(vcpu))
+ vcpu->arch.pkvm_powered_off = true;
+ spin_unlock(&vcpu->arch.mp_state_lock);
+ }
+
+ vcpu_set_flag(vcpu, VCPU_PKVM_FINALIZED);
+
+ return 0;
}
/*
diff --git a/arch/arm64/kvm/psci.c b/arch/arm64/kvm/psci.c
index e3db84400d1f8..208289a08099f 100644
--- a/arch/arm64/kvm/psci.c
+++ b/arch/arm64/kvm/psci.c
@@ -94,6 +94,9 @@ static unsigned long kvm_psci_vcpu_on(struct kvm_vcpu *source_vcpu)
*/
smp_wmb();
+ /* EL2 has committed the protected vCPU to ON_PENDING to get here. */
+ vcpu->arch.pkvm_powered_off = false;
+
WRITE_ONCE(vcpu->arch.mp_state.mp_state, KVM_MP_STATE_RUNNABLE);
kvm_vcpu_wake_up(vcpu);
--
2.39.5
More information about the linux-arm-kernel
mailing list