Re: [PATCH v8 37/46] KVM: selftests: Test that shared/private status is consistent across processes

From: Fuad Tabba

Date: Thu Jun 25 2026 - 03:15:56 EST


On Fri, 19 Jun 2026 at 01:32, Ackerley Tng via B4 Relay
<devnull+ackerleytng.google.com@xxxxxxxxxx> wrote:
>
> 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>
> ---

Two things below, otherwise:

Reviewed-by: Fuad Tabba <tabba@xxxxxxxxxx>

Cheers,
/fuad


> .../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>

nit: include order

>
> @@ -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); \

Not sure it's worth it, but if you want to silence Sashiko, waitid
with WNOWAIT might be the way to go (not tested, just from looking at
the man page). This is though very unlikely, mentioning it since
Sashiko complained.


> + 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
>
>