Browse Source

Merge branch 'zlib_coverage_squashed'

Nick Mathewson 7 years ago
parent
commit
81cfd5c9a1
4 changed files with 83 additions and 62 deletions
  1. 3 0
      changes/test_zlib_bombs
  2. 4 0
      changes/zlib_12
  3. 13 46
      src/common/torgzip.c
  4. 63 16
      src/test/test_util.c

+ 3 - 0
changes/test_zlib_bombs

@@ -0,0 +1,3 @@
+  o Testing:
+    - We now have unit tests for our code to reject zlib "compression bombs".
+      (Fortunately, the code works fine.)

+ 4 - 0
changes/zlib_12

@@ -0,0 +1,4 @@
+  o New system requirements:
+    - We now require zlib version 1.2 or later. (Back when we started,
+      zlib 1.1 and zlib 1.0 were still found in the wild. 1.2 was released in
+      2003. We recommend the latest version.)

+ 13 - 46
src/common/torgzip.c

@@ -46,34 +46,16 @@
 
 #include <zlib.h>
 
+#if defined ZLIB_VERNUM && ZLIB_VERNUM < 0x1200
+#error "We require zlib version 1.2 or later."
+#endif
+
 static size_t tor_zlib_state_size_precalc(int inflate,
                                           int windowbits, int memlevel);
 
 /** Total number of bytes allocated for zlib state */
 static size_t total_zlib_allocation = 0;
 
-/** Set to 1 if zlib is a version that supports gzip; set to 0 if it doesn't;
- * set to -1 if we haven't checked yet. */
-static int gzip_is_supported = -1;
-
-/** Return true iff we support gzip-based compression.  Otherwise, we need to
- * use zlib. */
-int
-is_gzip_supported(void)
-{
-  if (gzip_is_supported >= 0)
-    return gzip_is_supported;
-
-  if (!strcmpstart(ZLIB_VERSION, "0.") ||
-      !strcmpstart(ZLIB_VERSION, "1.0") ||
-      !strcmpstart(ZLIB_VERSION, "1.1"))
-    gzip_is_supported = 0;
-  else
-    gzip_is_supported = 1;
-
-  return gzip_is_supported;
-}
-
 /** Return a string representation of the version of the currently running
  * version of zlib. */
 const char *
@@ -165,12 +147,6 @@ tor_gzip_compress(char **out, size_t *out_len,
 
   *out = NULL;
 
-  if (method == GZIP_METHOD && !is_gzip_supported()) {
-    /* Old zlib version don't support gzip in deflateInit2 */
-    log_warn(LD_BUG, "Gzip not supported with zlib %s", ZLIB_VERSION);
-    goto err;
-  }
-
   stream = tor_malloc_zero(sizeof(struct z_stream_s));
   stream->zalloc = Z_NULL;
   stream->zfree = Z_NULL;
@@ -182,9 +158,11 @@ tor_gzip_compress(char **out, size_t *out_len,
                    method_bits(method, HIGH_COMPRESSION),
                    get_memlevel(HIGH_COMPRESSION),
                    Z_DEFAULT_STRATEGY) != Z_OK) {
+    //LCOV_EXCL_START -- we can only provoke failure by giving junk arguments.
     log_warn(LD_GENERAL, "Error from deflateInit2: %s",
              stream->msg?stream->msg:"<no message>");
     goto err;
+    //LCOV_EXCL_STOP
   }
 
   /* Guess 50% compression. */
