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