Branch data Line data Source code
1 : : /*
2 : : * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 : : *
4 : : * Licensed under the Apache License, Version 2.0 (the "License").
5 : : * You may not use this file except in compliance with the License.
6 : : * A copy of the License is located at
7 : : *
8 : : * http://aws.amazon.com/apache2.0
9 : : *
10 : : * or in the "license" file accompanying this file. This file is distributed
11 : : * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 : : * express or implied. See the License for the specific language governing
13 : : * permissions and limitations under the License.
14 : : */
15 : :
16 : : /* force the internal header to be included first, since it modifies _GNU_SOURCE/_POSIX_C_SOURCE */
17 : : /* clang-format off */
18 : : #include "utils/s2n_fork_detection_features.h"
19 : : /* clang-format on */
20 : :
21 : : #include "utils/s2n_fork_detection.h"
22 : :
23 : : #include "error/s2n_errno.h"
24 : : #include "utils/s2n_safety.h"
25 : :
26 : : #if defined(S2N_MADVISE_SUPPORTED) && defined(MADV_WIPEONFORK)
27 : : #if (MADV_WIPEONFORK != 18)
28 : : #error "MADV_WIPEONFORK is not 18"
29 : : #endif
30 : : #else /* defined(S2N_MADVISE_SUPPORTED) && defined(MADV_WIPEONFORK) */
31 : : #define MADV_WIPEONFORK 18
32 : : #endif
33 : :
34 : : /* Sometimes (for example, on FreeBSD) MAP_INHERIT_ZERO is called INHERIT_ZERO */
35 : : #if !defined(MAP_INHERIT_ZERO) && defined(INHERIT_ZERO)
36 : : #define MAP_INHERIT_ZERO INHERIT_ZERO
37 : : #endif
38 : :
39 : : /* These variables are used to disable all fork detection mechanisms or at the
40 : : * individual level during testing.
41 : : */
42 : : static bool ignore_wipeonfork_or_inherit_zero_method_for_testing = false;
43 : : static bool ignore_pthread_atfork_method_for_testing = false;
44 : : static bool ignore_fork_detection_for_testing = false;
45 : :
46 : 1225693 : #define S2N_FORK_EVENT 0
47 : 741 : #define S2N_NO_FORK_EVENT 1
48 : :
49 : : struct FGN_STATE {
50 : : /* The current cached fork generation number for this process */
51 : : uint64_t current_fork_generation_number;
52 : :
53 : : /* Semaphore controlling access to the shared sentinel and signaling whether
54 : : * fork detection is enabled or not. We could use zero_on_fork_addr, but
55 : : * avoid overloading by using an explicit variable.
56 : : */
57 : : bool is_fork_detection_enabled;
58 : :
59 : : /* Sentinel that signals a fork event has occurred */
60 : : volatile char *zero_on_fork_addr;
61 : :
62 : : pthread_once_t fork_detection_once;
63 : : pthread_rwlock_t fork_detection_rw_lock;
64 : : };
65 : :
66 : : /* We only need a single statically initialised state. Note, the state is
67 : : * inherited by child processes.
68 : : */
69 : : static struct FGN_STATE fgn_state = {
70 : : .current_fork_generation_number = 0,
71 : : .is_fork_detection_enabled = false,
72 : : .zero_on_fork_addr = NULL,
73 : : .fork_detection_once = PTHREAD_ONCE_INIT,
74 : : .fork_detection_rw_lock = PTHREAD_RWLOCK_INITIALIZER,
75 : : };
76 : :
77 : : /* Can currently never fail. See initialise_fork_detection_methods() for
78 : : * motivation.
79 : : */
80 : : static inline S2N_RESULT s2n_initialise_wipeonfork_best_effort(void *addr, long page_size)
81 : 481 : {
82 : 481 : #if defined(S2N_MADVISE_SUPPORTED)
83 : : /* Return value ignored on purpose */
84 : 481 : madvise(addr, (size_t) page_size, MADV_WIPEONFORK);
85 : 481 : #endif
86 : :
87 : 481 : return S2N_RESULT_OK;
88 : 481 : }
89 : :
90 : : static inline S2N_RESULT s2n_initialise_inherit_zero(void *addr, long page_size)
91 : 481 : {
92 : : #if defined(S2N_MINHERIT_SUPPORTED) && defined(MAP_INHERIT_ZERO)
93 : : RESULT_ENSURE(minherit(addr, page_size, MAP_INHERIT_ZERO) == 0, S2N_ERR_FORK_DETECTION_INIT);
94 : : #endif
95 : :
96 : 481 : return S2N_RESULT_OK;
97 : 481 : }
98 : :
99 : : static void s2n_pthread_atfork_on_fork(void)
100 : 186 : {
101 : : /* This zeroises the first byte of the memory page pointed to by
102 : : * *zero_on_fork_addr. This is the same byte used as fork event detection
103 : : * sentinel in s2n_get_fork_generation_number(). The same memory page, and in
104 : : * turn, the byte, is also the memory zeroised by the MADV_WIPEONFORK fork
105 : : * detection mechanism.
106 : : *
107 : : * Aquire locks to be on the safe side. We want to avoid the checks in
108 : : * s2n_get_fork_generation_number() getting executed before setting the sentinel
109 : : * flag. The write lock prevents any other thread from owning any other type
110 : : * of lock.
111 : : *
112 : : * pthread_atfork_on_fork() cannot return errors. Hence, there is no way to
113 : : * gracefully recover if [un]locking fails.
114 : : */
115 [ - + ]: 186 : if (pthread_rwlock_wrlock(&fgn_state.fork_detection_rw_lock) != 0) {
116 : 0 : printf("pthread_rwlock_wrlock() failed. Aborting.\n");
117 : 0 : abort();
118 : 0 : }
119 : :
120 [ - + ]: 186 : if (fgn_state.zero_on_fork_addr == NULL) {
121 : 0 : printf("fgn_state.zero_on_fork_addr is NULL. Aborting.\n");
122 : 0 : abort();
123 : 0 : }
124 : 186 : *fgn_state.zero_on_fork_addr = 0;
125 : :
126 [ - + ]: 186 : if (pthread_rwlock_unlock(&fgn_state.fork_detection_rw_lock) != 0) {
127 : 0 : printf("pthread_rwlock_unlock() failed. Aborting.\n");
128 : 0 : abort();
129 : 0 : }
130 : 186 : }
131 : :
132 : : static S2N_RESULT s2n_inititalise_pthread_atfork(void)
133 : 476 : {
134 : : /* Register the fork handler pthread_atfork_on_fork that is executed in the
135 : : * child process after a fork.
136 : : */
137 [ + - ]: 476 : if (s2n_is_pthread_atfork_supported() == true) {
138 [ - + ][ # # ]: 476 : RESULT_ENSURE(pthread_atfork(NULL, NULL, s2n_pthread_atfork_on_fork) == 0, S2N_ERR_FORK_DETECTION_INIT);
139 : 476 : }
140 : :
141 : 476 : return S2N_RESULT_OK;
142 : 476 : }
143 : :
144 : : static S2N_RESULT s2n_initialise_fork_detection_methods_try(void *addr, long page_size)
145 : 516 : {
146 [ - + ]: 516 : RESULT_GUARD_PTR(addr);
147 : :
148 : : /* Some systems don't define MADV_WIPEONFORK in sys/mman.h but the kernel
149 : : * still supports the mechanism (AL2 being a prime example). Likely because
150 : : * glibc on the system is old. We might be able to include kernel header
151 : : * files directly, that define MADV_WIPEONFORK, conditioning on specific
152 : : * OS's. But it is a mess. A more reliable method is to probe the system, at
153 : : * run-time, whether madvise supports the MADV_WIPEONFORK advice. However,
154 : : * the method to probe for this feature is equivalent to actually attempting
155 : : * to initialise the MADV_WIPEONFORK fork detection. Compare with
156 : : * probe_madv_wipeonfork_support() (used for testing).
157 : : *
158 : : * Instead, we apply best-effort to initialise the MADV_WIPEONFORK fork
159 : : * detection and otherwise always require pthread_atfork to be initialised.
160 : : * We also currently always apply prediction resistance. So, this should be
161 : : * a safe default.
162 : : */
163 [ + + ]: 516 : if (ignore_wipeonfork_or_inherit_zero_method_for_testing == false) {
164 [ - + ]: 481 : RESULT_GUARD(s2n_initialise_wipeonfork_best_effort(addr, page_size));
165 : 481 : }
166 : :
167 [ + + ]: 516 : if (ignore_wipeonfork_or_inherit_zero_method_for_testing == false) {
168 [ - + ]: 481 : RESULT_GUARD(s2n_initialise_inherit_zero(addr, page_size));
169 : 481 : }
170 : :
171 [ + + ]: 516 : if (ignore_pthread_atfork_method_for_testing == false) {
172 [ - + ]: 476 : RESULT_GUARD(s2n_inititalise_pthread_atfork());
173 : 476 : }
174 : :
175 : 516 : fgn_state.zero_on_fork_addr = addr;
176 : 516 : *fgn_state.zero_on_fork_addr = S2N_NO_FORK_EVENT;
177 : 516 : fgn_state.is_fork_detection_enabled = true;
178 : :
179 : 516 : return S2N_RESULT_OK;
180 : 516 : }
181 : :
182 : : static S2N_RESULT s2n_setup_mapping(void **addr, long *page_size)
183 : 620 : {
184 : 620 : *page_size = sysconf(_SC_PAGESIZE);
185 [ - + ][ # # ]: 620 : RESULT_ENSURE_GT(*page_size, 0);
186 : :
187 : 620 : *addr = mmap(NULL, (size_t) *page_size, PROT_READ | PROT_WRITE,
188 : 620 : MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
189 [ - + ][ # # ]: 620 : RESULT_ENSURE_NE(*addr, MAP_FAILED);
190 : :
191 : 620 : return S2N_RESULT_OK;
192 : 620 : }
193 : :
194 : : static void s2n_initialise_fork_detection_methods(void)
195 : 516 : {
196 : 516 : void *addr = MAP_FAILED;
197 : 516 : long page_size = 0;
198 : :
199 : : /* Only used to disable fork detection mechanisms during testing. */
200 [ + + ][ - + ]: 516 : if (ignore_wipeonfork_or_inherit_zero_method_for_testing == true && ignore_pthread_atfork_method_for_testing == true) {
201 : 0 : ignore_fork_detection_for_testing = true;
202 : 0 : return;
203 : 0 : }
204 : :
205 [ - + ]: 516 : if (s2n_result_is_error(s2n_setup_mapping(&addr, &page_size)) == true) {
206 : 0 : return;
207 : 0 : }
208 : :
209 : : /* Now we know that we have some memory mapped. Try to initialise fork
210 : : * detection methods. Unmap the memory if we fail for some reason.
211 : : */
212 [ - + ]: 516 : if (s2n_result_is_error(s2n_initialise_fork_detection_methods_try(addr, page_size)) == true) {
213 : : /* No reason to verify return value of munmap() since we can't use that
214 : : * information for anything anyway. */
215 : 0 : munmap(addr, (size_t) page_size);
216 : 0 : addr = NULL;
217 : 0 : fgn_state.zero_on_fork_addr = NULL;
218 : 0 : fgn_state.is_fork_detection_enabled = false;
219 : 0 : }
220 : 516 : }
221 : :
222 : : /* s2n_get_fork_generation_number returns S2N_RESULT_OK on success and
223 : : * S2N_RESULT_ERROR otherwise.
224 : : *
225 : : * On success, returns the current fork generation number in
226 : : * return_fork_generation_number. Caller must synchronise access to
227 : : * return_fork_generation_number.
228 : : */
229 : : S2N_RESULT s2n_get_fork_generation_number(uint64_t *return_fork_generation_number)
230 : 1225516 : {
231 [ # # ][ - + ]: 1225516 : RESULT_ENSURE(pthread_once(&fgn_state.fork_detection_once, s2n_initialise_fork_detection_methods) == 0, S2N_ERR_FORK_DETECTION_INIT);
232 : :
233 [ - + ]: 1225516 : if (ignore_fork_detection_for_testing == true) {
234 : : /* Fork detection is meant to be disabled. Hence, return success.
235 : : * This should only happen during testing.
236 : : */
237 [ # # ][ # # ]: 0 : RESULT_ENSURE(s2n_in_unit_test(), S2N_ERR_NOT_IN_UNIT_TEST);
238 : 0 : return S2N_RESULT_OK;
239 : 0 : }
240 : :
241 [ # # ][ - + ]: 1225516 : RESULT_ENSURE(fgn_state.is_fork_detection_enabled == true, S2N_ERR_FORK_DETECTION_INIT);
242 : :
243 : : /* In most cases, we would not need to increment the fork generation number.
244 : : * So, it is cheaper, in the expected case, to take an optimistic read lock
245 : : * and later aquire a write lock if needed.
246 : : * Note that we set the returned fgn before checking for a fork event. We
247 : : * need to do this because thread execution might change between releasing
248 : : * the read lock and taking the write lock. In that time span, another
249 : : * thread can reset the fork event detection sentinel and we return from
250 : : * s2n_get_fork_generation_number() without setting the returned fgn
251 : : * appropriately.
252 : : */
253 [ # # ][ - + ]: 1225516 : RESULT_ENSURE(pthread_rwlock_rdlock(&fgn_state.fork_detection_rw_lock) == 0, S2N_ERR_RETRIEVE_FORK_GENERATION_NUMBER);
254 : 1225516 : *return_fork_generation_number = fgn_state.current_fork_generation_number;
255 [ + + ]: 1225516 : if (*fgn_state.zero_on_fork_addr != S2N_FORK_EVENT) {
256 : : /* No fork event detected. */
257 [ - + ][ # # ]: 1225339 : RESULT_ENSURE(pthread_rwlock_unlock(&fgn_state.fork_detection_rw_lock) == 0, S2N_ERR_RETRIEVE_FORK_GENERATION_NUMBER);
258 : 1225339 : return S2N_RESULT_OK;
259 : 1225339 : }
260 [ # # ][ - + ]: 177 : RESULT_ENSURE(pthread_rwlock_unlock(&fgn_state.fork_detection_rw_lock) == 0, S2N_ERR_RETRIEVE_FORK_GENERATION_NUMBER);
261 : :
262 : : /* We are mutating the process-global, cached fork generation number. Need
263 : : * to acquire the write lock for that. Set returned fgn before checking the
264 : : * if condition with the same reasons as above.
265 : : */
266 [ - + ][ # # ]: 177 : RESULT_ENSURE(pthread_rwlock_wrlock(&fgn_state.fork_detection_rw_lock) == 0, S2N_ERR_RETRIEVE_FORK_GENERATION_NUMBER);
267 : 177 : *return_fork_generation_number = fgn_state.current_fork_generation_number;
268 [ + + ]: 225 : if (*fgn_state.zero_on_fork_addr == S2N_FORK_EVENT) {
269 : : /* Fork event has been detected; reset sentinel, increment cached fork
270 : : * generation number (which is now "current" in this child process), and
271 : : * write incremented fork generation number to the output parameter.
272 : : */
273 : 225 : *fgn_state.zero_on_fork_addr = S2N_NO_FORK_EVENT;
274 : 225 : fgn_state.current_fork_generation_number = fgn_state.current_fork_generation_number + 1;
275 : 225 : *return_fork_generation_number = fgn_state.current_fork_generation_number;
276 : 225 : }
277 [ - + ][ # # ]: 177 : RESULT_ENSURE(pthread_rwlock_unlock(&fgn_state.fork_detection_rw_lock) == 0, S2N_ERR_RETRIEVE_FORK_GENERATION_NUMBER);
278 : :
279 : 177 : return S2N_RESULT_OK;
280 : 177 : }
281 : :
282 : : static void s2n_cleanup_cb_munmap(void **probe_addr)
283 : 104 : {
284 : 104 : munmap(*probe_addr, (size_t) sysconf(_SC_PAGESIZE));
285 : 104 : }
286 : :
287 : : /* Run-time probe checking whether the system supports the MADV_WIPEONFORK fork
288 : : * detection mechanism.
289 : : */
290 : : static S2N_RESULT s2n_probe_madv_wipeonfork_support(void)
291 : 104 : {
292 : 104 : bool result = false;
293 : :
294 : : /* It is not an error to call munmap on a range that does not contain any
295 : : * mapped pages.
296 : : */
297 : 104 : DEFER_CLEANUP(void *probe_addr = MAP_FAILED, s2n_cleanup_cb_munmap);
298 : 104 : long page_size = 0;
299 : :
300 [ - + ]: 104 : RESULT_GUARD(s2n_setup_mapping(&probe_addr, &page_size));
301 : :
302 : 104 : #if defined(S2N_MADVISE_SUPPORTED)
303 : : /* Some versions of qemu (up to at least 5.0.0-rc4, see
304 : : * linux-user/syscall.c) ignore invalid advice arguments. Hence, we first
305 : : * verify that madvise() rejects advice arguments it doesn't know about.
306 : : */
307 [ # # ][ - + ]: 104 : RESULT_ENSURE_NE(madvise(probe_addr, (size_t) page_size, -1), 0);
308 [ # # ][ - + ]: 104 : RESULT_ENSURE_EQ(madvise(probe_addr, (size_t) page_size, MADV_WIPEONFORK), 0);
309 : :
310 : 104 : result = true;
311 : 104 : #endif
312 : :
313 [ # # ][ - + ]: 104 : RESULT_ENSURE_EQ(result, true);
314 : :
315 : 104 : return S2N_RESULT_OK;
316 : 104 : }
317 : :
318 : : bool s2n_is_madv_wipeonfork_supported(void)
319 : 104 : {
320 : 104 : return s2n_result_is_ok(s2n_probe_madv_wipeonfork_support());
321 : 104 : }
322 : :
323 : : bool s2n_is_map_inherit_zero_supported(void)
324 : 2 : {
325 : : #if defined(S2N_MINHERIT_SUPPORTED) && defined(MAP_INHERIT_ZERO)
326 : : return true;
327 : : #else
328 : 2 : return false;
329 : 2 : #endif
330 : 2 : }
331 : :
332 : : bool s2n_is_pthread_atfork_supported(void)
333 : 511 : {
334 : : /*
335 : : * There is a bug in OpenBSD's libc which is triggered by
336 : : * multi-generational forking of multi-threaded processes which call
337 : : * pthread_atfork(3). Under these conditions, a grandchild process will
338 : : * deadlock when trying to fork a great-grandchild.
339 : : * https://marc.info/?l=openbsd-tech&m=167047636422884&w=2
340 : : */
341 : : #if defined(__OpenBSD__)
342 : : return false;
343 : : #else
344 : 511 : return true;
345 : 511 : #endif
346 : 511 : }
347 : :
348 : : /* Use for testing only */
349 : : S2N_RESULT s2n_ignore_wipeonfork_and_inherit_zero_for_testing(void)
350 : 35 : {
351 [ # # ][ - + ]: 35 : RESULT_ENSURE(s2n_in_unit_test(), S2N_ERR_NOT_IN_UNIT_TEST);
352 : :
353 : 35 : ignore_wipeonfork_or_inherit_zero_method_for_testing = true;
354 : :
355 : 35 : return S2N_RESULT_OK;
356 : 35 : }
357 : :
358 : : S2N_RESULT s2n_ignore_pthread_atfork_for_testing(void)
359 : 40 : {
360 [ # # ][ - + ]: 40 : RESULT_ENSURE(s2n_in_unit_test(), S2N_ERR_NOT_IN_UNIT_TEST);
361 : :
362 : 40 : ignore_pthread_atfork_method_for_testing = true;
363 : :
364 : 40 : return S2N_RESULT_OK;
365 : 40 : }
|