LCOV - code coverage report
Current view: top level - utils - s2n_random.c (source / functions) Hit Total Coverage
Test: unit_test_coverage.info Lines: 60 155 38.7 %
Date: 2026-04-01 07:28:41 Functions: 10 17 58.8 %
Branches: 24 148 16.2 %

           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 : }

Generated by: LCOV version 1.14