Skip to content

wp_seed_src: read /dev/urandom via raw fd to avoid seccomp SIGSYS#440

Open
ColtonWilley wants to merge 2 commits into
wolfSSL:masterfrom
ColtonWilley:seed-src-seccomp-sigsys-fix
Open

wp_seed_src: read /dev/urandom via raw fd to avoid seccomp SIGSYS#440
ColtonWilley wants to merge 2 commits into
wolfSSL:masterfrom
ColtonWilley:seed-src-seccomp-sigsys-fix

Conversation

@ColtonWilley

Copy link
Copy Markdown
Contributor

Problem

SEED-SRC read /dev/urandom through a buffered stdio FILE* (XFOPEN). That breaks OpenSSH's privsep preauth child:

  1. sshd primes its RNG (seed_rng -> RAND_status) before the privsep fork, so the first fread pulls a block of read-ahead into the stdio buffer.
  2. The child inherits the buffered FILE* and its fd.
  3. On child exit, glibc stdio cleanup calls lseek() to discard the unread read-ahead.
  4. lseek() is not in OpenSSH's preauth seccomp allow-list, so the child is killed with SIGSYS (audit shows sig=31 syscall=8).

Stock OpenSSL reads the device with a raw fd and never hits this; the buffered FILE* was the defect. Separately, re-initializing SEED-SRC (a second provider load) orphaned the previous buffered handle.

Fix

  • Read /dev/urandom with a raw fd (open/read/close, O_CLOEXEC). No stdio buffer, so no read-ahead and no exit-time lseek.
  • Share the fd and the wolfSSL seed callback across provider contexts, reference counted. The last unload closes the fd and restores the seed callback to wc_GenerateSeed — not NULL, which would make a later wc_InitRng fail DRBG_NO_SEED_CB under FIPS.
  • SEED-SRC now depends on wolfSSL file support; NO_WOLFSSL_DIR builds #error rather than silently falling back.

Tests

  • test_seccomp_sandbox — runs under the verbatim OpenSSH 9.9p1 preauth filter: single-context (the original crash), re-init, and multi-context survivor. Compiled in with -DWP_TEST_SECCOMP_SANDBOX (enabled by the seed-src CI job; a skip stub otherwise, since it needs PR_SET_SECCOMP).
  • test_seed_src_refcount — multi-context refcount; the first unload must not close the shared fd.
  • test_seed_src_reload — drives the refcount to zero (fd close + seed-callback restore) and reloads; re-exec'd so the count actually reaches zero, which the main suite's own reference otherwise prevents.

SEED-SRC opened /dev/urandom with XFOPEN (buffered fopen). The first
fread pulls read-ahead into the stdio buffer. sshd primes its RNG before
the privsep fork, so the preauth child inherits that buffered FILE*; at
child exit glibc stdio cleanup calls lseek() to drop the unread
read-ahead. lseek() isn't in OpenSSH's preauth seccomp filter, so the
child is killed with SIGSYS.

Use a raw fd (open/read/close, O_CLOEXEC) instead: no buffer, no
read-ahead, no exit-time lseek, matching OpenSSL's own device seeding.
The fd and wolfSSL seed callback are shared and reference counted across
provider contexts; the last unload closes the fd and restores the seed
callback to wc_GenerateSeed (not NULL, which fails wc_InitRng with
DRBG_NO_SEED_CB under FIPS). Also fixes a re-init leak that orphaned the
prior buffered handle.
@ColtonWilley ColtonWilley added the ci:all PR OSP toggle: run all label Jul 10, 2026

@padelsbach padelsbach left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good improvement. Couple nits

Comment thread src/wp_seed_src.c

(void)os;

if (WP_URANDOM_LOCK() != 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the lock, should probably check that the refcount >= 1 just to make sure the file is still open

Comment thread src/wp_seed_src.c
{
if (g_urandom_fd == XBADFD) {
do {
g_urandom_fd = open(URANDOM_PATH, O_RDONLY | O_CLOEXEC);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use XOPEN here for consistency?

SEED-SRC caches the /dev/urandom descriptor by number. Under OpenSSH's
inetd-mode (sshd -i) privsep setup the parent dup2's its own descriptors
over low fd numbers, reusing the number SEED-SRC cached. The forked,
seccomp-sandboxed preauth child then inherits g_urandom_fd pointing at the
wrong (non-blocking) descriptor; its reseed read returns EAGAIN, reseed_prngs
fails, and the connection is dropped - the agent-pkcs11 regress failure.

Record the device identity (dev/ino/mode/rdev) via fstat at open time and,
like OpenSSL's check_random_device(), revalidate the cached fd before each
read. If the number was reused, abandon it without closing (it belongs to
the host now) and reopen. An fstat EACCES (a sandbox that denies fstat) is
inconclusive, so the inherited fd is kept; EBADF means the fd was closed, so
reopen. The cleanup close is likewise guarded so a reused descriptor is never
closed.

Add regression test T4 (fd-reuse) to test_seccomp_sandbox.c: it dup2's a
non-blocking pipe over the cached fd, forces a parent reseed, and verifies a
sandboxed child still reseeds from a valid fd.
@ColtonWilley ColtonWilley added ci:all PR OSP toggle: run all and removed ci:all PR OSP toggle: run all labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:all PR OSP toggle: run all

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants