diff options
author | Daniel Baumann <mail@daniel-baumann.ch> | 2016-05-20 06:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <mail@daniel-baumann.ch> | 2016-05-20 06:55:18 +0000 |
commit | 07c2a71a11edd637f0ec9b42b5c5621980c96562 (patch) | |
tree | ffe015da89db655e2f4edbabd6924e41b3971f02 /unzcrash.cc | |
parent | Adding debian version 1.18~pre2-1. (diff) | |
download | lziprecover-07c2a71a11edd637f0ec9b42b5c5621980c96562.tar.xz lziprecover-07c2a71a11edd637f0ec9b42b5c5621980c96562.zip |
Merging upstream version 1.18.
Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
Diffstat (limited to '')
-rw-r--r-- | unzcrash.cc | 361 |
1 files changed, 276 insertions, 85 deletions
diff --git a/unzcrash.cc b/unzcrash.cc index 50262b9..3970638 100644 --- a/unzcrash.cc +++ b/unzcrash.cc @@ -1,6 +1,6 @@ /* Unzcrash - Tests robustness of decompressors to corrupted data. Inspired by unzcrash.c from Julian Seward's bzip2. - Copyright (C) 2008-2015 Antonio Diaz Diaz. + Copyright (C) 2008-2016 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,6 +22,7 @@ (eg, bug) which caused unzcrash to panic. */ +#include <algorithm> #include <cerrno> #include <climits> #include <csignal> @@ -44,7 +45,7 @@ namespace { const char * const Program_name = "Unzcrash"; const char * const program_name = "unzcrash"; -const char * const program_year = "2015"; +const char * const program_year = "2016"; const char * invocation_name = 0; int verbosity = 0; @@ -58,15 +59,28 @@ void show_help() "it, increasing 256 times each byte of the compressed data, so as to test\n" "all possible one-byte errors. This should not cause any invalid memory\n" "accesses. If it does, please, report it as a bug.\n" + "\nIf the decompressor returns with zero status, unzcrash compares the\n" + "output of the decompressor for the original and corrupt files. If the\n" + "outputs differ, it means that the decompressor failed to recognize the\n" + "corruption and produced garbage output. Please, report it as a bug.\n" + "\nIn order to compare the outputs, unzcrash needs a zcmp program able to\n" + "understand the format being tested. For example the one provided by zutils.\n" + "Use '--zcmp=false' to disable comparisons.\n" "\nOptions:\n" - " -h, --help display this help and exit\n" - " -V, --version output version information and exit\n" - " -b, --bits=<range> test N-bit errors instead of full byte\n" - " -p, --position=<bytes> first byte position to test [default 0]\n" - " -q, --quiet suppress all messages\n" - " -s, --size=<bytes> number of byte positions to test [all]\n" - " -v, --verbose be verbose (a 2nd -v gives more)\n" + " -h, --help display this help and exit\n" + " -V, --version output version information and exit\n" + " -b, --bits=<range> test N-bit errors instead of full byte\n" + " -B, --block[=<size>][,<val>] test blocks of given size [512,0]\n" + " -d, --delta=<n> test one of every n bytes/blocks/truncations\n" + " -p, --position=<bytes> first byte position to test [default 0]\n" + " -q, --quiet suppress all messages\n" + " -s, --size=<bytes> number of byte positions to test [all]\n" + " -t, --truncate test decompression of truncated file\n" + " -v, --verbose be verbose (a 2nd -v gives more)\n" + " -z, --zcmp=<command> set zcmp command name and options [zcmp]\n" "Examples of <range>: 1 1,2,3 1-4 1,3-5,8 1-3,5-8\n" + "A negative position is relative to the end of file.\n" + "A negative size is relative to the rest of the file.\n" "\nExit status: 0 for a normal exit, 1 for environmental problems (file\n" "not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or\n" "invalid input file, 3 for an internal consistency error (eg, bug) which\n" @@ -89,19 +103,16 @@ void show_version() void show_error( const char * const msg, const int errcode = 0, const bool help = false ) { - if( verbosity >= 0 ) + if( verbosity < 0 ) return; + if( msg && msg[0] ) { - if( msg && msg[0] ) - { - std::fprintf( stderr, "%s: %s", program_name, msg ); - if( errcode > 0 ) - std::fprintf( stderr, ": %s", std::strerror( errcode ) ); - std::fputc( '\n', stderr ); - } - if( help ) - std::fprintf( stderr, "Try '%s --help' for more information.\n", - invocation_name ); + std::fprintf( stderr, "%s: %s", program_name, msg ); + if( errcode > 0 ) std::fprintf( stderr, ": %s", std::strerror( errcode ) ); + std::fputc( '\n', stderr ); } + if( help ) + std::fprintf( stderr, "Try '%s --help' for more information.\n", + invocation_name ); } @@ -113,13 +124,12 @@ void internal_error( const char * const msg ) } -unsigned long long getnum( const char * const ptr, - const unsigned long long llimit, - const unsigned long long ulimit ) +long getnum( const char * const ptr, const long llimit, const long ulimit, + const bool comma = false ) { char * tail; errno = 0; - unsigned long long result = strtoull( ptr, &tail, 0 ); + long result = strtol( ptr, &tail, 0 ); if( tail == ptr ) { show_error( "Bad or missing numerical argument.", 0, true ); @@ -129,11 +139,10 @@ unsigned long long getnum( const char * const ptr, if( !errno && tail[0] ) { const int factor = ( tail[1] == 'i' ) ? 1024 : 1000; - int exponent = 0; - bool bad_multiplier = false; + int exponent = -1; // -1 = bad multiplier switch( tail[0] ) { - case ' ': break; + case ',': if( comma ) exponent = 0; break; case 'Y': exponent = 8; break; case 'Z': exponent = 7; break; case 'E': exponent = 6; break; @@ -141,20 +150,17 @@ unsigned long long getnum( const char * const ptr, case 'T': exponent = 4; break; case 'G': exponent = 3; break; case 'M': exponent = 2; break; - case 'K': if( factor == 1024 ) exponent = 1; else bad_multiplier = true; - break; - case 'k': if( factor == 1000 ) exponent = 1; else bad_multiplier = true; - break; - default : bad_multiplier = true; + case 'K': if( factor == 1024 ) exponent = 1; break; + case 'k': if( factor == 1000 ) exponent = 1; break; } - if( bad_multiplier ) + if( exponent < 0 ) { show_error( "Bad multiplier in numerical argument.", 0, true ); std::exit( 1 ); } for( int i = 0; i < exponent; ++i ) { - if( ulimit / factor >= result ) result *= factor; + if( LONG_MAX / factor >= std::labs( result ) ) result *= factor; else { errno = ERANGE; break; } } } @@ -168,6 +174,65 @@ unsigned long long getnum( const char * const ptr, } +void parse_block( const char * const ptr, long & size, uint8_t & value ) + { + const char * const ptr2 = std::strchr( ptr, ',' ); + + if( !ptr2 || ptr2 != ptr ) + size = getnum( ptr, 1, INT_MAX, true ); + if( ptr2 ) + value = getnum( ptr2 + 1, 0, 255 ); + } + + +/* Returns the address of a malloc'd buffer containing the file data and + its size in '*size'. + In case of error, returns 0 and does not modify '*size'. +*/ +uint8_t * read_file( const char * const name, long * const size ) + { + FILE * const f = std::fopen( name, "rb" ); + if( !f ) + { + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: Can't open input file '%s': %s\n", + program_name, name, std::strerror( errno ) ); + return 0; + } + + long buffer_size = 1 << 20; + uint8_t * buffer = (uint8_t *)std::malloc( buffer_size ); + if( !buffer ) { show_error( "Not enough memory." ); return 0; } + long file_size = std::fread( buffer, 1, buffer_size, f ); + while( file_size >= buffer_size ) + { + if( buffer_size >= LONG_MAX ) + { + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: Input file '%s' is too large.\n", + program_name, name ); + std::free( buffer ); return 0; + } + buffer_size = ( buffer_size <= LONG_MAX / 2 ) ? 2 * buffer_size : LONG_MAX; + uint8_t * const tmp = (uint8_t *)std::realloc( buffer, buffer_size ); + if( !tmp ) + { show_error( "Not enough memory." ); std::free( buffer ); return 0; } + buffer = tmp; + file_size += std::fread( buffer + file_size, 1, buffer_size - file_size, f ); + } + if( std::ferror( f ) || !std::feof( f ) ) + { + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: Error reading file '%s': %s\n", + program_name, name, std::strerror( errno ) ); + std::free( buffer ); return 0; + } + std::fclose( f ); + *size = file_size; + return buffer; + } + + class Bitset8 // 8 value bitset (1 to 8) { bool data[8]; @@ -241,21 +306,31 @@ int differing_bits( const uint8_t byte1, const uint8_t byte2 ) int main( const int argc, const char * const argv[] ) { - enum { buffer_size = 75 << 20 }; + enum Mode { m_block, m_byte, m_truncate }; + const char * mode_str[3] = { "block", "byte", "size" }; Bitset8 bits; // if Bitset8::parse not called test full byte - int pos = 0; - int max_size = buffer_size; + const char * zcmp_program = "zcmp"; + long pos = 0; + long max_size = LONG_MAX; + long delta = 1; + long block_size = 512; + Mode program_mode = m_byte; + uint8_t block_value = 0; invocation_name = argv[0]; const Arg_parser::Option options[] = { { 'h', "help", Arg_parser::no }, { 'b', "bits", Arg_parser::yes }, + { 'B', "block", Arg_parser::maybe }, + { 'd', "delta", Arg_parser::yes }, { 'p', "position", Arg_parser::yes }, { 'q', "quiet", Arg_parser::no }, { 's', "size", Arg_parser::yes }, + { 't', "truncate", Arg_parser::no }, { 'v', "verbose", Arg_parser::no }, { 'V', "version", Arg_parser::no }, + { 'z', "zcmp", Arg_parser::yes }, { 0 , 0, Arg_parser::no } }; const Arg_parser parser( argc, argv, options ); @@ -271,12 +346,17 @@ int main( const int argc, const char * const argv[] ) switch( code ) { case 'h': show_help(); return 0; - case 'b': if( !bits.parse( arg ) ) return 1; break; - case 'p': pos = getnum( arg, 0, buffer_size - 1 ); break; + case 'b': if( !bits.parse( arg ) ) return 1; program_mode = m_byte; break; + case 'B': if( arg[0] ) parse_block( arg, block_size, block_value ); + program_mode = m_block; break; + case 'd': delta = getnum( arg, 1, INT_MAX ); break; + case 'p': pos = getnum( arg, -LONG_MAX, LONG_MAX ); break; case 'q': verbosity = -1; break; - case 's': max_size = getnum( arg, 1, buffer_size ); break; + case 's': max_size = getnum( arg, -LONG_MAX, LONG_MAX ); break; + case 't': program_mode = m_truncate; break; case 'v': if( verbosity < 4 ) ++verbosity; break; case 'V': show_version(); return 0; + case 'z': zcmp_program = arg; break; default : internal_error( "uncaught option." ); } } // end process options @@ -289,67 +369,178 @@ int main( const int argc, const char * const argv[] ) return 1; } - FILE *f = std::fopen( parser.argument( argind + 1 ).c_str(), "rb" ); + const char * const filename = parser.argument( argind + 1 ).c_str(); + long file_size = 0; + uint8_t * const buffer = read_file( filename, &file_size ); + if( !buffer ) return 1; + const char * const command = parser.argument( argind ).c_str(); + char zcmp_command[1024] = { 0 }; + if( std::strcmp( zcmp_program, "false" ) != 0 ) + snprintf( zcmp_command, sizeof zcmp_command, "%s '%s' -", + zcmp_program, filename ); + + // verify original file + if( verbosity >= 1 ) fprintf( stderr, "Testing file '%s'\n", filename ); + FILE * f = popen( command, "w" ); if( !f ) + { show_error( "Can't open pipe to decompressor", errno ); return 1; } + if( (long)std::fwrite( buffer, 1, file_size, f ) != file_size ) + { show_error( "Can't write to decompressor", errno ); return 1; } + if( pclose( f ) != 0 ) { if( verbosity >= 0 ) - std::fprintf( stderr, "Can't open file '%s' for reading.\n", - parser.argument( argind + 1 ).c_str() ); + std::fprintf( stderr, "%s: Can't run '%s'.\n", program_name, command ); return 1; } - - uint8_t * const buffer = new uint8_t[buffer_size]; - const int size = std::fread( buffer, 1, buffer_size, f ); - if( size >= buffer_size ) + if( zcmp_command[0] ) { - if( verbosity >= 0 ) - std::fprintf( stderr, "input file '%s' is too large.\n", - parser.argument( argind + 1 ).c_str() ); - return 2; - } - std::fclose( f ); - - f = popen( parser.argument( argind ).c_str(), "w" ); - if( !f ) - { show_error( "Can't open pipe", errno ); return 1; } - const int wr = std::fwrite( buffer, 1, size, f ); - if( wr != size || pclose( f ) != 0 ) - { - if( verbosity >= 0 ) - std::fprintf( stderr, "Could not run '%s': %s\n", - parser.argument( argind ).c_str(), std::strerror( errno ) ); - return 1; + f = popen( zcmp_command, "w" ); + if( !f ) + { show_error( "Can't open pipe to zcmp command", errno ); return 1; } + if( (long)std::fwrite( buffer, 1, file_size, f ) != file_size ) + { show_error( "Can't write to zcmp command", errno ); return 1; } + if( pclose( f ) != 0 ) + { + show_error( "zcmp command failed. Skipping comparison" ); + zcmp_command[0] = 0; + } } std::signal( SIGPIPE, SIG_IGN ); - if( verbosity >= 1 ) bits.print(); - const int end = ( ( pos + max_size < size ) ? pos + max_size : size ); - for( int i = pos; i < end; ++i ) + if( pos < 0 ) pos = std::max( 0L, file_size + pos ); + if( pos >= file_size || max_size == 0 || + ( max_size < 0 && -max_size >= file_size - pos ) ) + { show_error( "Nothing to do; domain is empty." ); return 0; } + if( max_size < 0 ) max_size += file_size - pos; + const long end = ( ( max_size < file_size - pos ) ? pos + max_size : file_size ); + long positions = 0, decompressions = 0, successes = 0, failed_comparisons = 0; + if( program_mode == m_truncate ) + for( long i = pos; i < end; i += std::min( delta, end - i ) ) + { + if( verbosity >= 0 ) + std::fprintf( stderr, "length %ld\n", i ); + ++positions; ++decompressions; + f = popen( command, "w" ); + if( !f ) { show_error( "Can't open pipe", errno ); return 1; } + std::fwrite( buffer, 1, i, f ); + if( pclose( f ) == 0 ) + { + ++successes; + if( verbosity >= 0 ) + std::fputs( "passed the test\n", stderr ); + if( zcmp_command[0] ) + { + f = popen( zcmp_command, "w" ); + if( !f ) { show_error( "Can't open pipe", errno ); return 1; } + std::fwrite( buffer, 1, i, f ); + if( pclose( f ) != 0 ) + { + ++failed_comparisons; + if( verbosity >= 0 ) + std::fprintf( stderr, "byte %ld comparison failed\n", i ); + } + } + } + } + else if( program_mode == m_block ) { - if( verbosity >= 0 ) - std::fprintf( stderr, "byte %d\n", i ); - const uint8_t byte = buffer[i]; - for( int j = 1; j < 256; ++j ) + uint8_t * block = (uint8_t *)std::malloc( block_size ); + if( !block ) { show_error( "Not enough memory." ); return 1; } + for( long i = pos; i < end; i += std::min( block_size * delta, end - i ) ) + { + const long size = std::min( block_size, file_size - i ); + if( verbosity >= 0 ) + std::fprintf( stderr, "block %ld,%ld\n", i, size ); + ++positions; ++decompressions; + f = popen( command, "w" ); + if( !f ) { show_error( "Can't open pipe", errno ); return 1; } + std::memcpy( block , buffer + i, size ); + std::memset( buffer + i, block_value, size ); + std::fwrite( buffer, 1, file_size, f ); + if( pclose( f ) == 0 ) + { + ++successes; + if( verbosity >= 0 ) + std::fputs( "passed the test\n", stderr ); + if( zcmp_command[0] ) + { + f = popen( zcmp_command, "w" ); + if( !f ) { show_error( "Can't open pipe", errno ); return 1; } + std::fwrite( buffer, 1, file_size, f ); + if( pclose( f ) != 0 ) + { + ++failed_comparisons; + if( verbosity >= 0 ) + std::fprintf( stderr, "block %ld,%ld comparison failed\n", i, size ); + } + } + } + std::memcpy( buffer + i, block, size ); + } + std::free( block ); + } + else + { + if( verbosity >= 1 ) bits.print(); + for( long i = pos; i < end; i += std::min( delta, end - i ) ) { - ++buffer[i]; - if( bits.includes( differing_bits( byte, buffer[i] ) ) ) + if( verbosity >= 0 ) + std::fprintf( stderr, "byte %ld\n", i ); + ++positions; + const uint8_t byte = buffer[i]; + for( int j = 1; j < 256; ++j ) { - if( verbosity >= 2 ) - std::fprintf( stderr, "0x%02X (0x%02X+0x%02X) ", - buffer[i], byte, j ); - f = popen( parser.argument( argind ).c_str(), "w" ); - if( !f ) - { show_error( "Can't open pipe", errno ); return 1; } - std::fwrite( buffer, 1, size, f ); - if( pclose( f ) == 0 && verbosity >= 0 ) - std::fprintf( stderr, "0x%02X (0x%02X+0x%02X) passed the test\n", - buffer[i], byte, j ); + ++buffer[i]; + if( bits.includes( differing_bits( byte, buffer[i] ) ) ) + { + ++decompressions; + if( verbosity >= 2 ) + std::fprintf( stderr, "0x%02X (0x%02X+0x%02X) ", + buffer[i], byte, j ); + f = popen( command, "w" ); + if( !f ) { show_error( "Can't open pipe", errno ); return 1; } + std::fwrite( buffer, 1, file_size, f ); + if( pclose( f ) == 0 ) + { + ++successes; + if( verbosity >= 0 ) + std::fprintf( stderr, "0x%02X (0x%02X+0x%02X) passed the test\n", + buffer[i], byte, j ); + if( zcmp_command[0] ) + { + f = popen( zcmp_command, "w" ); + if( !f ) { show_error( "Can't open pipe", errno ); return 1; } + std::fwrite( buffer, 1, file_size, f ); + if( pclose( f ) != 0 ) + { + ++failed_comparisons; + if( verbosity >= 0 ) + std::fprintf( stderr, "byte %ld comparison failed\n", i ); + } + } + } + } } + buffer[i] = byte; + } + } + + if( verbosity >= 0 ) + { + std::fprintf( stderr, "\n%8ld %ss tested\n%8ld total decompressions" + "\n%8ld decompressions returned with zero status", + positions, mode_str[program_mode], decompressions, successes ); + if( successes > 0 ) + { + if( zcmp_command[0] ) + std::fprintf( stderr, ", of which\n%8ld comparisons failed\n", + failed_comparisons ); + else std::fprintf( stderr, "\n comparisons disabled\n" ); } - buffer[i] = byte; + else std::fputc( '\n', stderr ); } - delete[] buffer; + std::free( buffer ); return 0; } |