All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] KVM: arm64: Fix host-directed debug for non-protected pKVM guests
@ 2026-07-26 14:36 Fuad Tabba
  2026-07-26 14:36 ` [PATCH 1/2] KVM: arm64: Flush external_mdscr_el1 to the pKVM hyp vCPU Fuad Tabba
  2026-07-26 14:36 ` [PATCH 2/2] KVM: arm64: selftests: Add a userspace watchpoint test Fuad Tabba
  0 siblings, 2 replies; 5+ messages in thread
From: Fuad Tabba @ 2026-07-26 14:36 UTC (permalink / raw)
  To: Marc Zyngier, Oliver Upton
  Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
	Will Deacon, Catalin Marinas, kvmarm, linux-arm-kernel,
	linux-kernel, tabba

Hi folks,

Under pKVM, host-directed hardware debug of a non-protected guest
(KVM_GUESTDBG_USE_HW) and userspace single-step never fire.

When the host owns the debug registers, the world switch loads
MDSCR_EL1 from external_mdscr_el1, where the host's MDE/KDE/SS bits
live. flush_debug_state() copies the guest's debug_owner and the
owner-selected debug register state to the hyp vCPU, but not
external_mdscr_el1, so the field reaches hardware as zero and the debug
exceptions stay disabled. Patch 1 propagates it.

Patch 2 adds a debug-exceptions selftest for a userspace watchpoint,
which the suite did not cover: it checks that the watchpoint fires and
reports the accessed address in debug.arch.far. It fails without patch
1 and passes with it. The existing single-step test passes even when
stepping never fires, so it does not catch this.

Found while adding self-hosted debug support for protected VMs.

Based on Linux 7.2-rc4 (1590cf0329716).

Cheers,
/fuad

Fuad Tabba (2):
  KVM: arm64: Flush external_mdscr_el1 to the pKVM hyp vCPU
  KVM: arm64: selftests: Add a userspace watchpoint test

 arch/arm64/kvm/hyp/nvhe/hyp-main.c            | 10 ++++-
 .../selftests/kvm/arm64/debug-exceptions.c    | 41 +++++++++++++++++++
 2 files changed, 49 insertions(+), 2 deletions(-)


base-commit: 1590cf0329716306e948a8fc29f1d3ee87d3989f
-- 
2.39.5


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/2] KVM: arm64: Flush external_mdscr_el1 to the pKVM hyp vCPU
  2026-07-26 14:36 [PATCH 0/2] KVM: arm64: Fix host-directed debug for non-protected pKVM guests Fuad Tabba
@ 2026-07-26 14:36 ` Fuad Tabba
  2026-07-26 14:54   ` sashiko-bot
  2026-07-26 14:36 ` [PATCH 2/2] KVM: arm64: selftests: Add a userspace watchpoint test Fuad Tabba
  1 sibling, 1 reply; 5+ messages in thread
From: Fuad Tabba @ 2026-07-26 14:36 UTC (permalink / raw)
  To: Marc Zyngier, Oliver Upton
  Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
	Will Deacon, Catalin Marinas, kvmarm, linux-arm-kernel,
	linux-kernel, tabba

flush_debug_state() propagates the guest's debug_owner and the
owner-selected debug register state to the hyp vCPU, but not
external_mdscr_el1. While the host owns the debug registers, the world
switch loads MDSCR_EL1 from external_mdscr_el1 (ctxt_mdscr_el1()),
where the host's KDE/MDE/SS bits live. A non-protected guest under
KVM_GUESTDBG_USE_HW or single-step therefore runs with MDSCR_EL1.MDE/SS
clear in hardware, and its watchpoints, breakpoints and single-step
never fire.

Propagate external_mdscr_el1 to the hyp vCPU alongside the host-owned
debug state.

Fixes: 4ad3a0b87f2ec ("KVM: arm64: Don't hijack guest context MDSCR_EL1")
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
 arch/arm64/kvm/hyp/nvhe/hyp-main.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index d3c69de698f48..104026ee70e80 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -108,10 +108,16 @@ static void flush_debug_state(struct pkvm_hyp_vcpu *hyp_vcpu)
 
 	hyp_vcpu->vcpu.arch.debug_owner = host_vcpu->arch.debug_owner;
 
-	if (kvm_guest_owns_debug_regs(&hyp_vcpu->vcpu))
+	if (kvm_guest_owns_debug_regs(&hyp_vcpu->vcpu)) {
 		hyp_vcpu->vcpu.arch.vcpu_debug_state = host_vcpu->arch.vcpu_debug_state;
-	else if (kvm_host_owns_debug_regs(&hyp_vcpu->vcpu))
+	} else if (kvm_host_owns_debug_regs(&hyp_vcpu->vcpu)) {
 		hyp_vcpu->vcpu.arch.external_debug_state = host_vcpu->arch.external_debug_state;
+		/*
+		 * The world switch loads MDSCR_EL1 from external_mdscr_el1
+		 * (ctxt_mdscr_el1()).
+		 */
+		hyp_vcpu->vcpu.arch.external_mdscr_el1 = host_vcpu->arch.external_mdscr_el1;
+	}
 }
 
 static void sync_debug_state(struct pkvm_hyp_vcpu *hyp_vcpu)
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/2] KVM: arm64: selftests: Add a userspace watchpoint test
  2026-07-26 14:36 [PATCH 0/2] KVM: arm64: Fix host-directed debug for non-protected pKVM guests Fuad Tabba
  2026-07-26 14:36 ` [PATCH 1/2] KVM: arm64: Flush external_mdscr_el1 to the pKVM hyp vCPU Fuad Tabba
