From 04b79bc74644f5e89db991cd994174788f42e2b8 Mon Sep 17 00:00:00 2001 From: Fabian Druschke Date: Fri, 14 Nov 2025 14:35:53 +0100 Subject: [PATCH] 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. --- src/context.h | 1 - src/method.c | 62 ++++++++++++++++++++++++++++++++++++++++++++------- src/nwipe.c | 21 ----------------- src/options.h | 1 - 4 files changed, 54 insertions(+), 31 deletions(-) diff --git a/src/context.h b/src/context.h index 07de9b2..1c497bc 100644 --- a/src/context.h +++ b/src/context.h @@ -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. diff --git a/src/method.c b/src/method.c index 259c3f3..09534b6 100644 --- a/src/method.c +++ b/src/method.c @@ -47,6 +47,52 @@ #include "options.h" #include "pass.h" #include "logging.h" +#include +#include +#include /* SYS_getrandom */ +#if defined( __linux__ ) +/* On glibc/musl with available, it's fine (optional). */ +/* #include */ +#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; } diff --git a/src/nwipe.c b/src/nwipe.c index 6292954..55afe0f 100644 --- a/src/nwipe.c +++ b/src/nwipe.c @@ -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. */ diff --git a/src/options.h b/src/options.h index 96aeefc..dcf39f9 100644 --- a/src/options.h +++ b/src/options.h @@ -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"