[PATCH v8 37/46] KVM: selftests: Test that shared/private status is consistent across processes
From: Ackerley Tng via B4 Relay
Date: Thu Jun 18 2026 - 20:49:33 EST
From: Sean Christopherson <seanjc@xxxxxxxxxx>
Add a test to verify that a guest_memfd's shared/private status is
consistent across processes, and that any shared pages previously mapped in
any process are unmapped from all processes.
The test forks a child process after creating the shared guest_memfd
region so that the second process exists alongside the main process for the
entire test.
The processes then take turns to access memory to check that the
shared/private status is consistent across processes.
Signed-off-by: Sean Christopherson <seanjc@xxxxxxxxxx>
Co-developed-by: Ackerley Tng <ackerleytng@xxxxxxxxxx>
Signed-off-by: Ackerley Tng <ackerleytng@xxxxxxxxxx>
---
.../kvm/x86/guest_memfd_conversions_test.c | 118 +++++++++++++++++++++
1 file changed, 118 insertions(+)
diff --git a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
index f03af2c46426f..99b0023609670 100644
--- a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
+++ b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
@@ -2,6 +2,8 @@
/*
* Copyright (c) 2024, Google LLC.
*/
+#include <pthread.h>
+#include <time.h>
#include <sys/mman.h>
#include <unistd.h>
@@ -323,6 +325,122 @@ GMEM_CONVERSION_TEST_INIT_SHARED(truncate)
test_private(t, 0, 0, 'A');
}
+/* Test that shared/private memory protections work and are seen from any process. */
+GMEM_CONVERSION_TEST_INIT_SHARED(forked_accesses)
+{
+ enum test_state {
+ STATE_INIT,
+ STATE_CHECK_SHARED,
+ STATE_DONE_CHECKING_SHARED,
+ STATE_CHECK_PRIVATE,
+ STATE_DONE_CHECKING_PRIVATE,
+ };
+
+ struct sync_state {
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+ enum test_state step;
+ } *sync;
+
+ pthread_mutexattr_t mattr;
+ pthread_condattr_t cattr;
+ pid_t child_pid, parent_pid;
+ int status;
+
+ sync = kvm_mmap(sizeof(*sync), PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1);
+
+ pthread_mutexattr_init(&mattr);
+ pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
+ pthread_mutex_init(&sync->mutex, &mattr);
+ pthread_mutexattr_destroy(&mattr);
+
+ pthread_condattr_init(&cattr);
+ pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);
+ pthread_cond_init(&sync->cond, &cattr);
+ pthread_condattr_destroy(&cattr);
+
+ sync->step = STATE_INIT;
+
+#define TEST_STATE_AWAIT(__state) \
+ do { \
+ pthread_mutex_lock(&sync->mutex); \
+ while (sync->step != (__state)) { \
+ struct timespec ts, stop; \
+ int ret; \
+ \
+ clock_gettime(CLOCK_REALTIME, &ts); \
+ stop = timespec_add_ns(ts, 100 * 1000000UL); \
+ \
+ ret = pthread_cond_timedwait(&sync->cond, &sync->mutex, &stop); \
+ if (ret == ETIMEDOUT) { \
+ bool alive = (child_pid == 0) ? \
+ (getppid() == parent_pid) : \
+ (waitpid(child_pid, NULL, WNOHANG) == 0); \
+ TEST_ASSERT(alive, "Other process exited prematurely"); \
+ } else { \
+ TEST_ASSERT(!ret, "pthread_cond_timedwait failed"); \
+ } \
+ } \
+ pthread_mutex_unlock(&sync->mutex); \
+ } while (0)
+
+#define TEST_STATE_SET(__state) \
+ do { \
+ pthread_mutex_lock(&sync->mutex); \
+ sync->step = (__state); \
+ pthread_cond_broadcast(&sync->cond); \
+ pthread_mutex_unlock(&sync->mutex); \
+ } while (0)
+
+ parent_pid = getpid();
+ child_pid = fork();
+ TEST_ASSERT(child_pid != -1, "fork failed");
+
+ if (child_pid == 0) {
+ const char inconsequential = 0xdd;
+
+ TEST_STATE_AWAIT(STATE_CHECK_SHARED);
+
+ /*
+ * This maps the pages into the child process as well, and tests
+ * that the conversion process will unmap the guest_memfd memory
+ * from all processes.
+ */
+ host_do_rmw(t->mem, 0, 0xB, 0xC);
+
+ TEST_STATE_SET(STATE_DONE_CHECKING_SHARED);
+ TEST_STATE_AWAIT(STATE_CHECK_PRIVATE);
+
+ TEST_EXPECT_SIGBUS(READ_ONCE(t->mem[0]));
+ TEST_EXPECT_SIGBUS(WRITE_ONCE(t->mem[0], inconsequential));
+
+ TEST_STATE_SET(STATE_DONE_CHECKING_PRIVATE);
+ exit(0);
+ }
+
+ test_shared(t, 0, 0, 0xA, 0xB);
+
+ TEST_STATE_SET(STATE_CHECK_SHARED);
+ TEST_STATE_AWAIT(STATE_DONE_CHECKING_SHARED);
+
+ test_convert_to_private(t, 0, 0xC, 0xD);
+
+ TEST_STATE_SET(STATE_CHECK_PRIVATE);
+ TEST_STATE_AWAIT(STATE_DONE_CHECKING_PRIVATE);
+
+ TEST_ASSERT_EQ(waitpid(child_pid, &status, 0), child_pid);
+ TEST_ASSERT(WIFEXITED(status) && WEXITSTATUS(status) == 0,
+ "Child exited with unexpected status");
+
+ pthread_mutex_destroy(&sync->mutex);
+ pthread_cond_destroy(&sync->cond);
+ kvm_munmap(sync, sizeof(*sync));
+
+#undef TEST_STATE_SET
+#undef TEST_STATE_AWAIT
+}
+
int main(int argc, char *argv[])
{
TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SW_PROTECTED_VM));
--
2.55.0.rc0.738.g0c8ab3ebcc-goog