@ 2026-07-26 14:36 ` Fuad Tabba
  1 sibling, 0 replies; 5+ messages in thread
From: Fuad Tabba @ 2026-07-26 14:36 UTC (permalink / raw)
  To: Marc Zyngier, Oliver Upton
  Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
	Will Deacon, Catalin Marinas, kvmarm, linux-arm-kernel,
	linux-kernel, tabba

debug-exceptions covers guest self-hosted debug and userspace
single-step, but not a userspace (KVM_GUESTDBG_USE_HW) watchpoint,
whose KVM_EXIT_DEBUG reports the accessed address in debug.arch.far.
Add a test that installs a host-directed write watchpoint and checks
that the reported address matches the accessed variable.

Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
 .../selftests/kvm/arm64/debug-exceptions.c    | 41 +++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/tools/testing/selftests/kvm/arm64/debug-exceptions.c b/tools/testing/selftests/kvm/arm64/debug-exceptions.c
index 3eb4b1b6682dc..7dc5f0b4f6adf 100644
--- a/tools/testing/selftests/kvm/arm64/debug-exceptions.c
+++ b/tools/testing/selftests/kvm/arm64/debug-exceptions.c
@@ -527,6 +527,46 @@ void test_single_step_from_userspace(int test_cnt)
 	kvm_vm_free(vm);
 }
 
+static void guest_code_wp(void)
+{
+	write_data = 'x';
+	GUEST_DONE();
+}
+
+/*
+ * A userspace hardware watchpoint (KVM_GUESTDBG_USE_HW) must fire and report
+ * the accessed address in debug.arch.far, exercising the watchpoint exit path.
+ */
+static void test_watchpoint_from_userspace(void)
+{
+	struct kvm_guest_debug debug = {};
+	struct kvm_vcpu *vcpu;
+	struct kvm_run *run;
+	struct kvm_vm *vm;
+
+	vm = vm_create_with_one_vcpu(&vcpu, guest_code_wp);
+	run = vcpu->run;
+
+	debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW;
+	debug.arch.dbg_wcr[0] = DBGWCR_LEN8 | DBGWCR_RD | DBGWCR_WR |
+				DBGWCR_EL1 | DBGWCR_E;
+	/*
+	 * BAS = 0xff (LEN8) requires a doubleword-aligned DBGWVR; FAR still
+	 * reports the exact accessed byte.
+	 */
+	debug.arch.dbg_wvr[0] = PC(write_data) & ~7UL;
+	vcpu_guest_debug_set(vcpu, &debug);
+
+	vcpu_run(vcpu);
+	TEST_ASSERT(run->exit_reason == KVM_EXIT_DEBUG,
+		    "Expected KVM_EXIT_DEBUG, got %u", run->exit_reason);
+	TEST_ASSERT((u64)run->debug.arch.far == PC(write_data),
+		    "Watchpoint FAR 0x%lx != accessed address 0x%lx",
+		    (u64)run->debug.arch.far, PC(write_data));
+
+	kvm_vm_free(vm);
+}
+
 /*
  * Run debug testing using the various breakpoint#, watchpoint# and
  * context-aware breakpoint# with the given ID_AA64DFR0_EL1 configuration.
@@ -600,6 +640,7 @@ int main(int argc, char *argv[])
 
 	test_guest_debug_exceptions_all(aa64dfr0);
 	test_single_step_from_userspace(ss_iteration);
+	test_watchpoint_from_userspace();
 
 	return 0;
 }
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH 1/2] KVM: arm64: Flush external_mdscr_el1 to the pKVM hyp vCPU
  2026-07-26 14:36 ` [PATCH 1/2] KVM: arm64: Flush external_mdscr_el1 to the pKVM hyp vCPU Fuad Tabba
@ 2026-07-26 14:54   ` sashiko-bot
  2026-07-26 14:58     ` Fuad Tabba
  0 siblings, 1 reply; 5+ messages in thread
From: sashiko-bot @ 2026-07-26 14:54 UTC (permalink / raw)
  To: Fuad Tabba; +Cc: Marc Zyngier, Oliver Upton, kvmarm

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

New issues:
- [Critical] The patch breaks pKVM guest isolation by allowing the host to arm hardware debug exceptions against a protected VM.

Pre-existing issues:
- [Critical] The hypervisor unconditionally copies trap configurations and debug states between the untrusted host and the protected VM on every entry and exit, violating pKVM state isolation and confidentiality.
--

--- Patch [1]: [PATCH 1/2] KVM: arm64: Flush external_mdscr_el1 to the pKVM hyp vCPU ---
commit 11c3469e00f567f8f8ae658ca753847976608a5d
Author: Fuad Tabba <fuad.tabba@linux.dev>

KVM: arm64: Flush external_mdscr_el1 to the pKVM hyp vCPU

This commit propagates the external_mdscr_el1 register state to the pKVM
hyp vCPU during flush_debug_state(). This ensures that a non-protected guest
runs with the correct hardware flags set by the host.

> diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> --- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> +++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> @@ -108,10 +108,16 @@ static void flush_debug_state(struct pkvm_hyp_vcpu *hyp_vcpu)
>  
>  	hyp_vcpu->vcpu.arch.debug_owner = host_vcpu->arch.debug_owner;

[Severity: Critical]
This is a pre-existing issue, but does this allow the host to hijack debug
ownership for a protected VM?

The hypervisor unconditionally copies the untrusted host's debug_owner here
on every guest entry.

Additionally, similar unconditional copying occurs for trap configurations 
and debug states from the host structure:

arch/arm64/kvm/hyp/nvhe/hyp-main.c:flush_hyp_vcpu() {
    ...
    hyp_vcpu->vcpu.arch.mdcr_el2 = host_vcpu->arch.mdcr_el2;
    ...
}

And on guest exit, state is copied back out to the host:

arch/arm64/kvm/hyp/nvhe/hyp-main.c:sync_debug_state() {
    ...
    if (kvm_guest_owns_debug_regs(&hyp_vcpu->vcpu))
        host_vcpu->arch.vcpu_debug_state = hyp_vcpu->vcpu.arch.vcpu_debug_state;
    ...
}

Could this allow the host to reconfigure the protected guest's execution
environment via mdcr_el2, and leak private hardware debug register state 
back to the untrusted host?

> -	if (kvm_guest_owns_debug_regs(&hyp_vcpu->vcpu))
> +	if (kvm_guest_owns_debug_regs(&hyp_vcpu->vcpu)) {
>  		hyp_vcpu->vcpu.arch.vcpu_debug_state = host_vcpu->arch.vcpu_debug_state;
> -	else if (kvm_host_owns_debug_regs(&hyp_vcpu->vcpu))
> +	} else if (kvm_host_owns_debug_regs(&hyp_vcpu->vcpu)) {
>  		hyp_vcpu->vcpu.arch.external_debug_state = host_vcpu->arch.external_debug_state;
> +		/*
> +		 * The world switch loads MDSCR_EL1 from external_mdscr_el1
> +		 * (ctxt_mdscr_el1()).
> +		 */
> +		hyp_vcpu->vcpu.arch.external_mdscr_el1 = host_vcpu->arch.external_mdscr_el1;

[Severity: Critical]
Is this a regression that breaks pKVM guest isolation by allowing the host to 
arm hardware debug exceptions against a protected VM?

By copying external_mdscr_el1 from the untrusted host to the EL2 private
state unconditionally, a malicious host could populate external_mdscr_el1
with debug flags (e.g., MDE=1 and SS=1) prior to KVM_RUN.

The world switch would then load these host-controlled values directly into 
the hardware MDSCR_EL1 register. Under the pKVM threat model, could this 
allow the host to intercept execution, extract register state, and modify 
control flow of a protected VM?

> +	}
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260726143643.303456-1-fuad.tabba@linux.dev?part=1

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH 1/2] KVM: arm64: Flush external_mdscr_el1 to the pKVM hyp vCPU
  2026-07-26 14:54   ` sashiko-bot
@ 2026-07-26 14:58     ` Fuad Tabba
  0 siblings, 0 replies; 5+ messages in thread
From: Fuad Tabba @ 2026-07-26 14:58 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: Marc Zyngier, Oliver Upton, kvmarm

