summaryrefslogtreecommitdiffstats
path: root/unzcrash.cc
diff options
context:
space:
mode:
Diffstat (limited to 'unzcrash.cc')
-rw-r--r--unzcrash.cc117
1 files changed, 95 insertions, 22 deletions
diff --git a/unzcrash.cc b/unzcrash.cc
index 3970638..9a32b82 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-2016 Antonio Diaz Diaz.
+ Copyright (C) 2008-2017 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
@@ -40,12 +40,16 @@
#error "Environments where CHAR_BIT != 8 are not supported."
#endif
+#ifndef INT64_MAX
+#define INT64_MAX 0x7FFFFFFFFFFFFFFFLL
+#endif
+
namespace {
const char * const Program_name = "Unzcrash";
const char * const program_name = "unzcrash";
-const char * const program_year = "2016";
+const char * const program_year = "2017";
const char * invocation_name = 0;
int verbosity = 0;
@@ -55,14 +59,27 @@ void show_help()
{
std::printf( "%s - Tests robustness of decompressors to corrupted data.\n", Program_name );
std::printf( "\nUsage: %s [options] \"lzip -tv\" filename.lz\n", invocation_name );
- std::printf( "\nThis program reads the specified file and then repeatedly decompresses\n"
- "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"
+ std::printf( "\nBy default, unzcrash reads the specified file and then repeatedly\n"
+ "decompresses it, increasing 256 times each byte of the compressed data,\n"
+ "so as to test all possible one-byte errors.\n"
+ "\nIf the '--block' option is given, unzcrash reads the specified file\n"
+ "and then repeatedly decompresses it, setting all bytes in each\n"
+ "successive block to the value given, so as to test all possible full\n"
+ "sector errors.\n"
+ "\nIf the '--truncate' option is given, unzcrash reads the specified\n"
+ "file and then repeatedly decompresses it, truncating the file to\n"
+ "increasing lengths, so as to test all possible truncation points.\n"
+ "\nNone of the three test modes described above should cause any invalid\n"
+ "memory accesses. If any of them does, please, report it as a bug to the\n"
+ "maintainers of the decompressor being tested.\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"
+ "outputs differ, it means that the decompressor returned a false\n"
+ "negative; it failed to recognize the corruption and produced garbage\n"
+ "output. The only exception is when a multimember file is truncated just\n"
+ "after the last byte of a member, producing a shorter but valid\n"
+ "compressed file. Except in this latter case, please, report any false\n"
+ "negative 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"
@@ -72,6 +89,7 @@ void show_help()
" -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"
+ " -e, --set-byte=<pos>,<val> set byte at position <pos> to value <val>\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"
@@ -124,12 +142,13 @@ void internal_error( const char * const msg )
}
-long getnum( const char * const ptr, const long llimit, const long ulimit,
- const bool comma = false )
+long long getnum( const char * const ptr, const long long llimit = -LLONG_MAX,
+ const long long ulimit = LLONG_MAX,
+ const char ** const tailp = 0 )
{
char * tail;
errno = 0;
- long result = strtol( ptr, &tail, 0 );
+ long long result = strtoll( ptr, &tail, 0 );
if( tail == ptr )
{
show_error( "Bad or missing numerical argument.", 0, true );
@@ -138,11 +157,14 @@ long getnum( const char * const ptr, const long llimit, const long ulimit,
if( !errno && tail[0] )
{
- const int factor = ( tail[1] == 'i' ) ? 1024 : 1000;
+ char * const p = tail++;
+ int factor;
+ bool bsuf; // 'B' suffix is present
+ if( tail[0] == 'i' ) { ++tail; factor = 1024; } else factor = 1000;
+ if( tail[0] == 'B' ) { ++tail; bsuf = true; } else bsuf = false;
int exponent = -1; // -1 = bad multiplier
- switch( tail[0] )
+ switch( *p )
{
- case ',': if( comma ) exponent = 0; break;
case 'Y': exponent = 8; break;
case 'Z': exponent = 7; break;
case 'E': exponent = 6; break;
@@ -152,6 +174,8 @@ long getnum( const char * const ptr, const long llimit, const long ulimit,
case 'M': exponent = 2; break;
case 'K': if( factor == 1024 ) exponent = 1; break;
case 'k': if( factor == 1000 ) exponent = 1; break;
+ case 'B': if( factor == 1000 && !bsuf ) exponent = 0; break;
+ default : if( tailp ) { tail = p; exponent = 0; } break;
}
if( exponent < 0 )
{
@@ -160,7 +184,7 @@ long getnum( const char * const ptr, const long llimit, const long ulimit,
}
for( int i = 0; i < exponent; ++i )
{
- if( LONG_MAX / factor >= std::labs( result ) ) result *= factor;
+ if( LLONG_MAX / factor >= std::labs( result ) ) result *= factor;
else { errno = ERANGE; break; }
}
}
@@ -170,23 +194,64 @@ long getnum( const char * const ptr, const long llimit, const long ulimit,
show_error( "Numerical argument out of limits." );
std::exit( 1 );
}
+ if( tailp ) *tailp = tail;
return result;
}
void parse_block( const char * const ptr, long & size, uint8_t & value )
{
- const char * const ptr2 = std::strchr( ptr, ',' );
+ const char * tail = ptr;
+
+ if( tail[0] != ',' )
+ size = getnum( ptr, 1, INT_MAX, &tail );
+ if( tail[0] == ',' )
+ value = getnum( tail + 1, 0, 255 );
+ else if( tail[0] )
+ {
+ show_error( "Bad separator in argument of '--block'", 0, true );
+ std::exit( 1 );
+ }
+ }
+
+
+struct Bad_byte
+ {
+ enum Mode { literal, delta, flip };
+ long long pos;
+ Mode mode;
+ uint8_t value;
- if( !ptr2 || ptr2 != ptr )
- size = getnum( ptr, 1, INT_MAX, true );
- if( ptr2 )
- value = getnum( ptr2 + 1, 0, 255 );
+ Bad_byte() : pos( -1 ), mode( literal ), value( 0 ) {}
+ uint8_t operator()( const uint8_t old_value ) const
+ {
+ if( mode == delta ) return old_value + value;
+ if( mode == flip ) return old_value ^ value;
+ return value;
+ }
+ };
+
+
+// Recognized formats: <pos>,<value> <pos>,+<value> <pos>,f<value>
+//
+void parse_pos_value( const char * const ptr, Bad_byte & bad_byte )
+ {
+ const char * tail;
+ bad_byte.pos = getnum( ptr, 0, INT64_MAX, &tail );
+ if( tail[0] != ',' )
+ {
+ show_error( "Bad separator between <pos> and <val>.", 0, true );
+ std::exit( 1 );
+ }
+ if( tail[1] == '+' ) { ++tail; bad_byte.mode = Bad_byte::delta; }
+ else if( tail[1] == 'f' ) { ++tail; bad_byte.mode = Bad_byte::flip; }
+ else bad_byte.mode = Bad_byte::literal;
+ bad_byte.value = getnum( tail + 1, 0, 255 );
}
/* Returns the address of a malloc'd buffer containing the file data and
- its size in '*size'.
+ the file 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 )
@@ -309,6 +374,7 @@ int main( const int argc, const char * const argv[] )
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
+ Bad_byte bad_byte;
const char * zcmp_program = "zcmp";
long pos = 0;
long max_size = LONG_MAX;
@@ -324,6 +390,7 @@ int main( const int argc, const char * const argv[] )
{ 'b', "bits", Arg_parser::yes },
{ 'B', "block", Arg_parser::maybe },
{ 'd', "delta", Arg_parser::yes },
+ { 'e', "set-byte", Arg_parser::yes },
{ 'p', "position", Arg_parser::yes },
{ 'q', "quiet", Arg_parser::no },
{ 's', "size", Arg_parser::yes },
@@ -331,7 +398,7 @@ int main( const int argc, const char * const argv[] )
{ 'v', "verbose", Arg_parser::no },
{ 'V', "version", Arg_parser::no },
{ 'z', "zcmp", Arg_parser::yes },
- { 0 , 0, Arg_parser::no } };
+ { 0 , 0, Arg_parser::no } };
const Arg_parser parser( argc, argv, options );
if( parser.error().size() ) // bad option
@@ -350,6 +417,7 @@ int main( const int argc, const char * const argv[] )
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 'e': parse_pos_value( arg, bad_byte ); break;
case 'p': pos = getnum( arg, -LONG_MAX, LONG_MAX ); break;
case 'q': verbosity = -1; break;
case 's': max_size = getnum( arg, -LONG_MAX, LONG_MAX ); break;
@@ -414,6 +482,11 @@ int main( const int argc, const char * const argv[] )
{ 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 );
+ if( bad_byte.pos >= file_size )
+ { show_error( "Position of '--set-byte' is beyond end of file." );
+ return 1; }
+ if( bad_byte.pos >= 0 )
+ buffer[bad_byte.pos] = bad_byte( buffer[bad_byte.pos] );
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 ) )