Skip to content

Harden the Linux entropy source - #267

Merged
utelle merged 1 commit into
utelle:mainfrom
SchwarzDigits:fix/linux-entropy-source
Sep 13, 2026
Merged

utelle merged 1 commit into
utelle:mainfrom
SchwarzDigits:fix/linux-entropy-source

Conversation

@mtrossbach

Copy link
Copy Markdown
Contributor

Hi Ulrich,

this is one of the follow-ups I mentioned in #262. It changes entropy() in src/chacha20poly1305.c, which you reworked recently, so please tell me if you'd prefer a different approach.

Type: Bug fix

Background

We plan to use SQLite3 Multiple Ciphers in projects that may have to pass a security audit, for example in critical infrastructure or for public authorities. Whether or not that happens, we'd like the random number generation to be done right, because salts, IVs and nonces are only as good as the random source behind them. For Linux, the BSI technical guideline BSI TR-02102-1 (section 8.5.1, remark 8.2) specifically warns that /dev/urandom can return data before the kernel's random pool is initialized.

The problem

On Linux, entropy() calls getrandom() once. If that call doesn't return all requested bytes, it falls back to /dev/urandom. This also happens

  • when a signal interrupts getrandom() (EINTR) while it is still waiting for the random pool, which can happen shortly after boot, or
  • when getrandom() returns fewer bytes than requested.

In exactly these cases, /dev/urandom may be read from a pool that isn't seeded yet. The RNDGETENTCNT check in read_urandom() confirms that the device is a real random device, but not that the pool is initialized.

The changes (only src/chacha20poly1305.c)

The changes follow what established crypto libraries do:

  • Retry instead of falling back: getrandom() is called in a loop (read_getrandom()) that retries after EINTR and continues after short reads, like libsodium and BoringSSL.
  • Checked fallback: /dev/urandom is only used if getrandom() fails for another reason, for example ENOSYS on kernels older than 3.17 or EPERM under a seccomp filter. Before reading from it, read_urandom() waits until /dev/random is readable, which means that the pool is initialized. libsodium and the Rust getrandom crate do the same, and OpenSSL does it on older kernels. If /dev/random can't be opened, it reads /dev/urandom as before, like libsodium.
  • Same all-zero check for all sources: the check that read_urandom() already does now also applies to the results of getrandom() and CCRandomGenerateBytes() (new helper is_all_zero()).
  • Dead code removed: the SYS_getentropy branch. Linux has no such system call, so it was never compiled.

In the normal case, nothing changes: it's still a single getrandom() call. Apple, Windows, WebAssembly and the other Unix systems keep their random sources, and the file format is not affected.

How serious is it? Low in practice. It only matters shortly after boot, mainly on embedded devices and virtual machines, and only if a signal interrupts the waiting call or getrandom() is unavailable. But it's exactly the kind of detail an audit of the random number generation looks at. With this change, entropy() handles these cases the way libsodium and OpenSSL do.

How I tested it

Failure injection: a small test program includes src/chacha20poly1305.c and intercepts getrandom(), open() and poll(). It injects failures and records the order in which /dev/random and /dev/urandom are used. The results are the same on Linux aarch64 and x86_64 (glibc, GCC 13 and Clang 18), Alpine Linux (musl) and Android (NDK r28, bionic):

getrandom() behaviour Before After
works normally getrandom() only getrandom() only
interrupted three times by EINTR reads /dev/urandom without waiting retried (4 calls), no fallback
returns at most 5 bytes per call reads /dev/urandom without waiting continued (7 calls), no fallback
fails with ENOSYS or EPERM reads /dev/urandom without waiting waits for /dev/random, then reads /dev/urandom
fails with ENOSYS, and /dev/random can't be opened reads /dev/urandom reads /dev/urandom, as before
returns only zero bytes uses the zeros falls back to the checked path

Other checks:

  • The CI recipe (autoreconf, ./configure, make, the SQL tests and cryptotest) passes on Linux aarch64 and x86_64 with GCC and Clang.
  • macOS and WebAssembly: cryptotest passes. macOS uses the same random source as before.
  • Windows: a MinGW-w64 cross-compile of the amalgamation gives the same result as before.
  • No new compiler warnings. The -Wsign-compare warning of the old getrandom() line is gone.
  • The change also applies cleanly to hwaccel.

Best regards,
Markus

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.
@utelle
utelle merged commit bdc8d8f into utelle:main Sep 13, 2026
13 checks passed
utelle added a commit that referenced this pull request Sep 13, 2026
@utelle

utelle commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Hi Markus,

this is one of the follow-ups I mentioned in #262. It changes entropy() in src/chacha20poly1305.c, which you reworked recently, so please tell me if you'd prefer a different approach.

I'm fine with the changes introduced in this PR. And therefore I merged it, after having inspected the changes in detail.

Background

We plan to use SQLite3 Multiple Ciphers in projects that may have to pass a security audit, for example in critical infrastructure or for public authorities. Whether or not that happens, we'd like the random number generation to be done right, because salts, IVs and nonces are only as good as the random source behind them. For Linux, the BSI technical guideline BSI TR-02102-1 (section 8.5.1, remark 8.2) specifically warns that /dev/urandom can return data before the kernel's random pool is initialized.

For the typical use case of SQLite3MC the latter is unlikely to impose a real problem. However, several years ago we struggled with the random number generation on a virtual machine for similar reasons. So, it is really good to improve the code.

The changes (only src/chacha20poly1305.c)

I'm considering moving the code for generating random data into a separate source file. That should make future maintenance easier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants