rng: use getrandom(2) for PRNG seeding instead of /dev/urandom fd

The internal PRNGs are now seeded via getrandom(2) rather than through a
long-lived file descriptor to /dev/urandom.

Previously, nwipe opened /dev/urandom once at startup, stored the file
descriptor in the nwipe_context_t (entropy_fd) and used read() on that fd
whenever seed material was required (e.g. DoD, Gutmann, OPS-II and generic
random passes). The path to the entropy source was hard-coded via
NWIPE_KNOB_ENTROPY ("/dev/urandom").

This change introduces a small helper in method.c:

  - nwipe_read_entropy(buf, len)

which calls the getrandom(2) syscall in a loop until the requested number
of bytes has been filled, handling short reads and EINTR/EAGAIN. All
former uses of read(ctx->entropy_fd, ...) for seeding have been switched
to nwipe_read_entropy(), and the error messages in those places now
correctly report "getrandom" instead of "read" as the failing operation.

The nwipe_context_t no longer carries an entropy_fd and nwipe.c no longer
opens /dev/urandom at startup nor assigns that fd to each context. The
NWIPE_KNOB_ENTROPY macro and its only use site were removed. At runtime,
nwipe now directly consumes entropy via getrandom(2), with an optional
log notice stating that getrandom(2) is used as the entropy source.

Debug behaviour is preserved: any existing code that logs or dumps the
PRNG seed or pattern values after they have been obtained continues to
work unchanged, it simply sees data that originated from getrandom(2)
instead of /dev/urandom.

Rationale for preferring getrandom(2) over /dev/urandom via fd:

  - getrandom(2) is a dedicated kernel API for CSPRNG output; it does not
    depend on any device node or path existing in /dev. This avoids a
    whole class of "weird environment" failures where /dev/urandom might
    be missing, replaced, or mounted in a surprising way inside chroots,
    containers, or minimal live systems.

  - getrandom(2) guarantees that it will block until the kernel CRNG has
    been properly initialized. Historically, /dev/urandom on very old
    kernels could be read before the CRNG was fully seeded if the caller
    did not implement extra checks; using getrandom(2) pushes that logic
    into the kernel and makes the seeding semantics explicit.

  - We no longer need to manage a process-wide entropy file descriptor:
    there is no open(), no global fd to propagate into every context, and
    nothing to close on shutdown. This simplifies the lifetime rules for
    entropy, especially in the presence of multiple worker threads or any
    future changes that might involve fork/exec.

  - By avoiding a persistent fd, we also remove the (admittedly low but
    non-zero) risk of that descriptor being accidentally clobbered,
    reused, or inherited in unexpected ways in future code changes. Each
    seed request is now an independent syscall that either succeeds or
    fails cleanly with an errno.

  - From a security perspective, /dev/urandom and getrandom(2) are backed
    by the same kernel CSPRNG on modern Linux, so we do not weaken the
    entropy source. Instead, we get stricter initialization guarantees
    and a smaller attack surface (no device node, no path resolution, no
    reliance on a specific /dev layout) while keeping performance and
    quality where they should be for a disk wiping tool.

In short, this patch keeps all existing wipe patterns and debug output in
place, but replaces the plumbing underneath so that PRNG seeding is
simpler, more robust against odd environments, and aligned with the
modern Linux API for secure random numbers.
This commit is contained in:
Fabian Druschke
2025-11-14 14:35:53 +01:00
parent f83f229a6a
commit 04b79bc746
4 changed files with 54 additions and 31 deletions

View File

@@ -128,7 +128,6 @@ typedef struct nwipe_context_t_
int device_target; // The device target.
u64 eta; // The estimated number of seconds until method completion.
int entropy_fd; // The entropy source. Usually /dev/urandom.
int pass_count; // The number of passes performed by the working wipe method.
u64 pass_done; // The number of bytes that have already been i/o'd in this pass.
u64 pass_errors; // The number of errors across all passes.

View File