On Sun, 26 Jul 2026 at 15:54, <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
>
> New issues:
> - [Critical] The patch breaks pKVM guest isolation by allowing the host to arm hardware debug exceptions against a protected VM.
>
> Pre-existing issues:
> - [Critical] The hypervisor unconditionally copies trap configurations and debug states between the untrusted host and the protected VM on every entry and exit, violating pKVM state isolation and confidentiality.

For both issues, protected VM state isolation is WIP upstream [1],
coming soon (fingers crossed).

/fuad

[1] https://lore.kernel.org/all/20260715081238.1891918-1-fuad.tabba@linux.dev/

> --
>
> --- Patch [1]: [PATCH 1/2] KVM: arm64: Flush external_mdscr_el1 to the pKVM hyp vCPU ---
> commit 11c3469e00f567f8f8ae658ca753847976608a5d
> Author: Fuad Tabba <fuad.tabba@linux.dev>
>
> KVM: arm64: Flush external_mdscr_el1 to the pKVM hyp vCPU
>
> This commit propagates the external_mdscr_el1 register state to the pKVM
> hyp vCPU during flush_debug_state(). This ensures that a non-protected guest
> runs with the correct hardware flags set by the host.
>
> > diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> > --- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> > +++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> > @@ -108,10 +108,16 @@ static void flush_debug_state(struct pkvm_hyp_vcpu *hyp_vcpu)
> >
> >       hyp_vcpu->vcpu.arch.debug_owner = host_vcpu->arch.debug_owner;
>
> [Severity: Critical]
> This is a pre-existing issue, but does this allow the host to hijack debug
> ownership for a protected VM?
>
> The hypervisor unconditionally copies the untrusted host's debug_owner here
> on every guest entry.
>
> Additionally, similar unconditional copying occurs for trap configurations
> and debug states from the host structure:
>
> arch/arm64/kvm/hyp/nvhe/hyp-main.c:flush_hyp_vcpu() {
>     ...
>     hyp_vcpu->vcpu.arch.mdcr_el2 = host_vcpu->arch.mdcr_el2;
>     ...
> }
>
> And on guest exit, state is copied back out to the host:
>
> arch/arm64/kvm/hyp/nvhe/hyp-main.c:sync_debug_state() {
>     ...
>     if (kvm_guest_owns_debug_regs(&hyp_vcpu->vcpu))
>         host_vcpu->arch.vcpu_debug_state = hyp_vcpu->vcpu.arch.vcpu_debug_state;
>     ...
> }
>
> Could this allow the host to reconfigure the protected guest's execution
> environment via mdcr_el2, and leak private hardware debug register state
> back to the untrusted host?
>
> > -     if (kvm_guest_owns_debug_regs(&hyp_vcpu->vcpu))
> > +     if (kvm_guest_owns_debug_regs(&hyp_vcpu->vcpu)) {
> >               hyp_vcpu->vcpu.arch.vcpu_debug_state = host_vcpu->arch.vcpu_debug_state;
> > -     else if (kvm_host_owns_debug_regs(&hyp_vcpu->vcpu))
> > +     } else if (kvm_host_owns_debug_regs(&hyp_vcpu->vcpu)) {
> >               hyp_vcpu->vcpu.arch.external_debug_state = host_vcpu->arch.external_debug_state;
> > +             /*
> > +              * The world switch loads MDSCR_EL1 from external_mdscr_el1
> > +              * (ctxt_mdscr_el1()).
> > +              */
> > +             hyp_vcpu->vcpu.arch.external_mdscr_el1 = host_vcpu->arch.external_mdscr_el1;
>
> [Severity: Critical]
> Is this a regression that breaks pKVM guest isolation by allowing the host to
> arm hardware debug exceptions against a protected VM?
>
> By copying external_mdscr_el1 from the untrusted host to the EL2 private
> state unconditionally, a malicious host could populate external_mdscr_el1
> with debug flags (e.g., MDE=1 and SS=1) prior to KVM_RUN.
>
> The world switch would then load these host-controlled values directly into
> the hardware MDSCR_EL1 register. Under the pKVM threat model, could this
> allow the host to intercept execution, extract register state, and modify
> control flow of a protected VM?
>
> > +     }
> >  }
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260726143643.303456-1-fuad.tabba@linux.dev?part=1

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-07-26 14:58 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-26 14:36 [PATCH 0/2] KVM: arm64: Fix host-directed debug for non-protected pKVM guests Fuad Tabba
2026-07-26 14:36 ` [PATCH 1/2] KVM: arm64: Flush external_mdscr_el1 to the pKVM hyp vCPU Fuad Tabba
2026-07-26 14:54   ` sashiko-bot
2026-07-26 14:58     ` Fuad Tabba
2026-07-26 14:36 ` [PATCH 2/2] KVM: arm64: selftests: Add a userspace watchpoint test Fuad Tabba

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.