Adding upstream version 1.18.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
ed1b0d872f
commit
f46d8ce0c8
45 changed files with 1576 additions and 774 deletions
363
unzcrash.cc
363
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( verbosity >= 0 )
|
||||
std::fprintf( stderr, "byte %d\n", i );
|
||||
const uint8_t byte = buffer[i];
|
||||
for( int j = 1; j < 256; ++j )
|
||||
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 ) )
|
||||
{
|
||||
++buffer[i];
|
||||
if( bits.includes( differing_bits( byte, buffer[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 )
|
||||
{
|
||||
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 );
|
||||
++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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
buffer[i] = byte;
|
||||
else if( program_mode == m_block )
|
||||
{
|
||||
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 ) )
|
||||
{
|
||||
if( verbosity >= 0 )
|
||||
std::fprintf( stderr, "byte %ld\n", i );
|
||||
++positions;
|
||||
const uint8_t byte = buffer[i];
|
||||
for( int j = 1; j < 256; ++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;
|
||||
}
|
||||
}
|
||||
|
||||
delete[] buffer;
|
||||
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" );
|
||||
}
|
||||
else std::fputc( '\n', stderr );
|
||||
}
|
||||
|
||||
std::free( buffer );
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue