Improve unit test coverage for compression code.

These tests try uncompressing garbage, verify that we won't
make compression bombs, and verify that we won't uncompress
compression bombs.
This commit is contained in:
Nick Mathewson 2017-09-28 12:20:02 -04:00
parent 14614a592e
commit 3a073c463d
3 changed files with 141 additions and 3 deletions

View File

@ -51,8 +51,8 @@ static atomic_counter_t total_compress_allocation;
/** Return true if uncompressing an input of size <b>in_size</b> to an input of
* size at least <b>size_out</b> looks like a compression bomb. */
int
tor_compress_is_compression_bomb(size_t size_in, size_t size_out)
MOCK_IMPL(int,
tor_compress_is_compression_bomb,(size_t size_in, size_t size_out))
{
if (size_in == 0 || size_out < CHECK_FOR_COMPRESSION_BOMB_AFTER)
return 0;

View File

@ -45,7 +45,8 @@ int tor_uncompress(char **out, size_t *out_len,
compress_method_t detect_compression_method(const char *in, size_t in_len);
int tor_compress_is_compression_bomb(size_t size_in, size_t size_out);
MOCK_DECL(int,tor_compress_is_compression_bomb,(size_t size_in,
size_t size_out));
int tor_compress_supports_method(compress_method_t method);
unsigned tor_compress_get_supported_method_bitmask(void);

View File

@ -2480,6 +2480,125 @@ test_util_decompress_concatenated(void *arg)
;
}
static void
test_util_decompress_junk_impl(compress_method_t method)
{
char input[4096];
char *result = NULL, *result2 = NULL;
size_t szr, szr2, sz;
int r;
/* This shouldn't be a compressed string according to any method. */
strlcpy(input, "This shouldn't be a compressed string by any means.",
sizeof(input));
sz = strlen(input);
setup_capture_of_logs(LOG_WARN);
r = tor_uncompress(&result, &szr, input, sz, method, 0, LOG_WARN);
tt_int_op(r, OP_EQ, -1);
tt_ptr_op(result, OP_EQ, NULL);
expect_log_msg_containing("Error while uncompressing data: bad input?");
mock_clean_saved_logs();
/* Now try again, with a compressed object that starts out good and turns to
junk. */
crypto_rand(input, sizeof(input));
r = tor_compress(&result, &szr, input, sizeof(input), method);
tt_int_op(r, OP_EQ, 0);
crypto_rand(result+szr/2, szr-(szr/2)); // trash the 2nd half of the result
r = tor_uncompress(&result2, &szr2, result, szr, method, 0, LOG_WARN);
tt_int_op(r, OP_EQ, -1);
expect_log_msg_containing("Error while uncompressing data: bad input?");
done:
teardown_capture_of_logs();
tor_free(result);
tor_free(result2);
}
static void
test_util_decompress_junk(void *arg)
{
const char *methodname = arg;
tt_assert(methodname);
compress_method_t method = compression_method_get_by_name(methodname);
tt_int_op(method, OP_NE, UNKNOWN_METHOD);
if (! tor_compress_supports_method(method)) {
tt_skip();
}
test_util_decompress_junk_impl(method);
done:
;
}
/* mock replacement for tor_compress_is_compression_bomb that doesn't
* believe in compression bombs. */
static int
mock_is_never_compression_bomb(size_t in, size_t out)
{
(void)in;
(void) out;
return 0;
}
static void
test_util_decompress_dos_impl(compress_method_t method)
{
char *input;
char *result = NULL, *result2 = NULL;
size_t szr, szr2;
int r;
const size_t big = 1024*1024;
/* one megabyte of 0s. */
input = tor_malloc_zero(big);
/* Compress it into "result": it should fail. */
setup_full_capture_of_logs(LOG_WARN);
r = tor_compress(&result, &szr, input, big, method);
tt_int_op(r, OP_EQ, -1);
expect_log_msg_containing(
"other Tors would think this was a compression bomb");
teardown_capture_of_logs();
/* Try again, but this time suppress compression-bomb detection */
MOCK(tor_compress_is_compression_bomb, mock_is_never_compression_bomb);
r = tor_compress(&result, &szr, input, big, method);
UNMOCK(tor_compress_is_compression_bomb);
tt_int_op(r, OP_EQ, 0);
tt_ptr_op(result, OP_NE, NULL);
/* We should refuse to uncomrpess it again, since it looks like a
* compression bomb. */
setup_capture_of_logs(LOG_WARN);
r = tor_uncompress(&result2, &szr2, result, szr, method, 0, LOG_WARN);
tt_int_op(r, OP_EQ, -1);
expect_log_msg_containing("bomb; abandoning stream");
done:
teardown_capture_of_logs();
tor_free(result);
tor_free(result2);
}
static void
test_util_decompress_dos(void *arg)
{
const char *methodname = arg;
tt_assert(methodname);
compress_method_t method = compression_method_get_by_name(methodname);
tt_int_op(method, OP_NE, UNKNOWN_METHOD);
if (! tor_compress_supports_method(method)) {
tt_skip();
}
test_util_decompress_dos_impl(method);
done:
;
}
static void
test_util_gzip_compression_bomb(void *arg)
{
@ -5917,6 +6036,16 @@ test_util_get_unquoted_path(void *arg)
&passthrough_setup, \
(char*)(identifier) }
#define COMPRESS_JUNK(name, identifier) \
{ "compress_junk/" #name, test_util_decompress_junk, 0, \
&passthrough_setup, \
(char*)(identifier) }
#define COMPRESS_DOS(name, identifier) \
{ "compress_dos/" #name, test_util_decompress_dos, 0, \
&passthrough_setup, \
(char*)(identifier) }
#ifdef _WIN32
#define UTIL_TEST_NO_WIN(n, f) { #n, NULL, TT_SKIP, NULL, NULL }
#define UTIL_TEST_WIN_ONLY(n, f) UTIL_TEST(n, (f))
@ -5951,6 +6080,14 @@ struct testcase_t util_tests[] = {
COMPRESS_CONCAT(lzma, "x-tor-lzma"),
COMPRESS_CONCAT(zstd, "x-zstd"),
COMPRESS_CONCAT(none, "identity"),
COMPRESS_JUNK(zlib, "deflate"),
COMPRESS_JUNK(gzip, "gzip"),
COMPRESS_JUNK(lzma, "x-tor-lzma"),
COMPRESS_DOS(zlib, "deflate"),
COMPRESS_DOS(gzip, "gzip"),
COMPRESS_DOS(lzma, "x-tor-lzma"),
// Disabled for now, since it triggers #23551
// COMPRESS_DOS(zstd, "x-zstd"),
UTIL_TEST(gzip_compression_bomb, TT_FORK),
UTIL_LEGACY(datadir),
UTIL_LEGACY(memarea),