From: Sean Christopherson seanjc@google.com
Add a test to verify that a guest_memfd's shared/private status is consistent across 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@google.com Co-developed-by: Ackerley Tng ackerleytng@google.com Signed-off-by: Ackerley Tng ackerleytng@google.com --- .../kvm/guest_memfd_conversions_test.c | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+)
diff --git a/tools/testing/selftests/kvm/guest_memfd_conversions_test.c b/tools/testing/selftests/kvm/guest_memfd_conversions_test.c index 907d415d72315..e6abf2d30c62d 100644 --- a/tools/testing/selftests/kvm/guest_memfd_conversions_test.c +++ b/tools/testing/selftests/kvm/guest_memfd_conversions_test.c @@ -330,6 +330,80 @@ 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) +{ + /* + * No races are intended in this test, shared memory is only used to + * coordinate between processes. + */ + static enum { + STATE_INIT, + STATE_CHECK_SHARED, + STATE_DONE_CHECKING_SHARED, + STATE_CHECK_PRIVATE, + STATE_DONE_CHECKING_PRIVATE, + } *test_state; + pid_t child_pid; + + test_state = kvm_mmap(sizeof(*test_state), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1); + +#define TEST_STATE_AWAIT(__state) \ + while (READ_ONCE(*test_state) != __state) { \ + if (child_pid != 0) { \ + int status; \ + pid_t pid; \ + do { \ + pid = waitpid(child_pid, &status, WNOHANG); \ + } while (pid == -1 && errno == EINTR); \ + if (pid == -1) \ + TEST_FAIL("Couldn't check child status."); \ + else if (pid != 0) \ + TEST_FAIL("Child exited prematurely."); \ + } \ + } + +#define TEST_STATE_SET(__state) WRITE_ONCE(*test_state, __state) + + 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); + + kvm_munmap(test_state, sizeof(*test_state)); +} + int main(int argc, char *argv[]) { TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SW_PROTECTED_VM));