From e03c6a23db96a988632b37c9399d3f09b5a967d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Tro=C3=9Fbach?= Date: Fri, 11 Sep 2026 15:06:39 +0200 Subject: [PATCH] Harden the Linux entropy source On Linux, entropy() called getrandom() once and fell back to /dev/urandom whenever that call did not return all requested bytes, including when a signal interrupted it (EINTR) while it was still waiting for the kernel's random pool to be initialized. /dev/urandom does not wait for that initialization, so the fallback could read from a pool that is not yet seeded. - Call getrandom() in a loop that retries on EINTR and continues after short reads. - Before reading /dev/urandom, wait until the pool is initialized by polling /dev/random for readability, as libsodium does. - Apply the existing all-zero check to the getrandom() and CCRandomGenerateBytes() results as well. - Remove the SYS_getentropy branch; Linux has no such system call. --- src/chacha20poly1305.c | 81 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 11 deletions(-) diff --git a/src/chacha20poly1305.c b/src/chacha20poly1305.c index c5a75e9a..8ad6ceeb 100644 --- a/src/chacha20poly1305.c +++ b/src/chacha20poly1305.c @@ -464,13 +464,53 @@ static size_t entropy(void* buf, size_t n) #include #ifdef __linux__ +#include #include /* musl does not have so let's define RNDGETENTCNT here */ #ifndef RNDGETENTCNT #define RNDGETENTCNT _IOR('R', 0x00, int) #endif + +/* Waits until the kernel's random pool is initialized, as libsodium does */ +static int wait_for_random_pool(void) +{ + struct pollfd pfd; + int fd, ret; + + do + { + fd = open("/dev/random", O_RDONLY, 0); + } + while (fd == -1 && errno == EINTR); + if (fd == -1) + return 0; /* no /dev/random: don't block, like libsodium */ + + /* /dev/random becomes readable once the pool is initialized */ + pfd.fd = fd; + pfd.events = POLLIN; + pfd.revents = 0; + do + { + ret = poll(&pfd, 1, -1); + } + while (ret == -1 && (errno == EINTR || errno == EAGAIN)); + close(fd); + return (ret == 1) ? 0 : -1; +} #endif +/* Returns 1 if all n bytes are zero */ +static int is_all_zero(const void* buf, size_t n) +{ + size_t i; + for (i = 0; i < n; i++) + { + if (((const uint8_t*) buf)[i] != 0) + return 0; + } + return 1; +} + /* Returns the number of urandom bytes read (either 0 or n) */ static size_t read_urandom(void* buf, size_t n) { @@ -480,6 +520,12 @@ static size_t read_urandom(void* buf, size_t n) struct stat st; int errnold = errno; +#ifdef __linux__ + /* Unlike getrandom(), /dev/urandom does not wait for the pool */ + if (wait_for_random_pool() != 0) + goto fail; +#endif + do { fd = open("/dev/urandom", O_RDONLY, 0); @@ -515,13 +561,10 @@ static size_t read_urandom(void* buf, size_t n) close(fd); /* Verify that the random device returned non-zero data */ - for (i = 0; i < n; i++) + if (!is_all_zero(buf, n)) { - if (((uint8_t*) buf)[i] != 0) - { - errno = errnold; - return n; - } + errno = errnold; + return n; } /* Tiny n may unintentionally fall through! */ @@ -540,16 +583,32 @@ static size_t read_urandom(void* buf, size_t n) #endif #endif +#if defined(__linux__) && defined(SYS_getrandom) +/* Returns the number of getrandom() bytes read (either 0 or n) */ +static size_t read_getrandom(void* buf, size_t n) +{ + size_t i = 0; + int errnold = errno; + while (i < n) + { + long ret = syscall(SYS_getrandom, (char*) buf + i, n - i, 0); + if (ret > 0) + i += (size_t) ret; /* short read: continue */ + else if (ret != -1 || errno != EINTR) + break; /* interrupted: retry; any other error: give up */ + } + errno = errnold; + return (i == n) ? n : 0; +} +#endif + static size_t entropy(void* buf, size_t n) { #if defined(__APPLE__) && defined(HAVE_COMMONCRYPTO_COMMONRANDOM_H) - if (CCRandomGenerateBytes(buf, n) == kCCSuccess) + if (CCRandomGenerateBytes(buf, n) == kCCSuccess && !is_all_zero(buf, n)) return n; #elif defined(__linux__) && defined(SYS_getrandom) - if (syscall(SYS_getrandom, buf, n, 0) == n) - return n; -#elif defined(__linux__) && defined(SYS_getentropy) - if (syscall(SYS_getentropy, buf, n) == 0) + if (read_getrandom(buf, n) == n && !is_all_zero(buf, n)) return n; #endif return read_urandom(buf, n);