@@ -47,6 +47,52 @@
#include "options.h"
#include "pass.h"
#include "logging.h"
#include <errno.h>
#include <unistd.h>
#include <sys/syscall.h> /* SYS_getrandom */
#if defined( __linux__ )
/* On glibc/musl with <sys/random.h> available, it's fine (optional). */
/* #include <sys/random.h> */
#endif
/**
* @brief Fill a buffer with cryptographically secure random bytes using getrandom(2).
*
* This wrapper blocks until the kernel CRNG is initialized, then loops until
* @p len bytes are written (handling short reads and EINTR/EAGAIN).
*
* @param[out] buf Destination buffer.
* @param[in] len Number of bytes to generate.
* @return On success, returns (ssize_t)len.
* On error, returns -errno and leaves errno set.
*/
static ssize_t nwipe_read_entropy( void* buf, size_t len )
{
unsigned char* p = (unsigned char*) buf;
size_t n = len;
while( n > 0 )
{
/* Prefer the raw syscall to avoid libc version pitfalls. */
ssize_t r = syscall( SYS_getrandom, p, n, 0 /* blocking */ );
if( r < 0 )
{
if( errno == EINTR || errno == EAGAIN )
{
continue; /* retry */
}
return -errno;
}
if( r == 0 )
{
/* Extremely unlikely: treat as transient and retry. */
continue;
}
p += r;
n -= (size_t) r;
}
return (ssize_t) len;
}
/*
* Comment Legend
@@ -292,7 +338,7 @@ void* nwipe_dod522022m( void* ptr )
{ 0, NULL } };
/* Load the array with random characters. */
r = read( c->entropy_fd, &dod, sizeof( dod ) );
r = nwipe_read_entropy( &dod, sizeof( dod ) );
/* NOTE: Only the random data in dod[0], dod[3], and dod[4] is actually used. */
@@ -363,7 +409,7 @@ void* nwipe_dodshort( void* ptr )
{ 0, NULL } };
/* Load the array with random characters. */
r = read( c->entropy_fd, &dod, sizeof( dod ) );
r = nwipe_read_entropy( &dod, sizeof( dod ) );
/* NOTE: Only the random data in dod[0] is actually used. */
@@ -463,7 +509,7 @@ void* nwipe_gutmann( void* ptr )
u16 s[27];
/* Load the array with random characters. */
ssize_t r = read( c->entropy_fd, &s, sizeof( s ) );
ssize_t r = nwipe_read_entropy( &s, sizeof( s ) );
if( r != sizeof( s ) )
{
r = errno;
@@ -621,7 +667,7 @@ void* nwipe_ops2( void* ptr )
}
/* Load the array of random characters. */
r = read( c->entropy_fd, s, u );
r = nwipe_read_entropy( s, u );
if( r != u )
{
@@ -1001,13 +1047,13 @@ int nwipe_runmethod( nwipe_context_t* c, nwipe_pattern_t* patterns )
c->pass_type = NWIPE_PASS_WRITE;
/* Seed the PRNG. */
r = read( c->entropy_fd, c->prng_seed.s, c->prng_seed.length );
r = nwipe_read_entropy( c->prng_seed.s, c->prng_seed.length );
/* Check the result. */
if( r < 0 )
{
c->pass_type = NWIPE_PASS_NONE;
nwipe_perror( errno, __FUNCTION__, "read" );
nwipe_perror( errno, __FUNCTION__, "getrandom" );
nwipe_log( NWIPE_LOG_FATAL, "Unable to seed the PRNG." );
return -1;
}
@@ -1104,12 +1150,12 @@ int nwipe_runmethod( nwipe_context_t* c, nwipe_pattern_t* patterns )
c->pass_type = NWIPE_PASS_FINAL_OPS2;
/* Seed the PRNG. */
r = read( c->entropy_fd, c->prng_seed.s, c->prng_seed.length );
r = nwipe_read_entropy( c->prng_seed.s, c->prng_seed.length );
/* Check the result. */
if( r < 0 )
{
nwipe_perror( errno, __FUNCTION__, "read" );
nwipe_perror( errno, __FUNCTION__, "getrandom" );
nwipe_log( NWIPE_LOG_FATAL, "Unable to seed the PRNG." );
return -1;
}

View File

@@ -97,9 +97,6 @@ int main( int argc, char** argv )
char module_shortform[50];
char final_cmd_modprobe[sizeof( modprobe_command ) + sizeof( module_shortform )];
/* The entropy source file handle. */
int nwipe_entropy;
/* The generic index variables. */
int i;
int j;
@@ -256,21 +253,6 @@ int main( int argc, char** argv )
exit( 1 );
}
/* Open the entropy source. */
nwipe_entropy = open( NWIPE_KNOB_ENTROPY, O_RDONLY );
/* Check the result. */
if( nwipe_entropy < 0 )
{
nwipe_perror( errno, __FUNCTION__, "open" );
nwipe_log( NWIPE_LOG_FATAL, "Unable to open entropy source %s.", NWIPE_KNOB_ENTROPY );
cleanup();
free( c2 );
return errno;
}
nwipe_log( NWIPE_LOG_NOTICE, "Opened entropy source '%s'.", NWIPE_KNOB_ENTROPY );
/* Block relevant signals in main thread. Any other threads that are */
/* created after this will also block those signals. */
sigset_t sigset;
@@ -359,9 +341,6 @@ int main( int argc, char** argv )
for( i = 0; i < nwipe_enumerated; i++ )
{
/* Set the entropy source. */
c1[i]->entropy_fd = nwipe_entropy;
if( nwipe_options.autonuke )
{
/* When the autonuke option is set, select all disks. */

View File

@@ -24,7 +24,6 @@
#define OPTIONS_H_
/* Program knobs. */
#define NWIPE_KNOB_ENTROPY "/dev/urandom"
#define NWIPE_KNOB_IDENTITY_SIZE 512
#define NWIPE_KNOB_LABEL_SIZE 128
#define NWIPE_KNOB_LOADAVG "/proc/loadavg"