From: Mathieu Desnoyers Date: Sun, 17 Mar 2024 20:31:11 +0000 (-0400) Subject: mempool: Test COW vs malloc_init race X-Git-Url: http://git.efficios.com/?a=commitdiff_plain;h=2a6740bcb954bf4141c99778cd7309a037e6cd3e;p=librseq.git mempool: Test COW vs malloc_init race Test that the entire malloc init value is visible in CPU mappings. If the COW page copy race vs init happens while init is in the middle of storing to the newly allocated area, iteration on all CPUs comparing the visible content to the init value is responsible for detecting and mitigating uninitialized or partially initialized init value from the point of view of a CPU. Validate that this scheme has the intended effect wrt a concurrent COW caused by storing to a nearby per-cpu area on the same page. Signed-off-by: Mathieu Desnoyers Change-Id: Iebde10eb61e80cc0d8eb0ebc89925f4a2ad9de9c --- diff --git a/.gitignore b/.gitignore index 7dd4d39..3bcfbf4 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,8 @@ dkms.conf /tests/basic_percpu_ops_mm_cid_test_cxx.tap /tests/basic_test.tap /tests/basic_test_cxx.tap +/tests/mempool_cow_race_test.tap +/tests/mempool_cow_race_test_cxx.tap /tests/mempool_test.tap /tests/mempool_test_cxx.tap /tests/param_test diff --git a/tests/Makefile.am b/tests/Makefile.am index 9c5999e..1a50a72 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -21,6 +21,8 @@ noinst_PROGRAMS = \ basic_test_cxx.tap \ mempool_test.tap \ mempool_test_cxx.tap \ + mempool_cow_race_test.tap \ + mempool_cow_race_test_cxx.tap \ param_test \ param_test_cxx \ param_test_mm_cid \ @@ -94,6 +96,12 @@ mempool_test_tap_LDADD = $(top_builddir)/src/librseq.la $(top_builddir)/tests/ut mempool_test_cxx_tap_SOURCES = mempool_test_cxx.cpp list.h mempool_test_cxx_tap_LDADD = $(top_builddir)/src/librseq.la $(top_builddir)/tests/utils/libtap.la $(DL_LIBS) +mempool_cow_race_test_tap_SOURCES = mempool_cow_race_test.c +mempool_cow_race_test_tap_LDADD = $(top_builddir)/src/librseq.la $(top_builddir)/tests/utils/libtap.la $(DL_LIBS) + +mempool_cow_race_test_cxx_tap_SOURCES = mempool_cow_race_test_cxx.cpp +mempool_cow_race_test_cxx_tap_LDADD = $(top_builddir)/src/librseq.la $(top_builddir)/tests/utils/libtap.la $(DL_LIBS) + param_test_SOURCES = param_test.c param_test_LDADD = $(top_builddir)/src/librseq.la $(DL_LIBS) @@ -146,6 +154,8 @@ TESTS = \ basic_test_cxx.tap \ run_unregistered_test.tap \ run_unregistered_test_cxx.tap \ + mempool_cow_race_test.tap \ + mempool_cow_race_test_cxx.tap \ mempool_test.tap \ mempool_test_cxx.tap diff --git a/tests/mempool_cow_race_test.c b/tests/mempool_cow_race_test.c new file mode 100644 index 0000000..5b8752f --- /dev/null +++ b/tests/mempool_cow_race_test.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2024 Mathieu Desnoyers + +/* + * rseq memory pool COW race test. + * + * Test that the entire malloc init value is visible in CPU mappings. If + * the COW page copy race vs init happens while init is in the middle of + * storing to the newly allocated area, iteration on all CPUs comparing + * the visible content to the init value is responsible for detecting + * and mitigating uninitialized or partially initialized init value from + * the point of view of a CPU. Validate that this scheme has the + * intended effect wrt a concurrent COW caused by storing to a nearby + * per-cpu area on the same page. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "../src/rseq-utils.h" + +#include "tap.h" + +#define TEST_DURATION_S 10 /* seconds */ +#define TEST_ARRAY_LEN 256 +#define TEST_STRIDE 16384 + +enum phase { + PHASE_RESET_POOL, + PHASE_WRITE_POOL, +}; + +struct test_data { + char c[TEST_ARRAY_LEN]; +}; + +struct test_thread_args { + struct rseq_mempool *mempool; + int phase; /* enum phase */ + int stop_init_thread; + int stop_writer_thread; + struct test_data *ptr1; + struct test_data *ptr2; +}; + +struct test_data init_value; + +static void *test_init_thread(void *arg) +{ + struct test_thread_args *thread_args = (struct test_thread_args *) arg; + + while (!RSEQ_READ_ONCE(thread_args->stop_init_thread)) { + struct rseq_mempool_attr *attr; + struct rseq_mempool *mempool; + struct test_data *p; + int ret, i; + + attr = rseq_mempool_attr_create(); + ret = rseq_mempool_attr_set_robust(attr); + if (ret) + abort(); + ret = rseq_mempool_attr_set_percpu(attr, TEST_STRIDE, 1); + if (ret) + abort(); + ret = rseq_mempool_attr_set_max_nr_ranges(attr, 1); + if (ret) + abort(); + ret = rseq_mempool_attr_set_populate_policy(attr, RSEQ_MEMPOOL_POPULATE_NONE); + if (ret) + abort(); + mempool = rseq_mempool_create("test_data", sizeof(struct test_data), attr); + if (!mempool) + abort(); + thread_args->mempool = mempool; + rseq_mempool_attr_destroy(attr); + + thread_args->ptr1 = (struct test_data __rseq_percpu *) rseq_mempool_percpu_malloc(mempool); + if (!thread_args->ptr1) + abort(); + + rseq_smp_store_release(&thread_args->phase, PHASE_WRITE_POOL); + + /* malloc init runs concurrently with COW. */ + thread_args->ptr2 = (struct test_data __rseq_percpu *) + rseq_mempool_percpu_malloc_init(mempool, + &init_value, sizeof(struct test_data)); + if (!thread_args->ptr2) + abort(); + + p = rseq_percpu_ptr(thread_args->ptr2, 0); + for (i = 0; i < TEST_ARRAY_LEN; i++) { + if (p->c[i] != 0x22) { + fprintf(stderr, "Unexpected value\n"); + abort(); + } + } + + while (rseq_smp_load_acquire(&thread_args->phase) != PHASE_RESET_POOL) { } + + rseq_mempool_percpu_free(thread_args->ptr2, TEST_STRIDE); + rseq_mempool_percpu_free(thread_args->ptr1, TEST_STRIDE); + + if (rseq_mempool_destroy(mempool)) + abort(); + } + RSEQ_WRITE_ONCE(thread_args->stop_writer_thread, 1); + rseq_smp_store_release(&thread_args->phase, PHASE_WRITE_POOL); + return NULL; +} + +static void *test_writer_thread(void *arg) +{ + struct test_thread_args *thread_args = (struct test_thread_args *) arg; + + for (;;) { + unsigned int loop, delay; + + delay = rand() % 10000; + while (rseq_smp_load_acquire(&thread_args->phase) != PHASE_WRITE_POOL) { } + + if (RSEQ_READ_ONCE(thread_args->stop_writer_thread)) + break; + + for (loop = 0; loop < delay; loop++) + rseq_barrier(); + + /* Trigger COW. */ + rseq_percpu_ptr(thread_args->ptr1, 0)->c[0] = 0x33; + + rseq_smp_store_release(&thread_args->phase, PHASE_RESET_POOL); + } + + return NULL; +} + +int main(void) +{ + struct test_thread_args thread_args = {}; + pthread_t writer_thread, init_thread; + unsigned int remain; + int ret; + + plan_no_plan(); + + diag("Beginning COW vs malloc init race validation (%u seconds)...", TEST_DURATION_S); + srand(0x42); + + memset(&init_value.c, 0x22, TEST_ARRAY_LEN); + + thread_args.phase = PHASE_RESET_POOL; + + ret = pthread_create(&init_thread, NULL, test_init_thread, &thread_args); + if (ret) { + errno = ret; + perror("pthread_create"); + abort(); + } + + ret = pthread_create(&writer_thread, NULL, test_writer_thread, &thread_args); + if (ret) { + errno = ret; + perror("pthread_create"); + abort(); + } + + remain = TEST_DURATION_S; + do { + remain = sleep(remain); + } while (remain > 0); + + RSEQ_WRITE_ONCE(thread_args.stop_init_thread, 1); + + ret = pthread_join(writer_thread, NULL); + if (ret) { + errno = ret; + perror("pthread_join"); + abort(); + } + + ret = pthread_join(init_thread, NULL); + if (ret) { + errno = ret; + perror("pthread_join"); + abort(); + } + + ok(1, "Validate COW vs malloc init race"); + + exit(exit_status()); +} diff --git a/tests/mempool_cow_race_test_cxx.cpp b/tests/mempool_cow_race_test_cxx.cpp new file mode 100644 index 0000000..aac22f6 --- /dev/null +++ b/tests/mempool_cow_race_test_cxx.cpp @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: MIT */ +// SPDX-FileCopyrightText: 2024 EfficiOS Inc. + +#include "mempool_cow_race_test.c"