1 // SPDX-License-Identifier: MIT
2 // SPDX-FileCopyrightText: 2024 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
5 * rseq memory pool COW race test.
7 * Test that the entire malloc init value is visible in CPU mappings. If
8 * the COW page copy race vs init happens while init is in the middle of
9 * storing to the newly allocated area, iteration on all CPUs comparing
10 * the visible content to the init value is responsible for detecting
11 * and mitigating uninitialized or partially initialized init value from
12 * the point of view of a CPU. Validate that this scheme has the
13 * intended effect wrt a concurrent COW caused by storing to a nearby
14 * per-cpu area on the same page.
33 #include <rseq/rseq.h>
34 #include <rseq/mempool.h>
35 #include "../src/rseq-utils.h"
39 #define TEST_DURATION_S 10 /* seconds */
40 #define TEST_ARRAY_LEN 256
48 char c
[TEST_ARRAY_LEN
];
51 struct test_thread_args
{
52 struct rseq_mempool
*mempool
;
53 int phase
; /* enum phase */
55 int stop_writer_thread
;
56 struct test_data
*ptr1
;
57 struct test_data
*ptr2
;
60 struct test_data init_value
;
62 static void *test_init_thread(void *arg
)
64 struct test_thread_args
*thread_args
= (struct test_thread_args
*) arg
;
66 while (!RSEQ_READ_ONCE(thread_args
->stop_init_thread
)) {
67 struct rseq_mempool_attr
*attr
;
68 struct rseq_mempool
*mempool
;
72 attr
= rseq_mempool_attr_create();
73 ret
= rseq_mempool_attr_set_robust(attr
);
76 ret
= rseq_mempool_attr_set_percpu(attr
, 0, 1);
79 ret
= rseq_mempool_attr_set_max_nr_ranges(attr
, 1);
82 ret
= rseq_mempool_attr_set_populate_policy(attr
, RSEQ_MEMPOOL_POPULATE_COW_INIT
);
85 mempool
= rseq_mempool_create("test_data", sizeof(struct test_data
), attr
);
88 thread_args
->mempool
= mempool
;
89 rseq_mempool_attr_destroy(attr
);
91 thread_args
->ptr1
= (struct test_data __rseq_percpu
*) rseq_mempool_percpu_malloc(mempool
);
92 if (!thread_args
->ptr1
)
95 rseq_smp_store_release(&thread_args
->phase
, PHASE_WRITE_POOL
);
97 /* malloc init runs concurrently with COW. */
98 thread_args
->ptr2
= (struct test_data __rseq_percpu
*)
99 rseq_mempool_percpu_malloc_init(mempool
,
100 &init_value
, sizeof(struct test_data
));
101 if (!thread_args
->ptr2
)
104 p
= rseq_percpu_ptr(thread_args
->ptr2
, 0);
105 for (i
= 0; i
< TEST_ARRAY_LEN
; i
++) {
106 if (p
->c
[i
] != 0x22) {
107 fprintf(stderr
, "Unexpected value\n");
112 while (rseq_smp_load_acquire(&thread_args
->phase
) != PHASE_RESET_POOL
) { }
114 rseq_mempool_percpu_free(thread_args
->ptr2
);
115 rseq_mempool_percpu_free(thread_args
->ptr1
);
117 if (rseq_mempool_destroy(mempool
))
120 RSEQ_WRITE_ONCE(thread_args
->stop_writer_thread
, 1);
121 rseq_smp_store_release(&thread_args
->phase
, PHASE_WRITE_POOL
);
125 static void *test_writer_thread(void *arg
)
127 struct test_thread_args
*thread_args
= (struct test_thread_args
*) arg
;
130 unsigned int loop
, delay
;
132 delay
= rand() % 10000;
133 while (rseq_smp_load_acquire(&thread_args
->phase
) != PHASE_WRITE_POOL
) { }
135 if (RSEQ_READ_ONCE(thread_args
->stop_writer_thread
))
138 for (loop
= 0; loop
< delay
; loop
++)
142 rseq_percpu_ptr(thread_args
->ptr1
, 0)->c
[0] = 0x33;
144 rseq_smp_store_release(&thread_args
->phase
, PHASE_RESET_POOL
);
152 struct test_thread_args thread_args
= {};
153 pthread_t writer_thread
, init_thread
;
159 diag("Beginning COW vs malloc init race validation (%u seconds)...", TEST_DURATION_S
);
162 memset(&init_value
.c
, 0x22, TEST_ARRAY_LEN
);
164 thread_args
.phase
= PHASE_RESET_POOL
;
166 ret
= pthread_create(&init_thread
, NULL
, test_init_thread
, &thread_args
);
169 perror("pthread_create");
173 ret
= pthread_create(&writer_thread
, NULL
, test_writer_thread
, &thread_args
);
176 perror("pthread_create");
180 remain
= TEST_DURATION_S
;
182 remain
= sleep(remain
);
183 } while (remain
> 0);
185 RSEQ_WRITE_ONCE(thread_args
.stop_init_thread
, 1);
187 ret
= pthread_join(writer_thread
, NULL
);
190 perror("pthread_join");
194 ret
= pthread_join(init_thread
, NULL
);
197 perror("pthread_join");
201 ok(1, "Validate COW vs malloc init race");