3fe102974191117d68d6909cd4378bec1c49ae72
[librseq.git] / tests / mempool_test.c
1 // SPDX-License-Identifier: MIT
2 // SPDX-FileCopyrightText: 2024 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
3 /*
4 * rseq memory pool test.
5 */
6
7 #ifndef _GNU_SOURCE
8 #define _GNU_SOURCE
9 #endif
10 #include <assert.h>
11 #include <sched.h>
12 #include <signal.h>
13 #include <stdio.h>
14 #include <string.h>
15 #include <sys/time.h>
16 #include <inttypes.h>
17 #include <stdlib.h>
18 #include <sys/wait.h>
19 #include <unistd.h>
20
21 #include <rseq/mempool.h>
22 #include "../src/rseq-utils.h"
23
24 #include "../src/list.h"
25 #include "tap.h"
26
27 #if RSEQ_BITS_PER_LONG == 64
28 # define POISON_VALUE 0xABCDABCDABCDABCDULL
29 #else
30 # define POISON_VALUE 0xABCDABCDUL
31 #endif
32
33 struct test_data {
34 uintptr_t value[2];
35 struct test_data __rseq_percpu *backref;
36 struct list_head node;
37 };
38
39 static void test_mempool_fill(enum rseq_mempool_populate_policy policy,
40 unsigned long max_nr_ranges, size_t stride)
41 {
42 struct test_data __rseq_percpu *ptr;
43 struct test_data *iter, *tmp;
44 struct rseq_mempool *mempool;
45 struct rseq_mempool_attr *attr;
46 uint64_t count = 0;
47 LIST_HEAD(list);
48 int ret, i, size_order;
49 struct test_data init_value = {
50 .value = {
51 123,
52 456,
53 },
54 .backref = NULL,
55 .node = {},
56 };
57
58 attr = rseq_mempool_attr_create();
59 ok(attr, "Create pool attribute");
60 ret = rseq_mempool_attr_set_robust(attr);
61 ok(ret == 0, "Setting mempool robust attribute");
62 ret = rseq_mempool_attr_set_percpu(attr, stride, CPU_SETSIZE);
63 ok(ret == 0, "Setting mempool percpu type");
64 ret = rseq_mempool_attr_set_max_nr_ranges(attr, max_nr_ranges);
65 ok(ret == 0, "Setting mempool max_nr_ranges=%lu", max_nr_ranges);
66 ret = rseq_mempool_attr_set_poison(attr, POISON_VALUE);
67 ok(ret == 0, "Setting mempool poison");
68 ret = rseq_mempool_attr_set_populate_policy(attr, policy);
69 ok(ret == 0, "Setting mempool populate policy to %s",
70 policy == RSEQ_MEMPOOL_POPULATE_COW_INIT ? "COW_INIT" : "COW_ZERO");
71 mempool = rseq_mempool_create("test_data",
72 sizeof(struct test_data), attr);
73 ok(mempool, "Create mempool of size %zu", stride);
74 rseq_mempool_attr_destroy(attr);
75
76 for (;;) {
77 struct test_data *cpuptr;
78
79 ptr = (struct test_data __rseq_percpu *) rseq_mempool_percpu_zmalloc(mempool);
80 if (!ptr)
81 break;
82 /* Link items in cpu 0. */
83 cpuptr = rseq_percpu_ptr(ptr, 0, stride);
84 cpuptr->backref = ptr;
85 /* Randomize items in list. */
86 if (count & 1)
87 list_add(&cpuptr->node, &list);
88 else
89 list_add_tail(&cpuptr->node, &list);
90 count++;
91 }
92
93 size_order = rseq_get_count_order_ulong(sizeof(struct test_data));
94 ok(count * (1U << size_order) == stride * max_nr_ranges,
95 "Allocated %" PRIu64 " objects in pool", count);
96
97 list_for_each_entry(iter, &list, node) {
98 ptr = iter->backref;
99 for (i = 0; i < CPU_SETSIZE; i++) {
100 struct test_data *cpuptr = rseq_percpu_ptr(ptr, i, stride);
101
102 if (cpuptr->value[0] != 0)
103 abort();
104 cpuptr->value[0]++;
105 }
106 }
107 ok(1, "Check for pool content corruption");
108
109 list_for_each_entry_safe(iter, tmp, &list, node) {
110 ptr = iter->backref;
111 rseq_mempool_percpu_free(ptr, stride);
112 }
113 ok(1, "Free all objects");
114
115 ptr = (struct test_data __rseq_percpu *) rseq_mempool_percpu_zmalloc(mempool);
116 if (!ptr)
117 abort();
118 ok(1, "Allocate one object");
119
120 rseq_mempool_percpu_free(ptr, stride);
121 ok(1, "Free one object");
122
123 ptr = (struct test_data __rseq_percpu *)
124 rseq_mempool_percpu_malloc_init(mempool,
125 &init_value, sizeof(struct test_data));
126 if (!ptr)
127 abort();
128 ok(1, "Allocate one initialized object");
129
130 ok(ptr->value[0] == 123 && ptr->value[1] == 456, "Validate initial values");
131
132 rseq_mempool_percpu_free(ptr, stride);
133 ok(1, "Free one object");
134
135 ret = rseq_mempool_destroy(mempool);
136 ok(ret == 0, "Destroy mempool");
137 }
138
139 static void test_robust_double_free(struct rseq_mempool *pool,
140 enum rseq_mempool_populate_policy policy __attribute__((unused)))
141 {
142 struct test_data __rseq_percpu *ptr;
143
144 ptr = (struct test_data __rseq_percpu *) rseq_mempool_percpu_malloc(pool);
145
146 rseq_mempool_percpu_free(ptr);
147 rseq_mempool_percpu_free(ptr);
148 }
149
150 static void test_robust_corrupt_after_free(struct rseq_mempool *pool,
151 enum rseq_mempool_populate_policy policy)
152 {
153 struct test_data __rseq_percpu *ptr;
154 struct test_data *cpuptr;
155
156 ptr = (struct test_data __rseq_percpu *) rseq_mempool_percpu_malloc(pool);
157 /*
158 * Corrupt free list: For robust pools, the free list is located
159 * after the last cpu memory range for COW_ZERO, and after the init
160 * values memory range for COW_INIT.
161 */
162 if (policy == RSEQ_MEMPOOL_POPULATE_COW_ZERO)
163 cpuptr = (struct test_data *) rseq_percpu_ptr(ptr, rseq_mempool_get_max_nr_cpus(pool));
164 else
165 cpuptr = (struct test_data *) rseq_percpu_ptr(ptr, rseq_mempool_get_max_nr_cpus(pool) + 1);
166
167 rseq_mempool_percpu_free(ptr);
168 cpuptr->value[0] = (uintptr_t) test_robust_corrupt_after_free;
169
170 rseq_mempool_destroy(pool);
171 }
172
173 static void test_robust_memory_leak(struct rseq_mempool *pool,
174 enum rseq_mempool_populate_policy policy __attribute__((unused)))
175 {
176 (void) rseq_mempool_percpu_malloc(pool);
177
178 rseq_mempool_destroy(pool);
179 }
180
181 static void test_robust_free_list_corruption(struct rseq_mempool *pool,
182 enum rseq_mempool_populate_policy policy)
183 {
184 struct test_data __rseq_percpu *ptr;
185 struct test_data *cpuptr;
186
187 ptr = (struct test_data __rseq_percpu *) rseq_mempool_percpu_malloc(pool);
188 /*
189 * Corrupt free list: For robust pools, the free list is located
190 * after the last cpu memory range for COW_ZERO, and after the init
191 * values memory range for COW_INIT.
192 */
193 if (policy == RSEQ_MEMPOOL_POPULATE_COW_ZERO)
194 cpuptr = (struct test_data *) rseq_percpu_ptr(ptr, rseq_mempool_get_max_nr_cpus(pool));
195 else
196 cpuptr = (struct test_data *) rseq_percpu_ptr(ptr, rseq_mempool_get_max_nr_cpus(pool) + 1);
197
198 rseq_mempool_percpu_free(ptr);
199
200 cpuptr->value[0] = (uintptr_t) cpuptr;
201
202 (void) rseq_mempool_percpu_malloc(pool);
203 (void) rseq_mempool_percpu_malloc(pool);
204 }
205
206 static void test_robust_poison_corruption_malloc(struct rseq_mempool *pool,
207 enum rseq_mempool_populate_policy policy __attribute__((unused)))
208 {
209 struct test_data __rseq_percpu *ptr;
210 struct test_data *cpuptr;
211
212 ptr = (struct test_data __rseq_percpu *) rseq_mempool_percpu_malloc(pool);
213 cpuptr = (struct test_data *) rseq_percpu_ptr(ptr, 0);
214
215 rseq_mempool_percpu_free(ptr);
216
217 cpuptr->value[0] = 1;
218
219 (void) rseq_mempool_percpu_malloc(pool);
220 }
221
222 static void test_robust_poison_corruption_destroy(struct rseq_mempool *pool,
223 enum rseq_mempool_populate_policy policy __attribute__((unused)))
224 {
225 struct test_data __rseq_percpu *ptr;
226 struct test_data *cpuptr;
227
228 ptr = (struct test_data __rseq_percpu *) rseq_mempool_percpu_malloc(pool);
229 cpuptr = (struct test_data *) rseq_percpu_ptr(ptr, 0);
230
231 rseq_mempool_percpu_free(ptr);
232
233 cpuptr->value[0] = 1;
234
235 rseq_mempool_destroy(pool);
236 }
237
238 static struct rseq_mempool *make_test_pool(enum rseq_mempool_populate_policy policy)
239 {
240 struct rseq_mempool_attr *attr;
241 struct rseq_mempool *pool;
242 int ret;
243
244 pool = NULL;
245
246 attr = rseq_mempool_attr_create();
247
248 if (!attr) {
249 goto out;
250 }
251
252 ret = rseq_mempool_attr_set_robust(attr);
253
254 if (0 != ret) {
255 goto err_attr;
256 }
257
258 ret = rseq_mempool_attr_set_percpu(attr, RSEQ_MEMPOOL_STRIDE, 1);
259
260 if (0 != ret) {
261 goto err_attr;
262 }
263
264 ret = rseq_mempool_attr_set_populate_policy(attr, policy);
265
266 if (0 != ret) {
267 goto err_attr;
268 }
269
270 pool = rseq_mempool_create("mempool-robust",
271 sizeof(struct test_data), attr);
272 err_attr:
273 rseq_mempool_attr_destroy(attr);
274 out:
275 return pool;
276
277 }
278
279 static int run_robust_test(void (*test)(struct rseq_mempool *, enum rseq_mempool_populate_policy),
280 enum rseq_mempool_populate_policy policy)
281 {
282 pid_t cpid;
283 int status;
284 struct rseq_mempool *pool;
285
286 cpid = fork();
287
288 switch (cpid) {
289 case -1:
290 return 0;
291 case 0:
292 /*
293 * Intentional leak of test pool because some tests might want
294 * to do an explicit destroy on it.
295 */
296 pool = make_test_pool(policy);
297 if (!pool)
298 _exit(EXIT_FAILURE);
299 test(pool, policy);
300 _exit(EXIT_FAILURE);
301 default:
302 waitpid(cpid, &status, 0);
303 }
304
305 if (WIFSIGNALED(status) &&
306 (SIGABRT == WTERMSIG(status)))
307 return 1;
308
309 return 0;
310 }
311
312 static void run_robust_tests(enum rseq_mempool_populate_policy policy)
313 {
314
315 ok(run_robust_test(test_robust_double_free, policy),
316 "robust-double-free");
317
318 ok(run_robust_test(test_robust_memory_leak, policy),
319 "robust-memory-leak");
320
321 ok(run_robust_test(test_robust_poison_corruption_malloc, policy),
322 "robust-poison-corruption-malloc");
323
324 ok(run_robust_test(test_robust_poison_corruption_destroy, policy),
325 "robust-poison-corruption-destroy");
326
327 ok(run_robust_test(test_robust_corrupt_after_free, policy),
328 "robust-corrupt-after-free");
329
330 ok(run_robust_test(test_robust_free_list_corruption, policy),
331 "robust-free-list-corruption");
332 }
333
334 static void fork_child(struct rseq_mempool *pool,
335 enum rseq_mempool_populate_policy policy __attribute__((unused)))
336 {
337 rseq_mempool_destroy(pool);
338 }
339
340 /*
341 * Test that destroying a mempool works in child after fork.
342 */
343 static int run_fork_destroy_pool_test(void (*test)(struct rseq_mempool *, enum rseq_mempool_populate_policy),
344 enum rseq_mempool_populate_policy policy)
345 {
346 pid_t cpid;
347 int status;
348 struct rseq_mempool *pool;
349
350 pool = make_test_pool(policy);
351 if (!pool)
352 _exit(EXIT_FAILURE);
353
354 cpid = fork();
355
356 switch (cpid) {
357 case -1:
358 return 0;
359 case 0:
360 test(pool, policy);
361 _exit(EXIT_SUCCESS);
362 default:
363 waitpid(cpid, &status, 0);
364 }
365
366 if (WIFSIGNALED(status))
367 return 0;
368
369 return 1;
370 }
371
372 int main(void)
373 {
374 size_t len;
375 unsigned long nr_ranges;
376
377 plan_no_plan();
378
379 for (nr_ranges = 1; nr_ranges < 32; nr_ranges <<= 1) {
380 /* From page size to 64kB */
381 for (len = rseq_get_page_len(); len < 65536; len <<= 1) {
382 test_mempool_fill(RSEQ_MEMPOOL_POPULATE_COW_ZERO, nr_ranges, len);
383 test_mempool_fill(RSEQ_MEMPOOL_POPULATE_COW_INIT, nr_ranges, len);
384 }
385 }
386
387 len = rseq_get_page_len();
388 if (len < 65536)
389 len = 65536;
390 /* From min(page size, 64kB) to 4MB */
391 for (; len < 4096 * 1024; len <<= 1) {
392 test_mempool_fill(RSEQ_MEMPOOL_POPULATE_COW_ZERO, 1, len);
393 test_mempool_fill(RSEQ_MEMPOOL_POPULATE_COW_INIT, 1, len);
394 }
395
396 run_robust_tests(RSEQ_MEMPOOL_POPULATE_COW_ZERO);
397 run_robust_tests(RSEQ_MEMPOOL_POPULATE_COW_INIT);
398 ok(run_fork_destroy_pool_test(fork_child, RSEQ_MEMPOOL_POPULATE_COW_ZERO),
399 "fork destroy pool test populate COW_ZERO");
400 ok(run_fork_destroy_pool_test(fork_child, RSEQ_MEMPOOL_POPULATE_COW_INIT),
401 "fork destroy pool test populate COW_INIT");
402
403 exit(exit_status());
404 }
This page took 0.044213 seconds and 5 git commands to generate.