Commit | Line | Data |
---|---|---|
2a6740bc MD |
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 | |
2a6740bc MD |
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(); | |
855b8e69 | 76 | ret = rseq_mempool_attr_set_percpu(attr, 0, 1); |
2a6740bc MD |
77 | if (ret) |
78 | abort(); | |
79 | ret = rseq_mempool_attr_set_max_nr_ranges(attr, 1); | |
80 | if (ret) | |
81 | abort(); | |
4e8ae59d | 82 | ret = rseq_mempool_attr_set_populate_policy(attr, RSEQ_MEMPOOL_POPULATE_PRIVATE_NONE); |
2a6740bc MD |
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 | ||
855b8e69 MD |
114 | rseq_mempool_percpu_free(thread_args->ptr2); |
115 | rseq_mempool_percpu_free(thread_args->ptr1); | |
2a6740bc MD |
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 | } |