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 : : /*
17 : : * _XOPEN_SOURCE is needed for resolving the constant O_CLOEXEC in some
18 : : * environments. We use _XOPEN_SOURCE instead of _GNU_SOURCE because
19 : : * _GNU_SOURCE is not portable and breaks when attempting to build rust
20 : : * bindings on MacOS.
21 : : *
22 : : * https://man7.org/linux/man-pages/man2/open.2.html
23 : : * The O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW flags are not
24 : : * specified in POSIX.1-2001, but are specified in POSIX.1-2008.
25 : : * Since glibc 2.12, one can obtain their definitions by defining
26 : : * either _POSIX_C_SOURCE with a value greater than or equal to
27 : : * 200809L or _XOPEN_SOURCE with a value greater than or equal to
28 : : * 700. In glibc 2.11 and earlier, one obtains the definitions by
29 : : * defining _GNU_SOURCE.
30 : : *
31 : : * We use two feature probes to detect the need to perform this workaround.
32 : : * It is only applied if we can't get CLOEXEC without it and the build doesn't
33 : : * fail with _XOPEN_SOURCE being defined.
34 : : *
35 : : * # Relevent Links
36 : : *
37 : : * - POSIX.1-2017: https://pubs.opengroup.org/onlinepubs/9699919799
38 : : * - https://stackoverflow.com/a/5724485
39 : : * - https://stackoverflow.com/a/5583764
40 : : */
41 : : #if !defined(S2N_CLOEXEC_SUPPORTED) && defined(S2N_CLOEXEC_XOPEN_SUPPORTED) && !defined(_XOPEN_SOURCE)
42 : : #define _XOPEN_SOURCE 700
43 : : #include <fcntl.h>
44 : : #undef _XOPEN_SOURCE
45 : : #else
46 : : #include <fcntl.h>
47 : : #endif
48 : : #include <errno.h>
49 : : #include <limits.h>
50 : : /* LibreSSL requires <openssl/rand.h> include.
51 : : * https://github.com/aws/s2n-tls/issues/153#issuecomment-129651643
52 : : */
53 : : #include <openssl/rand.h>
54 : : #include <stdint.h>
55 : : #include <stdlib.h>
56 : : #include <sys/stat.h>
57 : : #include <sys/types.h>
58 : : #include <time.h>
59 : : #include <unistd.h>
60 : :
61 : : #include "api/s2n.h"
62 : : #include "crypto/s2n_fips.h"
63 : : #include "crypto/s2n_libcrypto.h"
64 : : #include "error/s2n_errno.h"
65 : : #include "s2n_io.h"
66 : : #include "utils/s2n_init.h"
67 : : #include "utils/s2n_mem.h"
68 : : #include "utils/s2n_random.h"
69 : : #include "utils/s2n_result.h"
70 : : #include "utils/s2n_safety.h"
71 : :
72 : : #if defined(O_CLOEXEC)
73 : : #define ENTROPY_FLAGS O_RDONLY | O_CLOEXEC
74 : : #else
75 : : #define ENTROPY_FLAGS O_RDONLY
76 : : #endif
77 : :
78 : : /* One second in nanoseconds */
79 : : #define ONE_S INT64_C(1000000000)
80 : :
81 : : /* Placeholder value for an uninitialized entropy file descriptor */
82 : 0 : #define UNINITIALIZED_ENTROPY_FD -1
83 : :
84 : : static struct s2n_rand_device s2n_dev_urandom = {
85 : : .source = "/dev/urandom",
86 : : .fd = UNINITIALIZED_ENTROPY_FD,
87 : : };
88 : :
89 : : static int s2n_rand_init_cb_impl(void);
90 : : static int s2n_rand_cleanup_cb_impl(void);
91 : : static int s2n_rand_get_entropy_from_urandom(void *ptr, uint32_t size);
92 : :
93 : : static int s2n_rand_entropy_fd_close_ptr(int *fd)
94 : 0 : {
95 [ # # ][ # # ]: 0 : if (fd && *fd != UNINITIALIZED_ENTROPY_FD) {
96 : 0 : close(*fd);
97 : 0 : }
98 : 0 : return S2N_SUCCESS;
99 : 0 : }
100 : :
101 : : /*
102 : : * Use libcrypto for randomness when the linked libcrypto supports at least
103 : : * one of RAND_priv_bytes or RAND_public_bytes, or when the libcrypto is
104 : : * AWS-LC (whose RAND_bytes is trusted even in older FIPS builds that lack
105 : : * the distinct pub/priv APIs). For older libcryptos that lack both and are
106 : : * not AWS-LC (e.g. OpenSSL 1.0.2), fall back to system random (/dev/urandom)
107 : : * to avoid depending on the weaker single-stream PRNG.
108 : : */
109 : : bool s2n_use_libcrypto_rand(void)
110 : 338530 : {
111 : 338530 : #if defined(S2N_LIBCRYPTO_SUPPORTS_PRIVATE_RAND) || defined(S2N_LIBCRYPTO_SUPPORTS_PUBLIC_RAND)
112 : 338530 : return true;
113 : : #elif defined(OPENSSL_IS_AWSLC)
114 : : /* Older AWS-LC FIPS builds (e.g. aws-lc-fips-2022) may lack
115 : : * RAND_priv_bytes/RAND_public_bytes but still provide a trusted
116 : : * RAND_bytes implementation that we can defer to.
117 : : */
118 : : return true;
119 : : #else
120 : : return false;
121 : : #endif
122 : 338530 : }
123 : :
124 : : static S2N_RESULT s2n_get_libcrypto_private_random_data(struct s2n_blob *out_blob)
125 : 3419 : {
126 [ - + ]: 3419 : RESULT_GUARD_PTR(out_blob);
127 [ - + ][ # # ]: 3419 : RESULT_ENSURE_REF(out_blob->data);
128 : 3419 : #if S2N_LIBCRYPTO_SUPPORTS_PRIVATE_RAND
129 [ - + ][ # # ]: 3419 : RESULT_GUARD_OSSL(RAND_priv_bytes(out_blob->data, out_blob->size), S2N_ERR_RANDOM);
130 : : #else
131 : : /* Libcryptos that support RAND_public_bytes but not RAND_priv_bytes still
132 : : * provide RAND_bytes. OpenSSL 1.0.2 (which lacks both) is handled by
133 : : * s2n_use_libcrypto_rand() returning false, so this path is only reached
134 : : * by libcryptos that have at least RAND_public_bytes.
135 : : */
136 : : RESULT_GUARD_OSSL(RAND_bytes(out_blob->data, out_blob->size), S2N_ERR_RANDOM);
137 : : #endif
138 : 3419 : return S2N_RESULT_OK;
139 : 3419 : }
140 : :
141 : : static S2N_RESULT s2n_get_libcrypto_public_random_data(struct s2n_blob *out_blob)
142 : 334383 : {
143 [ - + ]: 334383 : RESULT_GUARD_PTR(out_blob);
144 [ - + ][ # # ]: 334383 : RESULT_ENSURE_REF(out_blob->data);
145 : : #if S2N_LIBCRYPTO_SUPPORTS_PUBLIC_RAND
146 : : RESULT_GUARD_OSSL(RAND_public_bytes(out_blob->data, out_blob->size), S2N_ERR_RANDOM);
147 : : #else
148 [ - + ][ # # ]: 334383 : RESULT_GUARD_OSSL(RAND_bytes(out_blob->data, out_blob->size), S2N_ERR_RANDOM);
149 : 334383 : #endif
150 : 334383 : return S2N_RESULT_OK;
151 : 334383 : }
152 : :
153 : : static S2N_RESULT s2n_get_system_random_data(struct s2n_blob *blob)
154 : 0 : {
155 [ # # ]: 0 : RESULT_GUARD_PTR(blob);
156 [ # # ]: 0 : RESULT_GUARD_PTR(blob->data);
157 : :
158 : : /* This function sets s2n_errno on failure */
159 [ # # ]: 0 : RESULT_GUARD_POSIX(s2n_rand_get_entropy_from_urandom(blob->data, blob->size));
160 : :
161 : 0 : return S2N_RESULT_OK;
162 : 0 : }
163 : :
164 : : S2N_RESULT s2n_get_public_random_data(struct s2n_blob *blob)
165 : 334383 : {
166 [ + - ]: 334383 : if (s2n_use_libcrypto_rand()) {
167 [ - + ]: 334383 : RESULT_GUARD(s2n_get_libcrypto_public_random_data(blob));
168 : 334383 : } else {
169 [ # # ]: 0 : RESULT_GUARD(s2n_get_system_random_data(blob));
170 : 0 : }
171 : 334383 : return S2N_RESULT_OK;
172 : 334383 : }
173 : :
174 : : S2N_RESULT s2n_get_private_random_data(struct s2n_blob *blob)
175 : 3419 : {
176 [ + - ]: 3419 : if (s2n_use_libcrypto_rand()) {
177 [ - + ]: 3419 : RESULT_GUARD(s2n_get_libcrypto_private_random_data(blob));
178 : 3419 : } else {
179 [ # # ]: 0 : RESULT_GUARD(s2n_get_system_random_data(blob));
180 : 0 : }
181 : 3419 : return S2N_RESULT_OK;
182 : 3419 : }
183 : :
184 : : S2N_RESULT s2n_rand_get_urandom_for_test(struct s2n_rand_device **device)
185 : 1 : {
186 [ - + ][ # # ]: 1 : RESULT_ENSURE_REF(device);
187 [ - + ][ # # ]: 1 : RESULT_ENSURE(s2n_in_unit_test(), S2N_ERR_NOT_IN_UNIT_TEST);
188 : 1 : *device = &s2n_dev_urandom;
189 : 1 : return S2N_RESULT_OK;
190 : 1 : }
191 : :
192 : : static S2N_RESULT s2n_rand_device_open(struct s2n_rand_device *device)
193 : 0 : {
194 [ # # ][ # # ]: 0 : RESULT_ENSURE_REF(device);
195 [ # # ][ # # ]: 0 : RESULT_ENSURE_REF(device->source);
196 : :
197 : 0 : DEFER_CLEANUP(int fd = -1, s2n_rand_entropy_fd_close_ptr);
198 [ # # ][ # # ]: 0 : S2N_IO_RETRY_EINTR(fd, open(device->source, ENTROPY_FLAGS));
199 [ # # ][ # # ]: 0 : RESULT_ENSURE(fd >= 0, S2N_ERR_OPEN_RANDOM);
200 : :
201 : 0 : struct stat st = { 0 };
202 [ # # ][ # # ]: 0 : RESULT_ENSURE(fstat(fd, &st) == 0, S2N_ERR_OPEN_RANDOM);
203 : 0 : device->dev = st.st_dev;
204 : 0 : device->ino = st.st_ino;
205 : 0 : device->mode = st.st_mode;
206 : 0 : device->rdev = st.st_rdev;
207 : :
208 : 0 : device->fd = fd;
209 : :
210 : : /* Disable closing the file descriptor with defer cleanup */
211 : 0 : fd = UNINITIALIZED_ENTROPY_FD;
212 : :
213 : 0 : return S2N_RESULT_OK;
214 : 0 : }
215 : :
216 : : S2N_RESULT s2n_rand_device_validate(struct s2n_rand_device *device)
217 : 1 : {
218 [ - + ][ # # ]: 1 : RESULT_ENSURE_REF(device);
219 [ + - ][ + - ]: 1 : RESULT_ENSURE_NE(device->fd, UNINITIALIZED_ENTROPY_FD);
220 : :
221 : : /* Ensure that the random device is still valid by comparing it to the current file descriptor
222 : : * status. From:
223 : : * https://github.com/openssl/openssl/blob/260d97229c467d17934ca3e2e0455b1b5c0994a6/providers/implementations/rands/seeding/rand_unix.c#L513
224 : : */
225 : 0 : struct stat st = { 0 };
226 [ # # ][ # # ]: 0 : RESULT_ENSURE(fstat(device->fd, &st) == 0, S2N_ERR_OPEN_RANDOM);
227 [ # # ][ # # ]: 0 : RESULT_ENSURE_EQ(device->dev, st.st_dev);
228 [ # # ][ # # ]: 0 : RESULT_ENSURE_EQ(device->ino, st.st_ino);
229 [ # # ][ # # ]: 0 : RESULT_ENSURE_EQ(device->rdev, st.st_rdev);
230 : :
231 : : /* Ensure that the mode is the same (equal to 0 when xor'd), but don't check the permission bits. */
232 : 0 : mode_t permission_mask = ~(S_IRWXU | S_IRWXG | S_IRWXO);
233 [ # # ][ # # ]: 0 : RESULT_ENSURE_EQ((device->mode ^ st.st_mode) & permission_mask, 0);
234 : :
235 : 0 : return S2N_RESULT_OK;
236 : 0 : }
237 : :
238 : : static int s2n_rand_get_entropy_from_urandom(void *ptr, uint32_t size)
239 : 0 : {
240 [ # # ][ # # ]: 0 : POSIX_ENSURE_REF(ptr);
241 [ # # ][ # # ]: 0 : POSIX_ENSURE(s2n_dev_urandom.fd != UNINITIALIZED_ENTROPY_FD, S2N_ERR_NOT_INITIALIZED);
242 : :
243 : : /* It's possible that the file descriptor pointing to /dev/urandom was closed or changed from
244 : : * when it was last opened. Ensure that the file descriptor is still valid, and if it isn't,
245 : : * re-open it before getting entropy.
246 : : *
247 : : * If the file descriptor is invalid and the process doesn't have access to /dev/urandom (as is
248 : : * the case within a chroot tree), an error is raised here before attempting to indefinitely
249 : : * read.
250 : : */
251 [ # # ]: 0 : if (s2n_result_is_error(s2n_rand_device_validate(&s2n_dev_urandom))) {
252 [ # # ]: 0 : POSIX_GUARD_RESULT(s2n_rand_device_open(&s2n_dev_urandom));
253 : 0 : }
254 : :
255 : 0 : uint8_t *data = ptr;
256 : 0 : uint32_t n = size;
257 : 0 : struct timespec sleep_time = { .tv_sec = 0, .tv_nsec = 0 };
258 : 0 : long backoff = 1;
259 : :
260 [ # # ]: 0 : while (n) {
261 : 0 : errno = 0;
262 : 0 : int r = read(s2n_dev_urandom.fd, data, n);
263 [ # # ]: 0 : if (r <= 0) {
264 : : /*
265 : : * A non-blocking read() on /dev/urandom should "never" fail,
266 : : * except for EINTR. If it does, briefly pause and use
267 : : * exponential backoff to avoid creating a tight spinning loop.
268 : : *
269 : : * iteration delay
270 : : * --------- -----------------
271 : : * 1 10 nsec
272 : : * 2 100 nsec
273 : : * 3 1,000 nsec
274 : : * 4 10,000 nsec
275 : : * 5 100,000 nsec
276 : : * 6 1,000,000 nsec
277 : : * 7 10,000,000 nsec
278 : : * 8 99,999,999 nsec
279 : : * 9 99,999,999 nsec
280 : : * ...
281 : : */
282 [ # # ]: 0 : if (errno != EINTR) {
283 [ # # ]: 0 : backoff = S2N_MIN(backoff * 10, ONE_S - 1);
284 : 0 : sleep_time.tv_nsec = backoff;
285 : 0 : do {
286 : 0 : r = nanosleep(&sleep_time, &sleep_time);
287 [ # # ]: 0 : } while (r != 0);
288 : 0 : }
289 : :
290 : 0 : continue;
291 : 0 : }
292 : :
293 : 0 : data += r;
294 : 0 : n -= r;
295 : 0 : }
296 : :
297 : 0 : return S2N_SUCCESS;
298 : 0 : }
299 : :
300 : : /*
301 : : * Return a random number in the range [0, bound)
302 : : */
303 : : S2N_RESULT s2n_public_random(int64_t bound, uint64_t *output)
304 : 13148 : {
305 : 13148 : uint64_t r = 0;
306 : :
307 [ + + ][ + - ]: 13148 : RESULT_ENSURE_GT(bound, 0);
308 : :
309 : 13550 : while (1) {
310 : 13550 : struct s2n_blob blob = { 0 };
311 [ - + ]: 13550 : RESULT_GUARD_POSIX(s2n_blob_init(&blob, (void *) &r, sizeof(r)));
312 [ - + ]: 13550 : RESULT_GUARD(s2n_get_public_random_data(&blob));
313 : :
314 : : /* Imagine an int was one byte and UINT_MAX was 256. If the
315 : : * caller asked for s2n_random(129, ...) we'd end up in
316 : : * trouble. Each number in the range 0...127 would be twice
317 : : * as likely as 128. That's because r == 0 % 129 -> 0, and
318 : : * r == 129 % 129 -> 0, but only r == 128 returns 128,
319 : : * r == 257 is out of range.
320 : : *
321 : : * To de-bias the dice, we discard values of r that are higher
322 : : * that the highest multiple of 'bound' an int can support. If
323 : : * bound is a uint, then in the worst case we discard 50% - 1 r's.
324 : : * But since 'bound' is an int and INT_MAX is <= UINT_MAX / 2,
325 : : * in the worst case we discard 25% - 1 r's.
326 : : */
327 [ + + ]: 13550 : if (r < (UINT64_MAX - (UINT64_MAX % bound))) {
328 : 13146 : *output = r % bound;
329 : 13146 : return S2N_RESULT_OK;
330 : 13146 : }
331 : 13550 : }
332 : 13146 : }
333 : :
334 : : static int s2n_rand_init_cb_impl(void)
335 : 0 : {
336 [ # # ]: 0 : POSIX_GUARD_RESULT(s2n_rand_device_open(&s2n_dev_urandom));
337 : :
338 : 0 : return S2N_SUCCESS;
339 : 0 : }
340 : :
341 : : S2N_RESULT s2n_rand_init(void)
342 : 345 : {
343 : : /* Only open /dev/urandom when we actually need it for randomness.
344 : : * When libcrypto handles randomness, avoid the extra syscall and fd.
345 : : */
346 [ - + ]: 345 : if (!s2n_use_libcrypto_rand()) {
347 [ # # ][ # # ]: 0 : RESULT_ENSURE(s2n_rand_init_cb_impl() >= S2N_SUCCESS, S2N_ERR_CANCELLED);
348 : 0 : }
349 : :
350 : 345 : return S2N_RESULT_OK;
351 : 345 : }
352 : :
353 : : static int s2n_rand_cleanup_cb_impl(void)
354 : 0 : {
355 [ # # ]: 0 : if (s2n_dev_urandom.fd == UNINITIALIZED_ENTROPY_FD) {
356 : 0 : return S2N_SUCCESS;
357 : 0 : }
358 : :
359 [ # # ]: 0 : if (s2n_result_is_ok(s2n_rand_device_validate(&s2n_dev_urandom))) {
360 [ # # ]: 0 : POSIX_GUARD(close(s2n_dev_urandom.fd));
361 : 0 : }
362 : 0 : s2n_dev_urandom.fd = UNINITIALIZED_ENTROPY_FD;
363 : :
364 : 0 : return S2N_SUCCESS;
365 : 0 : }
366 : :
367 : : S2N_RESULT s2n_rand_cleanup(void)
368 : 372 : {
369 [ - + ]: 372 : if (!s2n_use_libcrypto_rand()) {
370 [ # # ][ # # ]: 0 : RESULT_ENSURE(s2n_rand_cleanup_cb_impl() >= S2N_SUCCESS, S2N_ERR_CANCELLED);
371 : 0 : }
372 : :
373 : 372 : return S2N_RESULT_OK;
374 : 372 : }
375 : :
376 : : int s2n_rand_set_callbacks(s2n_rand_init_callback rand_init_callback,
377 : : s2n_rand_cleanup_callback rand_cleanup_callback,
378 : : s2n_rand_seed_callback rand_seed_callback,
379 : : s2n_rand_mix_callback rand_mix_callback)
380 : 0 : {
381 : : /* Custom random callbacks are no longer supported. Randomness is now
382 : : * delegated directly to libcrypto or /dev/urandom. This stub is kept
383 : : * for backwards compatibility.
384 : : */
385 : 0 : (void) rand_init_callback;
386 : 0 : (void) rand_cleanup_callback;
387 : 0 : (void) rand_seed_callback;
388 : 0 : (void) rand_mix_callback;
389 : 0 : return S2N_SUCCESS;
390 : 0 : }
|