Browse Source

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.
Nick Mathewson 6 years ago
parent
commit
3a073c463d
3 changed files with 141 additions and 3 deletions
  1. 2 2
      src/common/compress.c
  2. 2 1
      src/common/compress.h
  3. 137 0
      src/test/test_util.c

+ 2 - 2
src/common/compress.c

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

+ 2 - 1
src/common/compress.h

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

+ 137 - 0
src/test/test_util.c

@@ -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),