mempool: Test COW vs malloc_init race
[librseq.git] / tests / mempool_cow_race_test.c
1 // SPDX-License-Identifier: MIT
2 // SPDX-FileCopyrightText: 2024 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
3
4 /*
5 * rseq memory pool COW race test.
6 *
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.
15 */
16
17 #ifndef _GNU_SOURCE
18 #define _GNU_SOURCE
19 #endif
20 #include <assert.h>
21 #include <sched.h>
22 #include <signal.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <sys/time.h>
26 #include <inttypes.h>
27 #include <stdlib.h>
28 #include <sys/wait.h>
29 #include <unistd.h>
30 #include <pthread.h>
31 #include <errno.h>
32
33 #include <rseq/rseq.h>
34 #include <rseq/mempool.h>
35 #include "../src/rseq-utils.h"
36
37 #include "tap.h"
38
39 #define TEST_DURATION_S 10 /* seconds */
40 #define TEST_ARRAY_LEN 256
41 #define TEST_STRIDE 16384
42
43 enum phase {
44 PHASE_RESET_POOL,
45 PHASE_WRITE_POOL,
46 };
47
48 struct test_data {
49 char c[TEST_ARRAY_LEN];
50 };
51
52 struct test_thread_args {
53 struct rseq_mempool *mempool;
54 int phase; /* enum phase */
55 int stop_init_thread;
56 int stop_writer_thread;
57 struct test_data *ptr1;
58 struct test_data *ptr2;
59 };
60
61 struct test_data init_value;
62
63 static void *test_init_thread(void *arg)
64 {
65 struct test_thread_args *thread_args = (struct test_thread_args *) arg;
66
67 while (!RSEQ_READ_ONCE(thread_args->stop_init_thread)) {
68 struct rseq_mempool_attr *attr;
69 struct rseq_mempool *mempool;
70 struct test_data *p;
71 int ret, i;
72
73 attr = rseq_mempool_attr_create();
74 ret = rseq_mempool_attr_set_robust(attr);
75 if (ret)
76 abort();
77 ret = rseq_mempool_attr_set_percpu(attr, TEST_STRIDE, 1);
78 if (ret)
79 abort();
80 ret = rseq_mempool_attr_set_max_nr_ranges(attr, 1);
81 if (ret)
82 abort();
83 ret = rseq_mempool_attr_set_populate_policy(attr, RSEQ_MEMPOOL_POPULATE_NONE);
84 if (ret)
85 abort();
86 mempool = rseq_mempool_create("test_data", sizeof(struct test_data), attr);
87 if (!mempool)
88 abort();
89 thread_args->mempool = mempool;
90 rseq_mempool_attr_destroy(attr);
91
92 thread_args->ptr1 = (struct test_data __rseq_percpu *) rseq_mempool_percpu_malloc(mempool);
93 if (!thread_args->ptr1)
94 abort();
95
96 rseq_smp_store_release(&thread_args->phase, PHASE_WRITE_POOL);
97
98 /* malloc init runs concurrently with COW. */
99 thread_args->ptr2 = (struct test_data __rseq_percpu *)
100 rseq_mempool_percpu_malloc_init(mempool,
101 &init_value, sizeof(struct test_data));
102 if (!thread_args->ptr2)
103 abort();
104
105 p = rseq_percpu_ptr(thread_args->ptr2, 0);
106 for (i = 0; i < TEST_ARRAY_LEN; i++) {
107 if (p->c[i] != 0x22) {
108 fprintf(stderr, "Unexpected value\n");
109 abort();
110 }
111 }
112
113 while (rseq_smp_load_acquire(&thread_args->phase) != PHASE_RESET_POOL) { }
114
115 rseq_mempool_percpu_free(thread_args->ptr2, TEST_STRIDE);
116 rseq_mempool_percpu_free(thread_args->ptr1, TEST_STRIDE);
117
118 if (rseq_mempool_destroy(mempool))
119 abort();
120 }
121 RSEQ_WRITE_ONCE(thread_args->stop_writer_thread, 1);
122 rseq_smp_store_release(&thread_args->phase, PHASE_WRITE_POOL);
123 return NULL;
124 }
125
126 static void *test_writer_thread(void *arg)
127 {
128 struct test_thread_args *thread_args = (struct test_thread_args *) arg;
129
130 for (;;) {
131 unsigned int loop, delay;
132
133 delay = rand() % 10000;
134 while (rseq_smp_load_acquire(&thread_args->phase) != PHASE_WRITE_POOL) { }
135
136 if (RSEQ_READ_ONCE(thread_args->stop_writer_thread))
137 break;
138
139 for (loop = 0; loop < delay; loop++)
140 rseq_barrier();
141
142 /* Trigger COW. */
143 rseq_percpu_ptr(thread_args->ptr1, 0)->c[0] = 0x33;
144
145 rseq_smp_store_release(&thread_args->phase, PHASE_RESET_POOL);
146 }
147
148 return NULL;
149 }
150
151 int main(void)
152 {
153 struct test_thread_args thread_args = {};
154 pthread_t writer_thread, init_thread;
155 unsigned int remain;
156 int ret;
157
158 plan_no_plan();
159
160 diag("Beginning COW vs malloc init race validation (%u seconds)...", TEST_DURATION_S);
161 srand(0x42);
162
163 memset(&init_value.c, 0x22, TEST_ARRAY_LEN);
164
165 thread_args.phase = PHASE_RESET_POOL;
166
167 ret = pthread_create(&init_thread, NULL, test_init_thread, &thread_args);
168 if (ret) {
169 errno = ret;
170 perror("pthread_create");
171 abort();
172 }
173
174 ret = pthread_create(&writer_thread, NULL, test_writer_thread, &thread_args);
175 if (ret) {
176 errno = ret;
177 perror("pthread_create");
178 abort();
179 }
180
181 remain = TEST_DURATION_S;
182 do {
183 remain = sleep(remain);
184 } while (remain > 0);
185
186 RSEQ_WRITE_ONCE(thread_args.stop_init_thread, 1);
187
188 ret = pthread_join(writer_thread, NULL);
189 if (ret) {
190 errno = ret;
191 perror("pthread_join");
192 abort();
193 }
194
195 ret = pthread_join(init_thread, NULL);
196 if (ret) {
197 errno = ret;
198 perror("pthread_join");
199 abort();
200 }
201
202 ok(1, "Validate COW vs malloc init race");
203
204 exit(exit_status());
205 }
This page took 0.039757 seconds and 4 git commands to generate.