// Copyright (c) 2014, Google Inc. // SPDX-License-Identifier: ISC #if !defined(_GNU_SOURCE) #define _GNU_SOURCE // needed for syscall() on Linux. #endif #include #include "internal.h" #if defined(OPENSSL_RAND_URANDOM) #include #include #include #include #include #include #include #if defined(OPENSSL_LINUX) #if defined(AWS_LC_URANDOM_NEEDS_U32) // On old Linux OS: unknown type name '__u32' when include . // If '__u32' is predefined, redefine will cause compiler error. typedef unsigned int __u32; #endif #include #include #include #include #if defined(OPENSSL_ANDROID) #include #endif #if !defined(OPENSSL_ANDROID) #define OPENSSL_HAS_GETAUXVAL #endif // glibc prior to 2.16 does not have getauxval and sys/auxv.h. Android has some // host builds (i.e. not building for Android itself, so |OPENSSL_ANDROID| is // unset) which are still using a 2.15 sysroot. // // TODO(davidben): Remove this once Android updates their sysroot. #if defined(__GLIBC_PREREQ) #if !__GLIBC_PREREQ(2, 16) #undef OPENSSL_HAS_GETAUXVAL #endif #endif #if defined(OPENSSL_HAS_GETAUXVAL) #include #endif #endif // OPENSSL_LINUX #include #include #include "getrandom_fillin.h" #include "../internal.h" #if defined(OPENSSL_MSAN) void __msan_unpoison(void *, size_t); #endif #ifndef MIN #define AWSLC_MIN(X,Y) (((X) < (Y)) ? (X) : (Y)) #else #define AWSLC_MIN(X,Y) MIN(X,Y) #endif // One second in nanoseconds. #define ONE_SECOND INT64_C(1000000000) // 250 milliseconds in nanoseconds. #define MILLISECONDS_250 INT64_C(250000000) #define INITIAL_BACKOFF_DELAY 1 enum random_flavor_t { NOT_CHOSEN, USE_GETRANDOM, USE_DEV_URANDOM }; // random_flavor determines the randomness function used. This is either // getrandom or /dev/urandom. It's protected by |initialize_random_flavor_once|. // If both getrandom and /dev/urandom are available, we prefer getrandom. The // reasons are: // - getrandom has better blocking semantics than /dev/urandom; Former blocks // on initialization by default. // - getrandom doesn't require a file descriptor. // - Existence of vgetrandom can yield greater performance, see e.g. // https://www.phoronix.com/news/glibc-getrandom-vDSO-Merged. static enum random_flavor_t random_flavor = NOT_CHOSEN; enum random_state_t { STATE_NOT_READY, STATE_READY }; // random_flavor_state is |STATE_READY| if the entropy pool of |random_flavor| // has been (fully) initialized and |STATE_NOT_READY| otherwise. Initialized in // in this context means that the implementation of |random_flavor| has decided // that enough entropy/noise has been accumulated. It's protected by // |ensure_random_state_is_initialized_once|. // // If |random_flavor_state| has been initialized, we assume it stays // initialized forever. That is, we assume "entropy depletion" does not exist. // See e.g. https://www.nccgroup.com/us/research-blog/on-linux-s-random-number-generation/ // for an exposé on entropy depletion. // // If a consumer requests a non-blocking read of randomness from // |random_flavor|, the state |random_flavor_state| is not checked before // returning randomness. static enum random_state_t random_flavor_state = STATE_NOT_READY; // urandom_fd is a file descriptor to /dev/urandom. It's protected by // |initialize_random_flavor_once|. static int urandom_fd = 0; static CRYPTO_once_t initialize_random_flavor_once = CRYPTO_ONCE_INIT; static CRYPTO_once_t ensure_random_state_is_initialized_once = CRYPTO_ONCE_INIT; // getrandom_syscall_number_available returns 1 if the getrandom system call // number is defined, otherwise 0. static int getrandom_syscall_number_available(void) { #if defined(USE_NR_getrandom) return 1; #else return 0; #endif } // do_backoff initiates exponential backoff. |backoff| holds the previous // backoff delay. Initial backoff delay is |INITIAL_BACKOFF_DELAY|. This // function will be called so rarely (if ever), that we keep it as a function // call and don't care about attempting to inline it. static void do_backoff(long *backoff) { // Exponential backoff. // // iteration delay // --------- ----------------- // 1 10 nsec // 2 100 nsec // 3 1,000 nsec // 4 10,000 nsec // 5 100,000 nsec // 6 1,000,000 nsec // 7 10,000,000 nsec // 8 99,999,999 nsec // 9 99,999,999 nsec // ... struct timespec sleep_time = {.tv_sec = 0, .tv_nsec = 0 }; // Cap backoff at 99,999,999 nsec, which is the maximum value the nanoseconds // field in |timespec| can hold. *backoff = AWSLC_MIN((*backoff) * 10, ONE_SECOND - 1); // |nanosleep| can mutate |sleep_time|. Hence, we use |backoff| for state. sleep_time.tv_nsec = *backoff; nanosleep(&sleep_time, &sleep_time); } static ssize_t wrapper_dev_urandom(void *buf, size_t buf_len, int block) { ssize_t ret = 0; size_t retry_counter = 0; long backoff = INITIAL_BACKOFF_DELAY; do { ret = read(urandom_fd, buf, buf_len); if ((ret == -1) && (errno != EINTR)) { if (!block || retry_counter >= MAX_BACKOFF_RETRIES) { break; } // We have observed extremely rare events in which a |read| on a // |urandom| fd failed with |errno| != |EINTR|. We regard this as an // intermittent error that is recoverable. Therefore, backoff to allow // recovery and to avoid creating a tight spinning loop. do_backoff(&backoff); retry_counter = retry_counter + 1; } } while (ret == -1); return ret; } static ssize_t wrapper_getrandom(void *buf, size_t buf_len, int block) { ssize_t ret = -1; #if defined(USE_NR_getrandom) size_t retry_counter = 0; long backoff = INITIAL_BACKOFF_DELAY; do { ret = syscall(__NR_getrandom, buf, buf_len, block ? 0 : GRND_NONBLOCK); if ((ret == -1) && (errno != EINTR)) { // Don't block in non-block mode except if a signal handler interrupted // |getrandom|. if (!block || (retry_counter >= MAX_BACKOFF_RETRIES)) { break; } // We have observed extremely rare events in which a |read| on a // |urandom| fd failed with |errno| != |EINTR|. |getrandom| uses |urandom| // under the covers. Assuming transitivity, |getrandom| is therefore also // subject to the same rare error events. do_backoff(&backoff); retry_counter = retry_counter + 1; } } while (ret == -1); #if defined(OPENSSL_MSAN) if (ret > 0) { // MSAN doesn't recognise |syscall| and thus doesn't notice that we have // initialised the output buffer. __msan_unpoison(buf, ret); } #endif // OPENSSL_MSAN #endif // defined(USE_NR_getrandom) return ret; } // init_try_getrandom_once tests whether getrandom is supported. Returns 1 if // getrandom is supported, 0 otherwise. static int init_try_getrandom_once(void) { if (!getrandom_syscall_number_available()) { return 0; } uint8_t dummy = 0; ssize_t getrandom_ret = wrapper_getrandom(&dummy, sizeof(dummy), 0); if (getrandom_ret == 1) { // We have getrandom and entropy pool is also fully initialized. random_flavor = USE_GETRANDOM; random_flavor_state = STATE_READY; return 1; } else if (getrandom_ret == -1 && errno == EAGAIN) { // We have getrandom, but the entropy pool has not been initialized yet. random_flavor = USE_GETRANDOM; return 1; } else if (getrandom_ret == -1 && errno == ENOSYS) { // Kernel doesn't support getrandom. return 0; } else { // Other errors are fatal. perror("getrandom"); abort(); } } // init_try_urandom_once attempts to initialize /dev/urandom. Returns 1 if // successful, 0 otherwise. static int init_try_urandom_once(void) { int ret = 0; int fd = 0; do { fd = open("/dev/urandom", O_RDONLY); } while (fd == -1 && errno == EINTR); if (fd < 0) { perror("failed to open /dev/urandom"); goto out; } int flags = fcntl(fd, F_GETFD); if (flags == -1) { // Native Client doesn't implement |fcntl|. if (errno != ENOSYS) { perror("failed to get flags from urandom fd"); goto out; } } else { flags |= FD_CLOEXEC; if (fcntl(fd, F_SETFD, flags) == -1) { perror("failed to set FD_CLOEXEC on urandom fd"); goto out; } } random_flavor = USE_DEV_URANDOM; urandom_fd = fd; ret = 1; out: return ret; } // init_once initializes the state of this module to values previously // requested. Only function that mutates the chosen random function flavor. static void init_random_flavor_once(void) { // First determine if we can use getrandom. We prefer getrandom. if (init_try_getrandom_once()) { return; } // getrandom is not available. Fall-back to /dev/urandom. if (init_try_urandom_once()) { return; } // This is a fatal error and there is no way to recover. abort(); } static void ensure_getrandom_is_initialized(void) { if (random_flavor_state == STATE_READY) { // The entropy pool was already initialized in |init_try_getrandom_once|. return; } uint8_t dummy = 0; ssize_t getrandom_ret = wrapper_getrandom(&dummy, sizeof(dummy), 0); if (getrandom_ret == -1 && errno == EAGAIN) { // Attempt to get the path of the current process to aid in debugging when // something blocks. const char *current_process = ""; #if defined(OPENSSL_HAS_GETAUXVAL) const unsigned long getauxval_ret = getauxval(AT_EXECFN); if (getauxval_ret != 0) { current_process = (const char *)getauxval_ret; } #endif fprintf(stderr, "%s: getrandom indicates that the entropy pool has not been " "initialized. Rather than continue with poor entropy, this process " "will block until entropy is available.\n", current_process); getrandom_ret = wrapper_getrandom(&dummy, sizeof(dummy), 1); } if (getrandom_ret != 1) { perror("getrandom"); abort(); } random_flavor_state = STATE_READY; } static void ensure_dev_urandom_is_initialized(void) { // On platforms where urandom doesn't block at startup, we ensure that the // kernel has sufficient entropy before continuing. for (;;) { int entropy_bits = 0; if (ioctl(urandom_fd, RNDGETENTCNT, &entropy_bits)) { fprintf(stderr, "RNDGETENTCNT on /dev/urandom failed. We cannot determine if the kernel " "has enough entropy and must abort.\n"); abort(); } static const int kBitsNeeded = 256; if (entropy_bits >= kBitsNeeded) { break; } fprintf(stderr, "RNDGETENTCNT on /dev/urandom indicates that the entropy pool does not " "have enough entropy. Rather than continue with poor entropy, this " "process will block until entropy is available.\n"); struct timespec sleep_time = {.tv_sec = 0, .tv_nsec = MILLISECONDS_250 }; nanosleep(&sleep_time, &sleep_time); } random_flavor_state = STATE_READY; } // wait_for_entropy ensures the entropy pool has been initialized. If // initialization haven't happened yet, it will block the process. static void ensure_entropy_state_is_initd_once(void) { if (random_flavor == USE_GETRANDOM) { ensure_getrandom_is_initialized(); } else if (random_flavor == USE_DEV_URANDOM) { ensure_dev_urandom_is_initialized(); } else { // Indicates a state machine logical error which is fatal. abort(); } } // fill_with_entropy writes |len| bytes of entropy into |out|. It returns one // on success and zero on error. If |block| is one, this function will block // until the entropy pool is initialized. Otherwise, this function may fail, // setting |errno| to |EAGAIN| if the entropy pool has not yet been initialized. static int fill_with_entropy(uint8_t *out, size_t len, int block, int seed) { if (len == 0) { return 1; } CRYPTO_once(&initialize_random_flavor_once, init_random_flavor_once); if (random_flavor == NOT_CHOSEN) { // This should never happen. It means there is a logical error somewhere. // Hard bail. abort(); } if (block) { // "Entropy depletion" is not a thing. So, it's enough to ensure the // entropy pool is initialized once. CRYPTO_once(&ensure_random_state_is_initialized_once, ensure_entropy_state_is_initd_once); if (random_flavor_state == STATE_NOT_READY) { // This should never happen. It means there is a logical error somewhere. // Hard bail. abort(); } } // Clear |errno| so it has defined value if |read| or |getrandom| // "successfully" returns zero. errno = 0; while (len > 0) { ssize_t ret = 0; if (random_flavor == USE_GETRANDOM) { ret = wrapper_getrandom(out, len, block); } else { ret = wrapper_dev_urandom(out, len, block); } if (ret <= 0) { return 0; } out += ret; len -= ret; } return 1; } // CRYPTO_sysrand puts |requested| random bytes into |out|. void CRYPTO_sysrand(uint8_t *out, size_t requested) { if (!fill_with_entropy(out, requested, /*block=*/1, /*seed=*/0)) { perror("entropy fill failed"); abort(); } } int CRYPTO_sysrand_if_available(uint8_t *out, size_t requested) { if (fill_with_entropy(out, requested, /*block=*/0, /*seed=*/0)) { return 1; } else if (errno == EAGAIN) { OPENSSL_memset(out, 0, requested); return 0; } else { perror("opportunistic entropy fill failed"); abort(); } } #endif // OPENSSL_RAND_URANDOM