2f2bbb27679429ed88e74b147521aa27fdb664b9
[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
42 enum phase {
43 PHASE_RESET_POOL,
44 PHASE_WRITE_POOL,
45 };
46
47 struct test_data {
48 char c[TEST_ARRAY_LEN];
49 };
50
51 struct test_thread_args {
52 struct rseq_mempool *mempool;
53 int phase; /* enum phase */
54 int stop_init_thread;
55 int stop_writer_thread;
56 struct test_data *ptr1;
57 struct test_data *ptr2;
58 };
59
60 struct test_data init_value;
61
62 static void *test_init_thread(void *arg)
63 {
64 struct test_thread_args *thread_args = (struct test_thread_args *) arg;
65
66 while (!RSEQ_READ_ONCE(thread_args->stop_init_thread)) {
67 struct rseq_mempool_attr *attr;
68 struct rseq_mempool *mempool;
69 struct test_data *p;
70 int ret, i;
71
72 attr = rseq_mempool_attr_create();
73 ret = rseq_mempool_attr_set_robust(attr);
74 if (ret)
75 abort();
76 ret = rseq_mempool_attr_set_percpu(attr, 0, 1);
77 if (ret)
78 abort();
79 ret = rseq_mempool_attr_set_max_nr_ranges(attr, 1);
80 if (ret)
81 abort();
82 ret = rseq_mempool_attr_set_populate_policy(attr, RSEQ_MEMPOOL_POPULATE_NONE);
83 if (ret)
84 abort();
85 mempool = rseq_mempool_create("test_data", sizeof(struct test_data), attr);
86 if (!mempool)
87 abort();
88 thread_args->mempool = mempool;
89 rseq_mempool_attr_destroy(attr);
90
91 thread_args->ptr1 = (struct test_data __rseq_percpu *) rseq_mempool_percpu_malloc(mempool);
92 if (!thread_args->ptr1)
93 abort();
94
95 rseq_smp_store_release(&thread_args->phase, PHASE_WRITE_POOL);
96
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)
102 abort();
103
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");
108 abort();
109 }
110 }
111
112 while (rseq_smp_load_acquire(&thread_args->phase) != PHASE_RESET_POOL) { }
113
114 rseq_mempool_percpu_free(thread_args->ptr2);
115 rseq_mempool_percpu_free(thread_args->ptr1);
116
117 if (rseq_mempool_destroy(mempool))
118 abort();
119 }
120 RSEQ_WRITE_ONCE(thread_args->stop_writer_thread, 1);
121 rseq_smp_store_release(&thread_args->phase, PHASE_WRITE_POOL);
122 return NULL;
123 }
124
125 static void *test_writer_thread(void *arg)
126 {
127 struct test_thread_args *thread_args = (struct test_thread_args *) arg;
128
129 for (;;) {
130 unsigned int loop, delay;
131
132 delay = rand() % 10000;
133 while (rseq_smp_load_acquire(&thread_args->phase) != PHASE_WRITE_POOL) { }
134
135 if (RSEQ_READ_ONCE(thread_args->stop_writer_thread))
136 break;
137
138 for (loop = 0; loop < delay; loop++)
139 rseq_barrier();
140
141 /* Trigger COW. */
142 rseq_percpu_ptr(thread_args->ptr1, 0)->c[0] = 0x33;
143
144 rseq_smp_store_release(&thread_args->phase, PHASE_RESET_POOL);
145 }
146
147 return NULL;
148 }
149
150 int main(void)
151 {
152 struct test_thread_args thread_args = {};
153 pthread_t writer_thread, init_thread;
154 unsigned int remain;
155 int ret;
156
157 plan_no_plan();
158
159 diag("Beginning COW vs malloc init race validation (%u seconds)...", TEST_DURATION_S);
160 srand(0x42);
161
162 memset(&init_value.c, 0x22, TEST_ARRAY_LEN);
163
164 thread_args.phase = PHASE_RESET_POOL;
165
166 ret = pthread_create(&init_thread, NULL, test_init_thread, &thread_args);
167 if (ret) {
168 errno = ret;
169 perror("pthread_create");
170 abort();
171 }
172
173 ret = pthread_create(&writer_thread, NULL, test_writer_thread, &thread_args);
174 if (ret) {
175 errno = ret;
176 perror("pthread_create");
177 abort();
178 }
179
180 remain = TEST_DURATION_S;
181 do {
182 remain = sleep(remain);
183 } while (remain > 0);
184
185 RSEQ_WRITE_ONCE(thread_args.stop_init_thread, 1);
186
187 ret = pthread_join(writer_thread, NULL);
188 if (ret) {
189 errno = ret;
190 perror("pthread_join");
191 abort();
192 }
193
194 ret = pthread_join(init_thread, NULL);
195 if (ret) {
196 errno = ret;
197 perror("pthread_join");
198 abort();
199 }
200
201 ok(1, "Validate COW vs malloc init race");
202
203 exit(exit_status());
204 }
This page took 0.033031 seconds and 3 git commands to generate.