From: Fuad Tabba <fuad.tabba@linux.dev>
To: maz@kernel.org, oupton@kernel.org, kvmarm@lists.linux.dev,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org
Cc: catalin.marinas@arm.com, will@kernel.org, joey.gouly@arm.com,
seiden@linux.ibm.com, suzuki.poulose@arm.com,
yuzenghui@huawei.com, mark.rutland@arm.com, steven.price@arm.com,
vdonnefort@google.com, qperret@google.com, tabba@google.com
Subject: [PATCH v4 02/18] KVM: arm64: Validate the host vCPU's VM before reading it under pKVM
Date: Thu, 1 Oct 2026 14:56:55 +0100 [thread overview]
Message-ID: <20261001135711.1640520-3-fuad.tabba@linux.dev> (raw)
In-Reply-To: <20261001135711.1640520-1-fuad.tabba@linux.dev>
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")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://lore.kernel.org/all/20260914070536.877D91F000FF@smtp.kernel.org/
Reviewed-by: Vincent Donnefort <vdonnefort@google.com>
Reviewed-by: Joey Gouly <joey.gouly@arm.com>
Cc: stable@vger.kernel.org
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/hyp/exception.c | 5 ++-
arch/arm64/kvm/hyp/include/hyp/adjust_pc.h | 28 ++++++++++++++++
arch/arm64/kvm/hyp/include/nvhe/pkvm.h | 2 ++
arch/arm64/kvm/hyp/nvhe/pkvm.c | 37 ++++++++++++++++++++++
4 files changed, 71 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/kvm/hyp/exception.c b/arch/arm64/kvm/hyp/exception.c
index 754e2dc1df54a..6e60d890afa4a 100644
--- a/arch/arm64/kvm/hyp/exception.c
+++ b/arch/arm64/kvm/hyp/exception.c
@@ -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);
diff --git a/arch/arm64/kvm/hyp/include/hyp/adjust_pc.h b/arch/arm64/kvm/hyp/include/hyp/adjust_pc.h
index 4fdfeabefeb43..f55950ee2a7e4 100644
--- a/arch/arm64/kvm/hyp/include/hyp/adjust_pc.h
+++ b/arch/arm64/kvm/hyp/include/hyp/adjust_pc.h
@@ -13,6 +13,34 @@
#include <asm/kvm_emulate.h>
#include <asm/kvm_host.h>
+#ifdef __KVM_NVHE_HYPERVISOR__
+#include <nvhe/pkvm.h>
+
+/* Under pKVM a host vCPU's ->kvm is host-writable. */
+static inline struct kvm *vcpu_get_kvm(struct kvm_vcpu *vcpu)
+{
+ if (is_protected_kvm_enabled())
+ return pkvm_vcpu_get_kvm(vcpu);
+
+ return kern_hyp_va(vcpu->kvm);
+}
+
+static inline void vcpu_put_kvm(struct kvm_vcpu *vcpu, struct kvm *kvm)
+{
+ if (is_protected_kvm_enabled())
+ pkvm_vcpu_put_kvm(vcpu, 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)) {
diff --git a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
index c904647d2f760..5ddb407149667 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
@@ -83,6 +83,8 @@ struct pkvm_hyp_vcpu *pkvm_load_hyp_vcpu(pkvm_handle_t handle,
unsigned int vcpu_idx);
void pkvm_put_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu);
struct pkvm_hyp_vcpu *pkvm_get_loaded_hyp_vcpu(void);
+struct kvm *pkvm_vcpu_get_kvm(struct kvm_vcpu *vcpu);
+void pkvm_vcpu_put_kvm(struct kvm_vcpu *vcpu, struct kvm *kvm);
struct pkvm_hyp_vm *get_pkvm_hyp_vm(pkvm_handle_t handle);
struct pkvm_hyp_vm *get_np_pkvm_hyp_vm(pkvm_handle_t handle);
diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
index 459bd9eb7e4bc..0e90de74778cf 100644
--- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
+++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
@@ -304,6 +304,43 @@ struct pkvm_hyp_vcpu *pkvm_get_loaded_hyp_vcpu(void)
}
+static struct pkvm_hyp_vm *pkvm_get_loaded_hyp_vm(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;
+}
+
+/*
+ * A loaded vCPU's VM is the hyp VM. An unloaded host vCPU's is mapped at
+ * EL2 only while pinned, so it's read once and pinned; the pin fails for
+ * memory the host isn't sharing.
+ */
+struct kvm *pkvm_vcpu_get_kvm(struct kvm_vcpu *vcpu)
+{
+ struct pkvm_hyp_vm *hyp_vm = pkvm_get_loaded_hyp_vm(vcpu);
+ struct kvm *kvm;
+
+ 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 pkvm_vcpu_put_kvm(struct kvm_vcpu *vcpu, struct kvm *kvm)
+{
+ if (kvm && !pkvm_get_loaded_hyp_vm(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;
--
2.39.5
next prev parent reply other threads:[~2026-10-01 13:57 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-01 13:56 [PATCH v4 00/18] KVM: arm64: Confine protected VM vCPU state to EL2 Fuad Tabba
2026-10-01 13:56 ` [PATCH v4 01/18] KVM: arm64: Sync HCR_EL2.VSE back to the host vCPU under pKVM Fuad Tabba
2026-10-01 13:56 ` Fuad Tabba [this message]
2026-10-01 13:56 ` [PATCH v4 03/18] KVM: arm64: Pin the host vCPU before adjusting its PC " Fuad Tabba
2026-10-01 13:56 ` [PATCH v4 04/18] KVM: arm64: Disable steal time for protected VMs Fuad Tabba
2026-10-01 13:56 ` [PATCH v4 05/18] KVM: arm64: Introduce per-EC entry handlers for pKVM Fuad Tabba
2026-10-01 13:56 ` [PATCH v4 06/18] KVM: arm64: Skip fixed-feature state flush for protected vCPUs Fuad Tabba
2026-10-01 14:19 ` sashiko-bot
2026-10-01 15:06 ` Fuad Tabba
2026-10-01 13:57 ` [PATCH v4 07/18] KVM: arm64: Add {flush,sync}_hyp_timer_state() primitives Fuad Tabba
2026-10-01 13:57 ` [PATCH v4 08/18] KVM: arm64: Add system register reset framework for protected VMs Fuad Tabba
2026-10-01 13:57 ` [PATCH v4 09/18] KVM: arm64: Implement HVC handling for protected guests at EL2 Fuad Tabba
2026-10-01 13:57 ` [PATCH v4 10/18] KVM: arm64: Handle PSCI calls for protected VMs " Fuad Tabba
2026-10-01 13:57 ` [PATCH v4 11/18] KVM: arm64: Restrict KVM_ARM_VCPU_INIT and PSCI version for protected VMs Fuad Tabba
2026-10-01 13:57 ` [PATCH v4 12/18] KVM: arm64: Prevent host PC adjustments for protected vCPUs Fuad Tabba
2026-10-01 14:21 ` sashiko-bot
2026-10-01 15:08 ` Fuad Tabba
2026-10-01 13:57 ` [PATCH v4 13/18] KVM: arm64: Inject an UNDEF at EL2 for unhandled protected guest exits Fuad Tabba
2026-10-01 13:57 ` [PATCH v4 14/18] KVM: arm64: Add per-EC entry/exit state marshalling for protected guests Fuad Tabba
2026-10-01 13:57 ` [PATCH v4 15/18] KVM: arm64: Reject host access to protected VM private state Fuad Tabba
2026-10-01 13:57 ` [PATCH v4 16/18] KVM: arm64: Reject host power-on of a vCPU that EL2 holds powered off Fuad Tabba
2026-10-01 14:18 ` sashiko-bot
2026-10-01 15:10 ` Fuad Tabba
2026-10-01 13:57 ` [PATCH v4 17/18] KVM: arm64: Advertise the capabilities that protected VMs support Fuad Tabba
2026-10-01 13:57 ` [PATCH v4 18/18] KVM: arm64: Document the protected VM userspace API Fuad Tabba
2026-10-01 14:31 ` [PATCH v4 00/18] KVM: arm64: Confine protected VM vCPU state to EL2 Fuad Tabba
2026-10-02 15:55 ` Marc Zyngier
2026-10-03 6:57 ` Fuad Tabba
2026-10-03 8:38 ` Marc Zyngier
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261001135711.1640520-3-fuad.tabba@linux.dev \
--to=fuad.tabba@linux.dev \
--cc=catalin.marinas@arm.com \
--cc=joey.gouly@arm.com \
--cc=kvmarm@lists.linux.dev \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mark.rutland@arm.com \
--cc=maz@kernel.org \
--cc=oupton@kernel.org \
--cc=qperret@google.com \
--cc=seiden@linux.ibm.com \
--cc=steven.price@arm.com \
--cc=suzuki.poulose@arm.com \
--cc=tabba@google.com \
--cc=vdonnefort@google.com \
--cc=will@kernel.org \
--cc=yuzenghui@huawei.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.