mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Fuad Tabba <fuad.tabba@linux.dev>
To: Marc Zyngier <maz@kernel.org>, Oliver Upton <oupton@kernel.org>
Cc: Will Deacon <will@kernel.org>,
	Catalin Marinas <catalin.marinas@arm.com>,
	Mark Rutland <mark.rutland@arm.com>,
	Paolo Bonzini <pbonzini@redhat.com>,
	Sean Christopherson <seanjc@google.com>,
	Shuah Khan <shuah@kernel.org>, Joey Gouly <joey.gouly@arm.com>,
	Steffen Eiden <seiden@linux.ibm.com>,
	Suzuki K Poulose <suzuki.poulose@arm.com>,
	Zenghui Yu <yuzenghui@huawei.com>,
	Vincent Donnefort <vdonnefort@google.com>,
	Quentin Perret <qperret@google.com>,
	Fuad Tabba <tabba@google.com>,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, linux-kselftest@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH v2 15/19] KVM: arm64: selftests: Add pkvm_isolation host-access test
Date: Mon,  9 Nov 2026 08:48:50 +0000	[thread overview]
Message-ID: <20261109084854.840576-16-fuad.tabba@linux.dev> (raw)
In-Reply-To: <20261109084854.840576-1-fuad.tabba@linux.dev>

The host keeps its userspace mapping of a protected guest's memory, but
can't reach the data through it. The host kernel's copy_to_user() and
copy_from_user() on that mapping fail. If the host kernel pins a page
and accesses it through its own mapping, as process_vm_readv() does,
EL2 zeroes and reclaims the page, and the guest loses it.

Test each of these, that MADV_DONTNEED and munmap() leave the guest's
pages pinned and accounted, and that a normal VM's fault on them fails.

Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../selftests/kvm/arm64/pkvm_isolation.c      | 225 ++++++++++++++++++
 2 files changed, 226 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/arm64/pkvm_isolation.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 13998964facd5..68bdd01f62270 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -193,6 +193,7 @@ TEST_GEN_PROGS_arm64 += arm64/page_fault_test
 TEST_GEN_PROGS_arm64 += arm64/pkvm_host_abi
 TEST_GEN_PROGS_arm64 += arm64/pkvm_id_regs
 TEST_GEN_PROGS_arm64 += arm64/pkvm_irq
+TEST_GEN_PROGS_arm64 += arm64/pkvm_isolation
 TEST_GEN_PROGS_arm64 += arm64/pkvm_mem_share
 TEST_GEN_PROGS_arm64 += arm64/pkvm_psci
 TEST_GEN_PROGS_arm64 += arm64/pkvm_smccc