@@ -237,13 +215,12 @@ tor_gzip_compress(char **out, size_t *out_len,
    *    the newly unsigned field isn't negative." */
   tor_assert(stream->total_out >= 0);
 #endif
-  if (((size_t)stream->total_out) > out_size + 4097) {
-    /* If we're wasting more than 4k, don't. */
-    *out = tor_realloc(*out, stream->total_out + 1);
-  }
   if (deflateEnd(stream)!=Z_OK) {
+    // LCOV_EXCL_START -- unreachable if we handled the zlib structure right
+    tor_assert_nonfatal_unreached();
     log_warn(LD_BUG, "Error freeing gzip structures");
     goto err;
+    // LCOV_EXCL_STOP
   }
   tor_free(stream);
 
@@ -291,12 +268,6 @@ tor_gzip_uncompress(char **out, size_t *out_len,
   tor_assert(in);
   tor_assert(in_len < UINT_MAX);
 
-  if (method == GZIP_METHOD && !is_gzip_supported()) {
-    /* Old zlib version don't support gzip in inflateInit2 */
-    log_warn(LD_BUG, "Gzip not supported with zlib %s", ZLIB_VERSION);
-    return -1;
-  }
-
   *out = NULL;
 
   stream = tor_malloc_zero(sizeof(struct z_stream_s));
@@ -308,9 +279,11 @@ tor_gzip_uncompress(char **out, size_t *out_len,
 
   if (inflateInit2(stream,
                    method_bits(method, HIGH_COMPRESSION)) != Z_OK) {
+    // LCOV_EXCL_START -- can only hit this if we give bad inputs.
     log_warn(LD_GENERAL, "Error from inflateInit2: %s",
              stream->msg?stream->msg:"<no message>");
     goto err;
+    // LCOV_EXCL_STOP
   }
 
   out_size = in_len * 2;  /* guess 50% compression. */
@@ -451,12 +424,6 @@ tor_zlib_new(int compress, compress_method_t method,
   tor_zlib_state_t *out;
   int bits, memlevel;
 
-  if (method == GZIP_METHOD && !is_gzip_supported()) {
-    /* Old zlib version don't support gzip in inflateInit2 */
-    log_warn(LD_BUG, "Gzip not supported with zlib %s", ZLIB_VERSION);
-    return NULL;
- }
-
  if (! compress) {
    /* use this setting for decompression, since we might have the
     * max number of window bits */
@@ -474,10 +441,10 @@ tor_zlib_new(int compress, compress_method_t method,
    if (deflateInit2(&out->stream, Z_BEST_COMPRESSION, Z_DEFLATED,
                     bits, memlevel,
                     Z_DEFAULT_STRATEGY) != Z_OK)
-     goto err;
+     goto err; // LCOV_EXCL_LINE
  } else {
    if (inflateInit2(&out->stream, bits) != Z_OK)
-     goto err;
+     goto err; // LCOV_EXCL_LINE
  }
  out->allocation = tor_zlib_state_size_precalc(!compress, bits, memlevel);
 

+ 63 - 16
src/test/test_util.c

@@ -1821,22 +1821,21 @@ test_util_gzip(void *arg)
   (void)arg;
   buf1 = tor_strdup("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAAAAAAAAAAAAAAAAZ");
   tt_assert(detect_compression_method(buf1, strlen(buf1)) == UNKNOWN_METHOD);
-  if (is_gzip_supported()) {
-    tt_assert(!tor_gzip_compress(&buf2, &len1, buf1, strlen(buf1)+1,
-                                   GZIP_METHOD));
-    tt_assert(buf2);
-    tt_assert(len1 < strlen(buf1));
-    tt_assert(detect_compression_method(buf2, len1) == GZIP_METHOD);
-
-    tt_assert(!tor_gzip_uncompress(&buf3, &len2, buf2, len1,
-                                     GZIP_METHOD, 1, LOG_INFO));
-    tt_assert(buf3);
-    tt_int_op(strlen(buf1) + 1,OP_EQ, len2);
-    tt_str_op(buf1,OP_EQ, buf3);
-
-    tor_free(buf2);
-    tor_free(buf3);
-  }
+
+  tt_assert(!tor_gzip_compress(&buf2, &len1, buf1, strlen(buf1)+1,
+                               GZIP_METHOD));
+  tt_assert(buf2);
+  tt_assert(len1 < strlen(buf1));
+  tt_assert(detect_compression_method(buf2, len1) == GZIP_METHOD);
+
+  tt_assert(!tor_gzip_uncompress(&buf3, &len2, buf2, len1,
+                                 GZIP_METHOD, 1, LOG_INFO));
+  tt_assert(buf3);
+  tt_int_op(strlen(buf1) + 1,OP_EQ, len2);
+  tt_str_op(buf1,OP_EQ, buf3);
+
+  tor_free(buf2);
+  tor_free(buf3);
 
   tt_assert(!tor_gzip_compress(&buf2, &len1, buf1, strlen(buf1)+1,
                                  ZLIB_METHOD));
@@ -1920,6 +1919,53 @@ test_util_gzip(void *arg)
   tor_free(buf1);
 }
 
+static void
+test_util_gzip_compression_bomb(void *arg)
+{
+  /* A 'compression bomb' is a very small object that uncompresses to a huge
+   * one. Most compression formats support them, but they can be a DOS vector.
+   * In Tor we try not to generate them, and we don't accept them.
+   */
+  (void) arg;
+  size_t one_million = 1<<20;
+  char *one_mb = tor_malloc_zero(one_million);
+  char *result = NULL;
+  size_t result_len = 0;
+  tor_zlib_state_t *state = NULL;
+
+  /* Make sure we can't produce a compression bomb */
+  tt_int_op(-1, OP_EQ, tor_gzip_compress(&result, &result_len,
+                                         one_mb, one_million,
+                                         ZLIB_METHOD));
+
+  /* Here's a compression bomb that we made manually. */
+  const char compression_bomb[1039] =
+    { 0x78, 0xDA, 0xED, 0xC1, 0x31, 0x01, 0x00, 0x00, 0x00, 0xC2,
+      0xA0, 0xF5, 0x4F, 0x6D, 0x08, 0x5F, 0xA0 /* .... */ };
+  tt_int_op(-1, OP_EQ, tor_gzip_uncompress(&result, &result_len,
+                                           compression_bomb, 1039,
+                                           ZLIB_METHOD, 0, LOG_WARN));
+
+
+  /* Now try streaming that. */
+  state = tor_zlib_new(0, ZLIB_METHOD, HIGH_COMPRESSION);
+  tor_zlib_output_t r;
+  const char *inp = compression_bomb;
+  size_t inlen = 1039;
+  do {
+    char *outp = one_mb;
+    size_t outleft = 4096; /* small on purpose */
+    r = tor_zlib_process(state, &outp, &outleft, &inp, &inlen, 0);
+    tt_int_op(inlen, OP_NE, 0);
+  } while (r == TOR_ZLIB_BUF_FULL);
+
+  tt_int_op(r, OP_EQ, TOR_ZLIB_ERR);
+
+ done:
+  tor_free(one_mb);
+  tor_zlib_free(state);
+}
+
 /** Run unit tests for mmap() wrapper functionality. */
 static void
 test_util_mmap(void *arg)
@@ -4935,6 +4981,7 @@ struct testcase_t util_tests[] = {
   UTIL_LEGACY(strmisc),
   UTIL_LEGACY(pow2),
   UTIL_LEGACY(gzip),
+  UTIL_LEGACY(gzip_compression_bomb),
   UTIL_LEGACY(datadir),
   UTIL_LEGACY(memarea),
   UTIL_LEGACY(control_formats),