• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright (c) 2020, Google Inc.
2  *
3  * Permission to use, copy, modify, and/or distribute this software for any
4  * purpose with or without fee is hereby granted, provided that the above
5  * copyright notice and this permission notice appear in all copies.
6  *
7  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10  * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
14 
15 #if !defined(_GNU_SOURCE)
16 #define _GNU_SOURCE  // needed for madvise() and MAP_ANONYMOUS on Linux.
17 #endif
18 
19 #include <openssl/base.h>
20 
21 #include "fork_detect.h"
22 
23 #if defined(OPENSSL_LINUX)
24 #include <assert.h>
25 #include <sys/mman.h>
26 #include <unistd.h>
27 #include <stdlib.h>
28 
29 #include "../delocate.h"
30 #include "../../internal.h"
31 
32 
33 #if defined(MADV_WIPEONFORK)
34 static_assert(MADV_WIPEONFORK == 18, "MADV_WIPEONFORK is not 18");
35 #else
36 #define MADV_WIPEONFORK 18
37 #endif
38 
39 DEFINE_STATIC_ONCE(g_fork_detect_once);
40 DEFINE_STATIC_MUTEX(g_fork_detect_lock);
41 DEFINE_BSS_GET(volatile char *, g_fork_detect_addr);
42 DEFINE_BSS_GET(uint64_t, g_fork_generation);
43 DEFINE_BSS_GET(int, g_force_madv_wipeonfork);
44 DEFINE_BSS_GET(int, g_force_madv_wipeonfork_enabled);
45 
init_fork_detect(void)46 static void init_fork_detect(void) {
47   if (*g_force_madv_wipeonfork_bss_get()) {
48     return;
49   }
50 
51   long page_size = sysconf(_SC_PAGESIZE);
52   if (page_size <= 0) {
53     return;
54   }
55 
56   void *addr = mmap(NULL, (size_t)page_size, PROT_READ | PROT_WRITE,
57                     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
58   if (addr == MAP_FAILED) {
59     return;
60   }
61 
62   // Some versions of qemu (up to at least 5.0.0-rc4, see linux-user/syscall.c)
63   // ignore |madvise| calls and just return zero (i.e. success). But we need to
64   // know whether MADV_WIPEONFORK actually took effect. Therefore try an invalid
65   // call to check that the implementation of |madvise| is actually rejecting
66   // unknown |advice| values.
67   if (madvise(addr, (size_t)page_size, -1) == 0 ||
68       madvise(addr, (size_t)page_size, MADV_WIPEONFORK) != 0) {
69     munmap(addr, (size_t)page_size);
70     return;
71   }
72 
73   *((volatile char *) addr) = 1;
74   *g_fork_detect_addr_bss_get() = addr;
75   *g_fork_generation_bss_get() = 1;
76 }
77 
CRYPTO_get_fork_generation(void)78 uint64_t CRYPTO_get_fork_generation(void) {
79   // In a single-threaded process, there are obviously no races because there's
80   // only a single mutator in the address space.
81   //
82   // In a multi-threaded environment, |CRYPTO_once| ensures that the flag byte
83   // is initialised atomically, even if multiple threads enter this function
84   // concurrently.
85   //
86   // In the limit, the kernel may clear WIPEONFORK pages while a multi-threaded
87   // process is running. (For example, because a VM was cloned.) Therefore a
88   // lock is used below to synchronise the potentially multiple threads that may
89   // concurrently observe the cleared flag.
90 
91   CRYPTO_once(g_fork_detect_once_bss_get(), init_fork_detect);
92   // This pointer is |volatile| because the value pointed to may be changed by
93   // external forces (i.e. the kernel wiping the page) thus the compiler must
94   // not assume that it has exclusive access to it.
95   volatile char *const flag_ptr = *g_fork_detect_addr_bss_get();
96   if (flag_ptr == NULL) {
97     // Our kernel is too old to support |MADV_WIPEONFORK| or
98     // |g_force_madv_wipeonfork| is set.
99     if (*g_force_madv_wipeonfork_bss_get() &&
100         *g_force_madv_wipeonfork_enabled_bss_get()) {
101       // A constant generation number to simulate support, even if the kernel
102       // doesn't support it.
103       return 42;
104     }
105     return 0;
106   }
107 
108   struct CRYPTO_STATIC_MUTEX *const lock = g_fork_detect_lock_bss_get();
109   uint64_t *const generation_ptr = g_fork_generation_bss_get();
110 
111   CRYPTO_STATIC_MUTEX_lock_read(lock);
112   uint64_t current_generation = *generation_ptr;
113   if (*flag_ptr) {
114     CRYPTO_STATIC_MUTEX_unlock_read(lock);
115     return current_generation;
116   }
117 
118   CRYPTO_STATIC_MUTEX_unlock_read(lock);
119   CRYPTO_STATIC_MUTEX_lock_write(lock);
120   current_generation = *generation_ptr;
121   if (*flag_ptr == 0) {
122     // A fork has occurred.
123     *flag_ptr = 1;
124 
125     current_generation++;
126     if (current_generation == 0) {
127       current_generation = 1;
128     }
129     *generation_ptr = current_generation;
130   }
131   CRYPTO_STATIC_MUTEX_unlock_write(lock);
132 
133   return current_generation;
134 }
135 
CRYPTO_fork_detect_force_madv_wipeonfork_for_testing(int on)136 void CRYPTO_fork_detect_force_madv_wipeonfork_for_testing(int on) {
137   *g_force_madv_wipeonfork_bss_get() = 1;
138   *g_force_madv_wipeonfork_enabled_bss_get() = on;
139 }
140 
141 #else   // !OPENSSL_LINUX
142 
CRYPTO_get_fork_generation(void)143 uint64_t CRYPTO_get_fork_generation(void) { return 0; }
144 
145 #endif  // OPENSSL_LINUX
146