Skip to contentSkip to conversation

KVM: arm64: Fix a couple of pKVM/nVHE TLB invalidation bugs

PATCH Applied Will Deacon 2 participants 8 messages Aug 14, 2024 – Aug 15, 2024

Subsystems (2)
lore
Share & tools
lore

Will Deacon

author maintainer
#0 lore raw

cover KVM: arm64: Fix a couple of pKVM/nVHE TLB invalidation bugs

Hi folks,

These two patches fix a couple of TLB invalidation bugs in pKVM. The first one was spotted by inspection while I was debugging a crash which turned out to be caused by the second problem.

Feedback welcome.

Cheers,

Will

Cc Marc Zyngier <[email protected]>
Cc Oliver Upton <[email protected]>
Cc Fuad Tabba <[email protected]>

--->8

Will Deacon (2):
KVM: arm64: Invalidate EL1&0 TLB entries for all VMIDs in nvhe hyp
init
KVM: arm64: Ensure TLBI uses correct VMID after changing context

 arch/arm64/kvm/hyp/nvhe/hyp-init.S | 2 +-
 arch/arm64/kvm/hyp/nvhe/tlb.c      | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)
Signature · 1 line
2.46.0.76.ge559c4bf1a-goog
author maintainer
#1 lore raw

PATCH 1/2 KVM: arm64: Invalidate EL1&0 TLB entries for all VMIDs in nvhe hyp init

When initialising the nVHE hypervisor, we invalidate potentially stale TLB entries for the EL1&0 regime using a 'vmalls12e1' invalidation. However, this invalidation operation applies only to the active VMID and therefore we could proceed with stale TLB entries for other VMIDs.

Replace the operation with an 'alle1' which applies to all entries for the EL1&0 regime, regardless of the VMID.

Cc Marc Zyngier <[email protected]>
Cc Oliver Upton <[email protected]>
Fixes 1025c8c0c6ac ("KVM: arm64: Wrap the host with a stage 2")
---
 arch/arm64/kvm/hyp/nvhe/hyp-init.S | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
arch/arm64/kvm/hyp/nvhe/hyp-init.S +1 -1
index 07120b37da35..401af1835be6 100644
@@ -130,7 +130,7 @@ alternative_else_nop_endif
 
 	/* Invalidate the stale TLBs from Bootloader */
 	tlbi	alle2
-	tlbi	vmalls12e1
+	tlbi	alle1
 	dsb	sy
 
 	mov_q	x0, INIT_SCTLR_EL2_MMU_ON
Signature · 1 line
2.46.0.76.ge559c4bf1a-goog
author maintainer
#2 lore raw

PATCH 2/2 KVM: arm64: Ensure TLBI uses correct VMID after changing context

In reply to Will Deacon

When the target context passed to enter_vmid_context() matches the current running context, the function returns early without manipulating the registers of the stage-2 MMU. This can result in a stale VMID due to the lack of an ISB instruction in exit_vmid_context() after writing the VTTBR when ARM64_WORKAROUND_SPECULATIVE_AT is not enabled.

For example, with pKVM enabled:

	// Initially running in host context
	enter_vmid_context(guest);
		-> __load_stage2(guest); isb	// Writes VTCR & VTTBR
	exit_vmid_context(guest);
		-> __load_stage2(host);		// Restores VTCR & VTTBR

	enter_vmid_context(host);
		-> Returns early as we're already in host context
	tlbi vmalls12e1is	// !!! Can use the stale VMID as we
				// haven't performed context
				// synchronisation since restoring
				// VTTBR.VMID

Add an unconditional ISB instruction to exit_vmid_context() after restoring the VTTBR. This already existed for the ARM64_WORKAROUND_SPECULATIVE_AT path, so we can simply hoist that onto the common path.

Cc Marc Zyngier <[email protected]>
Cc Oliver Upton <[email protected]>
Cc Fuad Tabba <[email protected]>
Fixes 58f3b0fc3b87 ("KVM: arm64: Support TLB invalidation in guest context")
---
 arch/arm64/kvm/hyp/nvhe/tlb.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
arch/arm64/kvm/hyp/nvhe/tlb.c +3 -3
index ca3c09df8d7c..48da9ca9763f 100644
@@ -132,10 +132,10 @@ static void exit_vmid_context(struct tlb_inv_context *cxt)
 	else
 		__load_host_stage2();
 