diff --git a/tools/testing/selftests/kvm/arm64/pkvm_isolation.c b/tools/testing/selftests/kvm/arm64/pkvm_isolation.c
new file mode 100644
index 0000000000000..f083d0434238a
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/pkvm_isolation.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * pkvm_isolation - what the host can and can't do with a protected VM's memory.
+ *
+ * Copyright (C) 2026 Google LLC
+ * Author: Fuad Tabba <fuad.tabba@linux.dev>
+ */
+
+#include <errno.h>
+#include <sys/mman.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include "kvm_util.h"
+#include "pkvm.h"
+#include "processor.h"
+#include "test_util.h"
+
+#define DATA_MEMSLOT	(PKVM_IDMAP_MEMSLOT + 1)
+#define DATA_GPA	BIT_ULL(30)
+#define DATA_PAGES	16
+#define DATA_SIZE	((size_t)DATA_PAGES * MIN_PAGE_SIZE)
+
+#define GUEST_VAL	0x0badf00ddeadbeefUL
+#define HOST_VAL	0x0123456789abcdefUL
+
+static volatile u64 *page_va(int idx)
+{
+	return (volatile u64 *)(DATA_GPA + (u64)idx * MIN_PAGE_SIZE);
+}
+
+/* The host acts on the guest's memory at the sync, then the guest checks it. */
+static void guest_code(void)
+{
+	int i;
+
+	pkvm_guest_init();
+
+	for (i = 0; i < DATA_PAGES; i++)
+		*page_va(i) = GUEST_VAL + i;
+
+	GUEST_SYNC(0);
+
+	for (i = 0; i < DATA_PAGES; i++)
+		GUEST_ASSERT_EQ(*page_va(i), GUEST_VAL + i);
+
+	GUEST_DONE();
+}
+
+static void guest_np_read(void)
+{
+	*page_va(0);
+	GUEST_DONE();
+}
+
+/* The host kernel's copy to or from private memory faults, and the uaccess fixup fails it. */
+static void host_write_from_private(void *hva)
+{
+	ssize_t ret;
+	int fds[2];
+
+	TEST_ASSERT(!pipe(fds), "pipe: errno %d", errno);
+
+	ret = write(fds[1], hva, MIN_PAGE_SIZE);
+	TEST_ASSERT(ret == -1 && errno == EFAULT,
+		    "write() from private memory: rc %zd errno %d", ret, errno);
+
+	close(fds[0]);
+	close(fds[1]);
+}
+
+static void host_read_into_private(void *hva)
+{
+	u64 val = HOST_VAL;
+	ssize_t ret;
+	int fds[2];
+
+	TEST_ASSERT(!pipe(fds), "pipe: errno %d", errno);
+	TEST_ASSERT_EQ(write(fds[1], &val, sizeof(val)), sizeof(val));
+
+	ret = read(fds[0], hva, sizeof(val));
+	TEST_ASSERT(ret == -1 && errno == EFAULT,
+		    "read() into private memory: rc %zd errno %d", ret, errno);
+
+	close(fds[0]);
+	close(fds[1]);
+}
+
+/* The host's long-term pins keep the guest's pages and their locked-memory accounting. */
+static void host_dontneed(void *hva)
+{
+	size_t locked = pkvm_host_locked_vm();
+
+	kvm_madvise(hva, DATA_SIZE, MADV_DONTNEED);
+	TEST_ASSERT_EQ(pkvm_host_locked_vm(), locked);
+
+	/* The host's mapping now has a new page, which the guest doesn't see. */
+	TEST_ASSERT_EQ(*(u64 *)hva, 0);
+	*(u64 *)hva = HOST_VAL;
+}
+
+static void host_munmap(void *hva)
+{
+	size_t locked = pkvm_host_locked_vm();
+
+	kvm_munmap(hva, DATA_SIZE);
+	TEST_ASSERT_EQ(pkvm_host_locked_vm(), locked);
+}
+
+/* A normal VM's fault on private memory fails, and the protected guest keeps its data. */
+static void host_np_vm_on_private(void *hva)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	int ret;
+
+	vm = vm_create_with_one_vcpu(&vcpu, guest_np_read);
+	vm_set_user_memory_region(vm, DATA_MEMSLOT, 0, DATA_GPA, getpagesize(), hva);
+	virt_map(vm, DATA_GPA, DATA_GPA, 1);
+
+	ret = _vcpu_run(vcpu);
+	TEST_ASSERT(ret == -1 && errno == EPERM,
+		    "normal VM's fault on private memory: rc %d errno %d", ret, errno);
+
+	kvm_vm_free(vm);
+}
+
+static void (*host_action)(void *hva);
+
+static void on_sync(struct kvm_vcpu *vcpu, struct ucall *uc)
+{
+	host_action(addr_gpa2hva(vcpu->vm, DATA_GPA));
+}
+
+static struct kvm_vm *vm_create_with_data(struct kvm_vcpu **vcpu)
+{
+	struct kvm_vm *vm = pkvm_vm_create_with_one_vcpu(vcpu, guest_code);
+
+	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, DATA_GPA, DATA_MEMSLOT,
+				    DATA_PAGES, 0);
+	virt_map(vm, DATA_GPA, DATA_GPA, DATA_PAGES);
+	return vm;
+}
+
+static void test_host_access(void (*action)(void *hva), const char *name)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+
+	vm = vm_create_with_data(&vcpu);
+
+	host_action = action;
+	pkvm_run_loop(vcpu, on_sync);
+
+	kvm_vm_free(vm);
+	TEST_ASSERT_EQ(pkvm_host_locked_vm(), 0);
+
+	ksft_test_result_pass("%s\n", name);
+}
+
+/*
+ * The host never reads the guest's data: a kernel copy from private memory
+ * reads zeros, and the guest's next access to the page fails.
+ */
+static void test_process_vm_readv(void)
+{
+	u64 buf[2] = { HOST_VAL, HOST_VAL };
+	struct iovec local = { .iov_base = buf, .iov_len = sizeof(buf) };
+	struct kvm_vcpu *vcpu;
+	struct iovec remote;
+	struct kvm_vm *vm;
+	struct ucall uc;
+	ssize_t ret;
+	int i;
+
+	vm = vm_create_with_data(&vcpu);
+	vcpu_run(vcpu);
+	TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_SYNC);
+
+	remote = (struct iovec){ .iov_base = addr_gpa2hva(vm, DATA_GPA), .iov_len = sizeof(buf) };
+	ret = process_vm_readv(getpid(), &local, 1, &remote, 1, 0);
+	TEST_ASSERT_EQ(ret, sizeof(buf));
+	for (i = 0; i < ARRAY_SIZE(buf); i++)
+		TEST_ASSERT(!buf[i], "process_vm_readv() of private memory read 0x%lx", buf[i]);
+
+	/* The guest's next access is its read of that page. */
+	ret = _vcpu_run(vcpu);
+	TEST_ASSERT(ret == -1 && errno == EFAULT,
+		    "guest access after process_vm_readv(): rc %zd errno %d", ret, errno);
+
+	kvm_vm_free(vm);
+	TEST_ASSERT_EQ(pkvm_host_locked_vm(), 0);
+
+	ksft_test_result_pass("process_vm_readv() of private memory reads no guest data\n");
+}
+
+static const struct {
+	void (*action)(void *hva);
+	const char *name;
+} host_accesses[] = {
+	{ host_write_from_private, "write() from private memory fails with EFAULT" },
+	{ host_read_into_private, "read() into private memory fails with EFAULT" },
+	{ host_dontneed, "MADV_DONTNEED of private memory leaves the guest's data" },
+	{ host_munmap, "munmap() of private memory leaves the guest's data" },
+};
+
+int main(void)
+{
+	const char *np_vm = "a normal VM's fault on private memory fails with EPERM";
+	int i;
+
+	TEST_REQUIRE(pkvm_supported());
+
+	ksft_print_header();
+	ksft_set_plan(ARRAY_SIZE(host_accesses) + 2);
+
+	for (i = 0; i < ARRAY_SIZE(host_accesses); i++)
+		test_host_access(host_accesses[i].action, host_accesses[i].name);
+
+	test_process_vm_readv();
+
+	test_host_access(host_np_vm_on_private, np_vm);
+
+	ksft_finished();
+}
-- 
2.47.3


  parent reply	other threads:[~2026-11-09  8:49 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-09  8:48 [PATCH v2 00/19] KVM: arm64: selftests: Tests for protected VMs Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 01/19] KVM: arm64: Hide generic ptrauth from protected VMs without ptrauth Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 02/19] KVM: arm64: selftests: Inject an abort before a vCPU's first run Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 03/19] KVM: selftests: Allow arm64 tests to create protected VMs Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 04/19] tools: arm-smccc: Add the pKVM memory sharing function IDs Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 05/19] KVM: selftests: Export the ucall pool's guest physical address Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 06/19] KVM: arm64: selftests: Add protected-VM boot trampoline Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 07/19] KVM: arm64: selftests: Add protected-VM library helpers Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 08/19] KVM: arm64: selftests: Add pkvm_host_abi host-interface test Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 09/19] KVM: arm64: selftests: Add pkvm_psci multi-vCPU PSCI test Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 10/19] KVM: arm64: selftests: Add pkvm_mem_share hypercall test Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 11/19] KVM: arm64: selftests: Add pkvm_smccc " Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 12/19] KVM: arm64: selftests: Add pkvm_id_regs register and trap test Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 13/19] KVM: arm64: selftests: Add pkvm_vcpu_regs register-state test Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 14/19] KVM: arm64: selftests: Add pkvm_irq interrupt test Fuad Tabba
2026-11-09  8:48 ` Fuad Tabba [this message]
2026-11-09  8:48 ` [PATCH v2 16/19] KVM: arm64: selftests: Add pkvm_lifecycle teardown test Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 17/19] KVM: arm64: selftests: Add pkvm_exceptions debug and SError test Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 18/19] KVM: arm64: selftests: Add pkvm_sve vector-length test Fuad Tabba
2026-11-09  8:48 ` [PATCH v2 19/19] KVM: arm64: selftests: Check MOPS, PIE, POE and FPMR in a protected VM Fuad Tabba

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=20261109084854.840576-16-fuad.tabba@linux.dev \
    --to=fuad.tabba@linux.dev \
    --cc=catalin.marinas@arm.com \
    --cc=joey.gouly@arm.com \
    --cc=kvm@vger.kernel.org \
    --cc=kvmarm@lists.linux.dev \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=maz@kernel.org \
    --cc=oupton@kernel.org \
    --cc=pbonzini@redhat.com \
    --cc=qperret@google.com \
    --cc=seanjc@google.com \
    --cc=seiden@linux.ibm.com \
    --cc=shuah@kernel.org \
    --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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®