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 );