-	if (cpus_have_final_cap(ARM64_WORKAROUND_SPECULATIVE_AT)) {
-		/* Ensure write of the old VMID */
-		isb();
+	/* Ensure write of the old VMID */
+	isb();
 
+	if (cpus_have_final_cap(ARM64_WORKAROUND_SPECULATIVE_AT)) {
 		if (!(cxt->sctlr & SCTLR_ELx_M)) {
 			write_sysreg_el1(cxt->sctlr, SYS_SCTLR);
 			isb();
Signature · 1 line
2.46.0.76.ge559c4bf1a-goog
maintainer
#3 lore raw

Re: [PATCH 2/2] KVM: arm64: Ensure TLBI uses correct VMID after changing context

Hi Will,

Quoted 5 lines · Will Deacon

When the target context passed to enter_vmid_context() matches the current running context, the function returns early without manipulating the registers of the stage-2 MMU. This can result in a stale VMID due to the lack of an ISB instruction in exit_vmid_context() after writing the

This refers to the exit_vmid_context() *before* a subsequent enter_vmid_context(), right?

Quoted 1 line

VTTBR when ARM64_WORKAROUND_SPECULATIVE_AT is not enabled.

I'm not sure I completely get the failure mode, so please bear with me.

Quoted 11 lines

For example, with pKVM enabled:

	// Initially running in host context
	enter_vmid_context(guest);
		-> __load_stage2(guest); isb	// Writes VTCR & VTTBR
	exit_vmid_context(guest);
		-> __load_stage2(host);		// Restores VTCR & VTTBR

	enter_vmid_context(host);
		-> Returns early as we're already in host context

Is this the code you are referring to?

	vcpu = host_ctxt->__hyp_running_vcpu;

	[...]

	if (vcpu) {
		/*
		 * We're in guest context. However, for this to work, this needs
		 * to be called from within __kvm_vcpu_run(), which ensures that
		 * __hyp_running_vcpu is set to the current guest vcpu.
		 */
		if (mmu == vcpu->arch.hw_mmu || WARN_ON(mmu != host_s2_mmu))
			return;

		cxt->mmu = vcpu->arch.hw_mmu;
	} else {
		/* We're in host context. */
		if (mmu == host_s2_mmu)
			return;

		cxt->mmu = host_s2_mmu;
	}

So I take it that no vcpu is running at this point, and that we have done a vcpu_put().

Is there an actual path within pKVM that causes a guest TLBI to be followed by a host __kvm_tlb_flush_vmid() *without* a CSE? I can't convinced myself that such a path exist in the current upstream code base.

Quoted 27 lines
	tlbi vmalls12e1is	// !!! Can use the stale VMID as we
				// haven't performed context
				// synchronisation since restoring
				// VTTBR.VMID

Add an unconditional ISB instruction to exit_vmid_context() after restoring the VTTBR. This already existed for the ARM64_WORKAROUND_SPECULATIVE_AT path, so we can simply hoist that onto the common path.

Cc Marc Zyngier <[email protected]>
Cc Oliver Upton <[email protected]>
Cc Fuad Tabba <[email protected]>
Fixes 58f3b0fc3b87 ("KVM: arm64: Support TLB invalidation in guest context")
---
 arch/arm64/kvm/hyp/nvhe/tlb.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
arch/arm64/kvm/hyp/nvhe/tlb.c +0 -0
index ca3c09df8d7c..48da9ca9763f 100644
@@ -132,10 +132,10 @@ static void exit_vmid_context(struct tlb_inv_context *cxt)
 	else
 		__load_host_stage2();
 

arch/arm64/kvm/hyp/nvhe/tlb.c static void exit_vmid_context(struct tlb_inv_context *cxt) · line 141

Quoted 10 lines
arch/arm64/kvm/hyp/nvhe/tlb.c +3 -3
-	if (cpus_have_final_cap(ARM64_WORKAROUND_SPECULATIVE_AT)) {
-		/* Ensure write of the old VMID */
-		isb();
+	/* Ensure write of the old VMID */
+	isb();
 
+	if (cpus_have_final_cap(ARM64_WORKAROUND_SPECULATIVE_AT)) {
 		if (!(cxt->sctlr & SCTLR_ELx_M)) {
 			write_sysreg_el1(cxt->sctlr, SYS_SCTLR);
 			isb();

The patch itself makes sense to me. I'd just like to understand how we end-up in that situation.

Thanks,

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

Re: [PATCH 2/2] KVM: arm64: Ensure TLBI uses correct VMID after changing context

Hey Marc,

Thanks for having a look.

Quoted 10 lines · Marc Zyngier
Quoted 5 lines · Will Deacon

When the target context passed to enter_vmid_context() matches the current running context, the function returns early without manipulating the registers of the stage-2 MMU. This can result in a stale VMID due to the lack of an ISB instruction in exit_vmid_context() after writing the

This refers to the exit_vmid_context() *before* a subsequent enter_vmid_context(), right?

Yes!

Quoted 3 lines
Quoted 1 line

VTTBR when ARM64_WORKAROUND_SPECULATIVE_AT is not enabled.

I'm not sure I completely get the failure mode, so please bear with me.

Of course, it's confused me a tonne of times and writing the commit message was a real pain.

Quoted 26 lines
Quoted 10 lines

For example, with pKVM enabled:

	// Initially running in host context
	enter_vmid_context(guest);
		-> __load_stage2(guest); isb	// Writes VTCR & VTTBR
	exit_vmid_context(guest);
		-> __load_stage2(host);		// Restores VTCR & VTTBR

	enter_vmid_context(host);
		-> Returns early as we're already in host context

Is this the code you are referring to?

	vcpu = host_ctxt->__hyp_running_vcpu;

	[...]

	if (vcpu) {
		/*
		 * We're in guest context. However, for this to work, this needs
		 * to be called from within __kvm_vcpu_run(), which ensures that
		 * __hyp_running_vcpu is set to the current guest vcpu.
		 */
		if (mmu == vcpu->arch.hw_mmu || WARN_ON(mmu != host_s2_mmu))
			return;
Quoted 8 lines
		cxt->mmu = vcpu->arch.hw_mmu;
	} else {
		/* We're in host context. */
		if (mmu == host_s2_mmu)
			return;

		cxt->mmu = host_s2_mmu;
	}

Yes, this is the "early return" where we don't bother touching the MMU because we're already in the correct context.

Quoted 7 lines

So I take it that no vcpu is running at this point, and that we have done a vcpu_put().

Is there an actual path within pKVM that causes a guest TLBI to be followed by a host __kvm_tlb_flush_vmid() *without* a CSE? I can't convinced myself that such a path exist in the current upstream code base.

I think you're right that this can't happen upstream. We see the issue in Android when reclaiming pages from a guest during teardown. That amounts to unmapping pages from the guest, poisoning them and mapping them back into the host. Mapping them into the host can then trigger table -> block conversion and the associated TLB invalidation wasn't effective because it was still using the guest VMID.

We can carry this patch in Android if you prefer, but given that {enter,exit}_vmid_context() are upstream, it would be nice to land the fix so that we don't run into this bug again in future (it took some debugging!).

Cheers,

Will

maintainer
#5 lore raw

Re: [PATCH 2/2] KVM: arm64: Ensure TLBI uses correct VMID after changing context

Quoted 13 lines · Will Deacon
Quoted 5 lines

Is there an actual path within pKVM that causes a guest TLBI to be followed by a host __kvm_tlb_flush_vmid() *without* a CSE? I can't convinced myself that such a path exist in the current upstream code base.

I think you're right that this can't happen upstream. We see the issue in Android when reclaiming pages from a guest during teardown. That amounts to unmapping pages from the guest, poisoning them and mapping them back into the host. Mapping them into the host can then trigger table -> block conversion and the associated TLB invalidation wasn't effective because it was still using the guest VMID.

Quoted 4 lines

We can carry this patch in Android if you prefer, but given that {enter,exit}_vmid_context() are upstream, it would be nice to land the fix so that we don't run into this bug again in future (it took some debugging!).

I think it is definitely worth addressing, and given that this is nVHE only, an extra CSE isn't going to show on the radar.

The question is more whether this is 6.11 or 6.12 material. If that's not an immediate fix for upstream, I'm tempted to queue it for 6.12.

Does this work for you?

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

Re: [PATCH 2/2] KVM: arm64: Ensure TLBI uses correct VMID after changing context

Quoted 20 lines · Marc Zyngier
Quoted 13 lines · Will Deacon
Quoted 5 lines

Is there an actual path within pKVM that causes a guest TLBI to be followed by a host __kvm_tlb_flush_vmid() *without* a CSE? I can't convinced myself that such a path exist in the current upstream code base.

I think you're right that this can't happen upstream. We see the issue in Android when reclaiming pages from a guest during teardown. That amounts to unmapping pages from the guest, poisoning them and mapping them back into the host. Mapping them into the host can then trigger table -> block conversion and the associated TLB invalidation wasn't effective because it was still using the guest VMID.

Quoted 4 lines

We can carry this patch in Android if you prefer, but given that {enter,exit}_vmid_context() are upstream, it would be nice to land the fix so that we don't run into this bug again in future (it took some debugging!).

Quoted 7 lines

I think it is definitely worth addressing, and given that this is nVHE only, an extra CSE isn't going to show on the radar.

The question is more whether this is 6.11 or 6.12 material. If that's not an immediate fix for upstream, I'm tempted to queue it for 6.12.

Does this work for you?

6.12 is absolutely fine, thank you!

Will

maintainer Applied
#7 lore raw
In reply to Will Deacon
Quoted 9 lines · Will Deacon

These two patches fix a couple of TLB invalidation bugs in pKVM. The first one was spotted by inspection while I was debugging a crash which turned out to be caused by the second problem.

Feedback welcome.

Cheers,

[...]

Applied to next, thanks!

[1/2] KVM: arm64: Invalidate EL1&0 TLB entries for all VMIDs in nvhe hyp init

      commit: dc0dddb1d66de88c571cf1a5bc3b484521a578af
[2/2] KVM: arm64: Ensure TLBI uses correct VMID after changing context
      commit: ed49fe5a6fb9c1a1bbbf4b5b648c7d34a756cb6d

Cheers,

	M.
Signature · 1 line
Without deviation from the norm, progress is not possible.