From: Sean Christopherson <[email protected]>

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 <[email protected]>
Co-developed-by: Ackerley Tng <[email protected]>
Signed-off-by: Ackerley Tng <[email protected]>
---
 .../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



Reply via email to