From fea3d8c30395511d344d0b1479257a67c03eba6c Mon Sep 17 00:00:00 2001 From: Fabian Druschke Date: Fri, 19 Dec 2025 00:59:45 +0100 Subject: [PATCH] prng, gui: add PRNG benchmarking, auto-selection and CLI support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change introduces a comprehensive PRNG benchmarking and auto-selection framework, available both in the ncurses GUI and via the command line. A new interactive “PRNG Benchmark” menu (key: 'b') has been added to the GUI. It benchmarks all available PRNG engines using a RAM-only workload and displays a sorted leaderboard showing sustained throughput in MB/s. In addition, a new automatic PRNG selection mode is implemented: --prng=auto When enabled, nwipe benchmarks all available PRNGs at startup and automatically selects the fastest PRNG for the current hardware. This selection happens before device scanning and does not alter any wipe semantics or security guarantees. For non-GUI and diagnostic use, a standalone benchmark mode is also added: --prng-benchmark This runs the same RAM-only PRNG benchmark, prints a sorted leaderboard to stdout and the log, and exits without scanning or wiping any devices. It is fully compatible with --nogui. Key features: - Unified PRNG benchmark core shared by GUI, auto-selection and CLI modes - RAM-only benchmark using aligned multi-megabyte buffers - Deterministic in-memory seeding (benchmark-only, not used for wiping) - Sorted leaderboard showing MB/s per PRNG - Automatic fastest-PRNG selection via --prng=auto (opt-in) - Interactive GUI benchmark view with dimmed informational styling - No impact on wipe logic, data security or default behaviour This change improves transparency, diagnostics and performance tuning across a wide range of hardware platforms, while keeping all new behaviour explicitly opt-in. --- src/gui.c | 9 --- src/nwipe.c | 119 ++++++++++++++++++++++++++++++++++++++ src/options.c | 39 +++++++++++++ src/options.h | 3 + src/prng.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/prng.h | 19 ++++++ 6 files changed, 336 insertions(+), 9 deletions(-) diff --git a/src/gui.c b/src/gui.c index 9dcc3ab..f12d6ee 100644 --- a/src/gui.c +++ b/src/gui.c @@ -209,15 +209,6 @@ static void nwipe_gui_draw_acs_prefix( WINDOW* w, int y, int x ) mvwaddch( w, y, x + 3, ' ' ); } -typedef struct -{ - const nwipe_prng_t* prng; - double mbps; - double seconds; - unsigned long long bytes; - int rc; -} nwipe_prng_bench_result_t; - static double nwipe_gui_monotonic_seconds( void ) { struct timespec ts; diff --git a/src/nwipe.c b/src/nwipe.c index 0e70cb2..8aed267 100644 --- a/src/nwipe.c +++ b/src/nwipe.c @@ -96,6 +96,25 @@ int devnamecmp( const void* a, const void* b ) return ( ret ); } +static int nwipe_prng_bench_cmp_desc( const void* a, const void* b ) +{ + const nwipe_prng_bench_result_t* A = (const nwipe_prng_bench_result_t*) a; + const nwipe_prng_bench_result_t* B = (const nwipe_prng_bench_result_t*) b; + + /* successful results first */ + if( A->rc != 0 && B->rc == 0 ) + return 1; + if( A->rc == 0 && B->rc != 0 ) + return -1; + + /* then sort by MB/s */ + if( A->mbps < B->mbps ) + return 1; + if( A->mbps > B->mbps ) + return -1; + return 0; +} + #define NWIPE_PDF_DIR_MODE ( S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH ) /* -> 0755: rwx for owner, r-x for group and others */ @@ -339,6 +358,106 @@ int main( int argc, char** argv ) /* Log OS info */ nwipe_log_OSinfo(); + /* ------------------------------------------------------------ + * PRNG benchmark / auto-select (runs before device scan) + * ------------------------------------------------------------ */ + if( nwipe_options.prng_benchmark_only || nwipe_options.prng_auto ) + { + /* tune defaults */ + const size_t io_block = 4 * 1024 * 1024; /* 4 MiB RAM buffer blocks */ + double seconds = nwipe_options.prng_bench_seconds; + + /* If user requested auto-select and didn't override seconds, keep it short */ + if( nwipe_options.prng_auto ) + { + if( seconds <= 0.0 ) + seconds = 0.25; + /* Optional: if you consider "1.0" to be the default and want auto shorter: + * if( seconds == 1.0 ) seconds = 0.25; + */ + } + else + { + if( seconds <= 0.0 ) + seconds = 1.0; + } + + nwipe_prng_bench_result_t results[16]; + memset( results, 0, sizeof( results ) ); + + if( nwipe_options.prng_benchmark_only ) + { + int n = nwipe_prng_benchmark_all( + seconds, io_block, results, (int) ( sizeof( results ) / sizeof( results[0] ) ) ); + if( n <= 0 ) + { + nwipe_log( NWIPE_LOG_ERROR, "PRNG benchmark failed (no results)." ); + printf( "PRNG benchmark failed (no results).\n" ); + cleanup(); + exit( 3 ); + } + + qsort( results, (size_t) n, sizeof( results[0] ), nwipe_prng_bench_cmp_desc ); + + /* Print to console + log */ + printf( "\nPRNG Benchmark (RAM-only) ~%.2fs each, block=%zu MiB\n", seconds, io_block / ( 1024 * 1024 ) ); + printf( "---------------------------------------------------\n" ); + + nwipe_log( NWIPE_LOG_INFO, + "PRNG Benchmark (RAM-only) ~%.2fs each, block=%zu MiB", + seconds, + io_block / ( 1024 * 1024 ) ); + + for( int i = 0; i < n; i++ ) + { + if( results[i].rc == 0 ) + { + printf( "%2d) %-40s %10.1f MB/s\n", i + 1, results[i].prng->label, results[i].mbps ); + nwipe_log( NWIPE_LOG_INFO, + "PRNG bench %2d) %-40s %10.1f MB/s", + i + 1, + results[i].prng->label, + results[i].mbps ); + } + else + { + printf( "%2d) %-40s (failed: rc=%d)\n", i + 1, results[i].prng->label, results[i].rc ); + nwipe_log( NWIPE_LOG_WARNING, + "PRNG bench %2d) %-40s (failed: rc=%d)", + i + 1, + results[i].prng->label, + results[i].rc ); + } + } + + printf( "\n" ); + cleanup(); + exit( 0 ); + } + + /* --prng=auto path */ + if( nwipe_options.prng_auto ) + { + const nwipe_prng_t* best = nwipe_prng_select_fastest( + seconds, io_block, results, (int) ( sizeof( results ) / sizeof( results[0] ) ) ); + + if( best != NULL ) + { + /* Apply selection */ + nwipe_options.prng = (nwipe_prng_t*) best; + + nwipe_log( NWIPE_LOG_INFO, "Auto-selected fastest PRNG: %s", best->label ); + printf( "Auto-selected fastest PRNG: %s\n", best->label ); + } + else + { + nwipe_log( NWIPE_LOG_WARNING, + "Auto PRNG selection: no working PRNG found, keeping configured default." ); + printf( "Auto PRNG selection: no working PRNG found, keeping configured default.\n" ); + } + } + } + /* Check that hdparm exists, we use hdparm for some HPA/DCO detection etc, if not * exit nwipe. These checks are required if the PATH environment is not setup ! * Example: Debian sid 'su' as opposed to 'su -' diff --git a/src/options.c b/src/options.c index d29bca5..f24e1ab 100644 --- a/src/options.c +++ b/src/options.c @@ -91,6 +91,8 @@ int nwipe_options_parse( int argc, char** argv ) /* The Pseudo Random Number Generator. */ { "prng", required_argument, 0, 'p' }, + { "prng-benchmark", no_argument, 0, 0 }, + { "prng-bench-seconds", required_argument, 0, 0 }, /* The number of times to run the method. */ { "rounds", required_argument, 0, 'r' }, @@ -140,6 +142,10 @@ int nwipe_options_parse( int argc, char** argv ) nwipe_options.autonuke = 0; nwipe_options.autopoweroff = 0; nwipe_options.method = &nwipe_random; + nwipe_options.prng_auto = 0; + nwipe_options.prng_benchmark_only = 0; + nwipe_options.prng_bench_seconds = 1.0; /* default for interactive / manual */ + /* * Determines and sets the default PRNG based on AES-NI support and system architecture. * It selects AES-CTR PRNG if AES-NI is supported, xoroshiro256 for 64-bit systems without AES-NI, @@ -425,6 +431,21 @@ int nwipe_options_parse( int argc, char** argv ) break; } + if( strcmp( nwipe_options_long[i].name, "prng-benchmark" ) == 0 ) + { + nwipe_options.prng_benchmark_only = 1; + break; + } + if( strcmp( nwipe_options_long[i].name, "prng-bench-seconds" ) == 0 ) + { + nwipe_options.prng_bench_seconds = atof( optarg ); + if( nwipe_options.prng_bench_seconds < 0.05 ) + nwipe_options.prng_bench_seconds = 0.05; + if( nwipe_options.prng_bench_seconds > 10.0 ) + nwipe_options.prng_bench_seconds = 10.0; + break; + } + /* getopt_long should raise on invalid option, so we should never get here. */ exit( EINVAL ); @@ -590,6 +611,12 @@ int nwipe_options_parse( int argc, char** argv ) case 'p': /* PRNG option. */ + if( strcmp( optarg, "auto" ) == 0 ) + { + nwipe_options.prng_auto = 1; + /* keep current default as fallback until autoselect runs */ + break; + } if( strcmp( optarg, "mersenne" ) == 0 || strcmp( optarg, "twister" ) == 0 ) { nwipe_options.prng = &nwipe_twister; @@ -850,6 +877,18 @@ void display_help() puts( " If set to \"noPDF\" no PDF reports are written.\n" ); puts( " -p, --prng=METHOD PRNG option " "(mersenne|twister|isaac|isaac64|add_lagg_fibonacci_prng|xoroshiro256_prng|aes_ctr_prng)\n" ); + puts( " --prng=auto" ); + puts( " Automatically benchmark all available PRNGs at startup and" ); + puts( " select the fastest one for the current hardware." ); + puts( "" ); + puts( " --prng-benchmark" ); + puts( " Run a RAM-only PRNG throughput benchmark and exit." ); + puts( " Prints a sorted leaderboard (MB/s). No wipe is performed." ); + puts( "" ); + puts( " --prng-bench-seconds=N" ); + puts( " Seconds per PRNG during benchmarking (default: 1.0)." ); + puts( " For --prng=auto this is automatically reduced unless set." ); + puts( " -q, --quiet Anonymize logs and the GUI by removing unique data, i.e." ); puts( " serial numbers, LU WWN Device ID, and SMBIOS/DMI data." ); puts( " XXXXXX = S/N exists, ????? = S/N not obtainable\n" ); diff --git a/src/options.h b/src/options.h index dbec04f..eaa8a62 100644 --- a/src/options.h +++ b/src/options.h @@ -63,6 +63,9 @@ typedef struct int nowait; // Do not wait for a final key before exiting. int nosignals; // Do not allow signals to interrupt a wipe. int nogui; // Do not show the GUI. + int prng_auto; /* 1 = auto-select fastest PRNG at startup */ + int prng_benchmark_only; /* 1 = run PRNG benchmark and exit (nogui-friendly) */ + double prng_bench_seconds; /* seconds per PRNG (default e.g. 0.25 for auto, 1.0 for manual) */ char* banner; // The product banner shown on the top line of the screen. void* method; // A function pointer to the wipe method that will be used. char logfile[FILENAME_MAX]; // The filename to log the output to. diff --git a/src/prng.c b/src/prng.c index 5debcc9..e54d7de 100644 --- a/src/prng.c +++ b/src/prng.c @@ -44,6 +44,15 @@ nwipe_prng_t nwipe_xoroshiro256_prng = { "XORoshiro-256", nwipe_xoroshiro256_prn /* AES-CTR-NI PRNG Structure */ nwipe_prng_t nwipe_aes_ctr_prng = { "AES-CTR (Kernel)", nwipe_aes_ctr_prng_init, nwipe_aes_ctr_prng_read }; +static const nwipe_prng_t* all_prngs[] = { + &nwipe_twister, + &nwipe_isaac, + &nwipe_isaac64, + &nwipe_add_lagg_fibonacci_prng, + &nwipe_xoroshiro256_prng, + &nwipe_aes_ctr_prng, +}; + /* Print given number of bytes from unsigned integer number to a byte stream buffer starting with low-endian. */ static inline void u32_to_buffer( u8* restrict buffer, u32 val, const int len ) { @@ -681,3 +690,150 @@ int nwipe_aes_ctr_prng_read( NWIPE_PRNG_READ_SIGNATURE ) } return 0; } + +/* ---------------------------------------------------------------------- + * PRNG benchmark / auto-selection core + * ---------------------------------------------------------------------- */ + +static double nwipe_prng_monotonic_seconds( void ) +{ + struct timespec ts; + clock_gettime( CLOCK_MONOTONIC, &ts ); + return (double) ts.tv_sec + (double) ts.tv_nsec / 1000000000.0; +} + +/* einfacher LCG zum Seed-Befüllen – nur für Benchmark, kein Kryptokram */ +static void nwipe_prng_make_seed( unsigned char* seed, size_t len ) +{ + unsigned long t = (unsigned long) time( NULL ); + unsigned long x = ( t ^ 0xA5A5A5A5UL ) + (unsigned long) (uintptr_t) seed; + + for( size_t i = 0; i < len; i++ ) + { + x = x * 1664525UL + 1013904223UL; + seed[i] = (unsigned char) ( ( x >> 16 ) & 0xFF ); + } +} + +static void* nwipe_prng_alloc_aligned( size_t alignment, size_t size ) +{ + void* p = NULL; + if( posix_memalign( &p, alignment, size ) != 0 ) + return NULL; + return p; +} + +static void nwipe_prng_bench_one( const nwipe_prng_t* prng, + nwipe_prng_bench_result_t* out, + void* io_buf, + size_t io_block, + double seconds_per_prng ) +{ + void* state = NULL; + + unsigned char seedbuf[4096]; + nwipe_entropy_t seed; + seed.s = (u8*) seedbuf; + seed.length = sizeof( seedbuf ); + nwipe_prng_make_seed( seedbuf, sizeof( seedbuf ) ); + + out->prng = prng; + out->mbps = 0.0; + out->seconds = 0.0; + out->bytes = 0; + out->rc = 0; + + int rc = prng->init( &state, &seed ); + if( rc != 0 ) + { + out->rc = rc; + if( state ) + free( state ); + return; + } + + const double t0 = nwipe_prng_monotonic_seconds(); + double now = t0; + + while( ( now - t0 ) < seconds_per_prng ) + { + rc = prng->read( &state, io_buf, io_block ); + if( rc != 0 ) + { + out->rc = rc; + break; + } + + out->bytes += (unsigned long long) io_block; + now = nwipe_prng_monotonic_seconds(); + } + + out->seconds = now - t0; + if( out->rc == 0 && out->seconds > 0.0 ) + { + out->mbps = ( (double) out->bytes / ( 1024.0 * 1024.0 ) ) / out->seconds; + } + + if( state ) + free( state ); +} + +int nwipe_prng_benchmark_all( double seconds_per_prng, + size_t io_block_bytes, + nwipe_prng_bench_result_t* results, + size_t results_count ) +{ + if( results == NULL || results_count == 0 ) + return 0; + + /* Anzahl PRNGs begrenzen auf results_count */ + size_t max = sizeof( all_prngs ) / sizeof( all_prngs[0] ); + if( results_count < max ) + max = results_count; + + void* io_buf = nwipe_prng_alloc_aligned( 4096, io_block_bytes ); + if( !io_buf ) + { + nwipe_log( NWIPE_LOG_ERROR, "PRNG benchmark: unable to allocate %zu bytes buffer", io_block_bytes ); + return -1; + } + + for( size_t i = 0; i < max; i++ ) + { + nwipe_prng_bench_result_t* r = &results[i]; + memset( r, 0, sizeof( *r ) ); + nwipe_prng_bench_one( all_prngs[i], r, io_buf, io_block_bytes, seconds_per_prng ); + } + + free( io_buf ); + return (int) max; +} + +const nwipe_prng_t* nwipe_prng_select_fastest( double seconds_per_prng, + size_t io_block_bytes, + nwipe_prng_bench_result_t* results, + size_t results_count ) +{ + int n = nwipe_prng_benchmark_all( seconds_per_prng, io_block_bytes, results, results_count ); + if( n <= 0 ) + return NULL; + + const nwipe_prng_t* best = NULL; + double best_mbps = 0.0; + + for( int i = 0; i < n; i++ ) + { + if( results[i].rc == 0 && results[i].mbps > best_mbps ) + { + best_mbps = results[i].mbps; + best = results[i].prng; + } + } + + if( best == NULL ) + { + nwipe_log( NWIPE_LOG_WARNING, "Auto PRNG selection: no successful PRNG benchmark" ); + } + + return best; +} diff --git a/src/prng.h b/src/prng.h index 30e9047..0e244ef 100644 --- a/src/prng.h +++ b/src/prng.h @@ -45,6 +45,25 @@ typedef struct nwipe_prng_read_t read; // Read data from the prng. } nwipe_prng_t; +typedef struct +{ + const nwipe_prng_t* prng; + double mbps; + double seconds; + unsigned long long bytes; + int rc; +} nwipe_prng_bench_result_t; + +int nwipe_prng_benchmark_all( double seconds_per_prng, + size_t io_block_bytes, + nwipe_prng_bench_result_t* results, + size_t results_count ); + +const nwipe_prng_t* nwipe_prng_select_fastest( double seconds_per_prng, + size_t io_block_bytes, + nwipe_prng_bench_result_t* results, + size_t results_count ); + /* Mersenne Twister prototypes. */ int nwipe_twister_init( NWIPE_PRNG_INIT_SIGNATURE ); int nwipe_twister_read( NWIPE_PRNG_READ_SIGNATURE );