diff --git a/src/common/compress.c b/src/common/compress.c
index 9332c0017d..fd2cb06404 100644
--- a/src/common/compress.c
+++ b/src/common/compress.c
@@ -56,6 +56,147 @@ tor_compress_is_compression_bomb(size_t size_in, size_t size_out)
return (size_out / size_in > MAX_UNCOMPRESSION_FACTOR);
}
+/** Guess the size that in_len will be after compression or
+ * decompression. */
+static size_t
+guess_compress_size(int compress, compress_method_t method,
+ compression_level_t compression_level,
+ size_t in_len)
+{
+ // ignore these for now.
+ (void)method;
+ (void)compression_level;
+
+ /* Always guess a factor of 2. */
+ if (compress) {
+ in_len /= 2;
+ } else {
+ if (in_len < SIZE_T_CEILING/2)
+ in_len *= 2;
+ }
+ return MAX(in_len, 1024);
+}
+
+/** Internal function to implement tor_compress/tor_uncompress, depending on
+ * whether compress is set. All arguments are as for tor_compress or
+ * tor_uncompress. */
+static int
+tor_compress_impl(int compress,
+ char **out, size_t *out_len,
+ const char *in, size_t in_len,
+ compress_method_t method,
+ compression_level_t compression_level,
+ int complete_only,
+ int protocol_warn_level)
+{
+ tor_compress_state_t *stream;
+ int rv;
+
+ stream = tor_compress_new(compress, method, compression_level);
+
+ if (stream == NULL)
+ return -1;
+
+ size_t in_len_orig = in_len;
+ size_t out_remaining, out_alloc;
+ char *outptr;
+
+ out_remaining = out_alloc =
+ guess_compress_size(compress, method, compression_level, in_len);
+ *out = outptr = tor_malloc(out_remaining);
+
+ const int finish = complete_only || compress;
+
+ while (1) {
+ switch (tor_compress_process(stream,
+ &outptr, &out_remaining,
+ &in, &in_len, finish)) {
+ case TOR_COMPRESS_DONE:
+ if (in_len == 0 || compress) {
+ goto done;
+ } else {
+ // More data is present, and we're decompressing. So we may need to
+ // reinitialize the stream if we are handling multiple concatenated
+ // inputs.
+ tor_compress_free(stream);
+ stream = tor_compress_new(compress, method, compression_level);
+ }
+ break;
+ case TOR_COMPRESS_OK:
+ if (compress || complete_only) {
+ goto err;
+ } else {
+ goto done;
+ }
+ break;
+ case TOR_COMPRESS_BUFFER_FULL: {
+ if (!compress && outptr < *out+out_alloc) {
+ // A buffer error in this case means that we have a problem
+ // with our input.
+ log_fn(protocol_warn_level, LD_PROTOCOL,
+ "Possible truncated or corrupt compressed data");
+ goto err;
+ }
+ if (out_alloc >= SIZE_T_CEILING / 2) {
+ log_warn(LD_GENERAL, "While %scompresing data: ran out of space.",
+ compress?"":"un");
+ goto err;
+ }
+ if (!compress &&
+ tor_compress_is_compression_bomb(in_len_orig, out_alloc)) {
+ // This should already have been caught down in the backend logic.
+ // LCOV_EXCL_START
+ tor_assert_nonfatal_unreached();
+ goto err;
+ // LCOV_EXCL_STOP
+ }
+ const size_t offset = outptr - *out;
+ out_alloc *= 2;
+ *out = tor_realloc(*out, out_alloc);
+ outptr = *out + offset;
+ out_remaining = out_alloc - offset;
+ break;
+ }
+ case TOR_COMPRESS_ERROR:
+ log_fn(protocol_warn_level, LD_GENERAL,
+ "Error while %scompresing data: bad input?",
+ compress?"":"un");
+ goto err; // bad data.
+ default:
+ // LCOV_EXCL_START
+ tor_assert_nonfatal_unreached();
+ goto err;
+ // LCOV_EXCL_STOP
+ }
+ }
+ done:
+ *out_len = outptr - *out;
+ if (compress && tor_compress_is_compression_bomb(*out_len, in_len_orig)) {
+ log_warn(LD_BUG, "We compressed something and got an insanely high "
+ "compression factor; other Tors would think this was a "
+ "compression bomb.");
+ goto err;
+ }
+ if (!compress) {
+ // NUL-terminate our output.
+ if (out_alloc == *out_len)
+ *out = tor_realloc(*out, out_alloc + 1);
+ (*out)[*out_len] = '\0';
+ }
+ rv = 0;
+ goto out;
+
+ err:
+ tor_free(*out);
+ *out_len = 0;
+ rv = -1;
+ goto out;
+
+ out:
+ tor_compress_free(stream);
+ return rv;
+}
+
/** Given in_len bytes at in, compress them into a newly
* allocated buffer, using the method described in method. Store the
* compressed string in *out, and its length in *out_len.
@@ -66,19 +207,9 @@ tor_compress(char **out, size_t *out_len,
const char *in, size_t in_len,
compress_method_t method)
{
- switch (method) {
- case GZIP_METHOD:
- case ZLIB_METHOD:
- return tor_zlib_compress(out, out_len, in, in_len, method);
- case LZMA_METHOD:
- return tor_lzma_compress(out, out_len, in, in_len, method);
- case ZSTD_METHOD:
- return tor_zstd_compress(out, out_len, in, in_len, method);
- case NO_METHOD:
- case UNKNOWN_METHOD:
- default:
- return -1;
- }
+ return tor_compress_impl(1, out, out_len, in, in_len, method,
+ BEST_COMPRESSION,
+ 1, LOG_WARN);
}
/** Given zero or more zlib-compressed or gzip-compressed strings of
@@ -99,28 +230,9 @@ tor_uncompress(char **out, size_t *out_len,
int complete_only,
int protocol_warn_level)
{
- switch (method) {
- case GZIP_METHOD:
- case ZLIB_METHOD:
- return tor_zlib_uncompress(out, out_len, in, in_len,
- method,
- complete_only,
- protocol_warn_level);
- case LZMA_METHOD:
- return tor_lzma_uncompress(out, out_len, in, in_len,
- method,
- complete_only,
- protocol_warn_level);
- case ZSTD_METHOD:
- return tor_zstd_uncompress(out, out_len, in, in_len,
- method,
- complete_only,
- protocol_warn_level);
- case NO_METHOD:
- case UNKNOWN_METHOD:
- default:
- return -1;
- }
+ return tor_compress_impl(0, out, out_len, in, in_len, method,
+ BEST_COMPRESSION,
+ complete_only, protocol_warn_level);
}
/** Try to tell whether the in_len-byte string in in is likely
diff --git a/src/common/compress.h b/src/common/compress.h
index 5dd916b961..cb5caeaf07 100644
--- a/src/common/compress.h
+++ b/src/common/compress.h
@@ -26,11 +26,11 @@ typedef enum {
/**
* Enumeration to define tradeoffs between memory usage and compression level.
- * HIGH_COMPRESSION saves the most bandwidth; LOW_COMPRESSION saves the most
+ * BEST_COMPRESSION saves the most bandwidth; LOW_COMPRESSION saves the most
* memory.
**/
typedef enum {
- HIGH_COMPRESSION, MEDIUM_COMPRESSION, LOW_COMPRESSION
+ BEST_COMPRESSION, HIGH_COMPRESSION, MEDIUM_COMPRESSION, LOW_COMPRESSION
} compression_level_t;
int tor_compress(char **out, size_t *out_len,
diff --git a/src/common/compress_lzma.c b/src/common/compress_lzma.c
index b0ba4b6ba8..906a4067bd 100644
--- a/src/common/compress_lzma.c
+++ b/src/common/compress_lzma.c
@@ -32,6 +32,7 @@ memory_level(compression_level_t level)
{
switch (level) {
default:
+ case BEST_COMPRESSION:
case HIGH_COMPRESSION: return 9;
case MEDIUM_COMPRESSION: return 6;
case LOW_COMPRESSION: return 3;
@@ -108,296 +109,6 @@ tor_lzma_get_header_version_str(void)
#endif
}
-/** Given in_len bytes at in, compress them into a newly
- * allocated buffer, using the LZMA method. Store the compressed string in
- * *out, and its length in *out_len. Return 0 on success, -1 on
- * failure.
- */
-int
-tor_lzma_compress(char **out, size_t *out_len,
- const char *in, size_t in_len,
- compress_method_t method)
-{
-#ifdef HAVE_LZMA
- lzma_stream stream = LZMA_STREAM_INIT;
- lzma_options_lzma stream_options;
- lzma_ret retval;
- lzma_action action;
- size_t out_size, old_size;
- off_t offset;
-
- tor_assert(out);
- tor_assert(out_len);
- tor_assert(in);
- tor_assert(in_len < UINT_MAX);
- tor_assert(method == LZMA_METHOD);
-
- stream.next_in = (unsigned char *)in;
- stream.avail_in = in_len;
-
- lzma_lzma_preset(&stream_options,
- memory_level(HIGH_COMPRESSION));
-
- retval = lzma_alone_encoder(&stream, &stream_options);
-
- if (retval != LZMA_OK) {
- log_warn(LD_GENERAL, "Error from LZMA encoder: %s (%u).",
- lzma_error_str(retval), retval);
- goto err;
- }
-
- out_size = in_len / 2;
- if (out_size < 1024)
- out_size = 1024;
-
- *out = tor_malloc(out_size);
-
- stream.next_out = (unsigned char *)*out;
- stream.avail_out = out_size;
-
- action = LZMA_RUN;
-
- while (1) {
- retval = lzma_code(&stream, action);
- switch (retval) {
- case LZMA_OK:
- action = LZMA_FINISH;
- break;
- case LZMA_STREAM_END:
- goto done;
- case LZMA_BUF_ERROR:
- offset = stream.next_out - ((unsigned char *)*out);
- old_size = out_size;
- out_size *= 2;
-
- if (out_size < old_size) {
- log_warn(LD_GENERAL, "Size overflow in LZMA compression.");
- goto err;
- }
-
- *out = tor_realloc(*out, out_size);
- stream.next_out = (unsigned char *)(*out + offset);
- if (out_size - offset > UINT_MAX) {
- log_warn(LD_BUG, "Ran over unsigned int limit of LZMA while "
- "compressing.");
- goto err;
- }
- stream.avail_out = (unsigned int)(out_size - offset);
- break;
-
- // We list all the possible values of `lzma_ret` here to silence the
- // `switch-enum` warning and to detect if a new member was added.
- case LZMA_NO_CHECK:
- case LZMA_UNSUPPORTED_CHECK:
- case LZMA_GET_CHECK:
- case LZMA_MEM_ERROR:
- case LZMA_MEMLIMIT_ERROR:
- case LZMA_FORMAT_ERROR:
- case LZMA_OPTIONS_ERROR:
- case LZMA_DATA_ERROR:
- case LZMA_PROG_ERROR:
- default:
- log_warn(LD_GENERAL, "LZMA compression didn't finish: %s.",
- lzma_error_str(retval));
- goto err;
- }
- }
-
- done:
- *out_len = stream.total_out;
- lzma_end(&stream);
-
- if (tor_compress_is_compression_bomb(*out_len, in_len)) {
- log_warn(LD_BUG, "We compressed something and got an insanely high "
- "compression factor; other Tor instances would think "
- "this is a compression bomb.");
- goto err;
- }
-
- return 0;
-
- err:
- lzma_end(&stream);
- tor_free(*out);
- return -1;
-#else // HAVE_LZMA.
- (void)out;
- (void)out_len;
- (void)in;
- (void)in_len;
- (void)method;
-
- return -1;
-#endif // HAVE_LZMA.
-}
-
-/** Given an LZMA compressed string of total length in_len bytes at
- * in, uncompress them into a newly allocated buffer. Store the
- * uncompressed string in *out, and its length in *out_len.
- * Return 0 on success, -1 on failure.
- *
- * If complete_only is true, we consider a truncated input as a failure;
- * otherwise we decompress as much as we can. Warn about truncated or corrupt
- * inputs at protocol_warn_level.
- */
-int
-tor_lzma_uncompress(char **out, size_t *out_len,
- const char *in, size_t in_len,
- compress_method_t method,
- int complete_only,
- int protocol_warn_level)
-{
-#ifdef HAVE_LZMA
- lzma_stream stream = LZMA_STREAM_INIT;
- lzma_ret retval;
- lzma_action action;
- size_t out_size, old_size;
- off_t offset;
-
- tor_assert(out);
- tor_assert(out_len);
- tor_assert(in);
- tor_assert(in_len < UINT_MAX);
- tor_assert(method == LZMA_METHOD);
-
- stream.next_in = (unsigned char *)in;
- stream.avail_in = in_len;
-
- // FIXME(ahf): This should be something more sensible than
- // UINT64_MAX: See #21665.
- retval = lzma_alone_decoder(&stream, UINT64_MAX);
-
- if (retval != LZMA_OK) {
- log_warn(LD_GENERAL, "Error from LZMA decoder: %s (%u).",
- lzma_error_str(retval), retval);
- goto err;
- }
-
- out_size = in_len * 2;
- if (out_size < 1024)
- out_size = 1024;
-
- if (out_size >= SIZE_T_CEILING || out_size > UINT_MAX)
- goto err;
-
- *out = tor_malloc(out_size);
- stream.next_out = (unsigned char *)*out;
- stream.avail_out = out_size;
-
- // FIXME(ahf): We should figure out how to use LZMA_FULL_FLUSH to
- // make the partial string read tests.
- // action = complete_only ? LZMA_FINISH : LZMA_SYNC_FLUSH. // To do this,
- // it seems like we have to use LZMA using their "xz" encoder instead of just
- // regular LZMA.
- (void)complete_only;
- action = LZMA_FINISH;
-
- while (1) {
- retval = lzma_code(&stream, action);
- switch (retval) {
- case LZMA_STREAM_END:
- if (stream.avail_in == 0)
- goto done;
-
- // We might have more data here. Reset our stream.
- lzma_end(&stream);
-
- retval = lzma_alone_decoder(&stream, UINT64_MAX);
-
- if (retval != LZMA_OK) {
- log_warn(LD_GENERAL, "Error from LZMA decoder: %s (%u).",
- lzma_error_str(retval), retval);
- goto err;
- }
- break;
- case LZMA_OK:
- break;
- case LZMA_BUF_ERROR:
- if (stream.avail_out > 0) {
- log_fn(protocol_warn_level, LD_PROTOCOL,
- "possible truncated or corrupt LZMA data.");
- goto err;
- }
-
- offset = stream.next_out - (unsigned char *)*out;
- old_size = out_size;
- out_size *= 2;
-
- if (out_size < old_size) {
- log_warn(LD_GENERAL, "Size overflow in LZMA uncompression.");
- goto err;
- }
-
- if (tor_compress_is_compression_bomb(in_len, out_size)) {
- log_warn(LD_GENERAL, "Input looks like a possible LZMA compression "
- "bomb. Not proceeding.");
- goto err;
- }
-
- if (out_size >= SIZE_T_CEILING) {
- log_warn(LD_BUG, "Hit SIZE_T_CEILING limit while uncompressing "
- "LZMA data.");
- goto err;
- }
-
- *out = tor_realloc(*out, out_size);
- stream.next_out = (unsigned char *)(*out + offset);
-
- if (out_size - offset > UINT_MAX) {
- log_warn(LD_BUG, "Ran over unsigned int limit of LZMA while "
- "uncompressing.");
- goto err;
- }
-
- stream.avail_out = (unsigned int)(out_size - offset);
- break;
-
- // We list all the possible values of `lzma_ret` here to silence the
- // `switch-enum` warning and to detect if a new member was added.
- case LZMA_NO_CHECK:
- case LZMA_UNSUPPORTED_CHECK:
- case LZMA_GET_CHECK:
- case LZMA_MEM_ERROR:
- case LZMA_MEMLIMIT_ERROR:
- case LZMA_FORMAT_ERROR:
- case LZMA_OPTIONS_ERROR:
- case LZMA_DATA_ERROR:
- case LZMA_PROG_ERROR:
- default:
- log_warn(LD_GENERAL, "LZMA decompression didn't finish: %s.",
- lzma_error_str(retval));
- goto err;
- }
- }
-
- done:
- *out_len = stream.next_out - (unsigned char*)*out;
- lzma_end(&stream);
-
- // NUL-terminate our output.
- if (out_size == *out_len)
- *out = tor_realloc(*out, out_size + 1);
- (*out)[*out_len] = '\0';
-
- return 0;
-
- err:
- lzma_end(&stream);
- tor_free(*out);
- return -1;
-#else // HAVE_LZMA.
- (void)out;
- (void)out_len;
- (void)in;
- (void)in_len;
- (void)method;
- (void)complete_only;
- (void)protocol_warn_level;
-
- return -1;
-#endif // HAVE_LZMA.
-}
-
/** Internal LZMA state for incremental compression/decompression.
* The body of this struct is not exposed. */
struct tor_lzma_compress_state_t {
diff --git a/src/common/compress_lzma.h b/src/common/compress_lzma.h
index 60a5f9ce82..1433c89f88 100644
--- a/src/common/compress_lzma.h
+++ b/src/common/compress_lzma.h
@@ -17,16 +17,6 @@ const char *tor_lzma_get_version_str(void);
const char *tor_lzma_get_header_version_str(void);
-int tor_lzma_compress(char **out, size_t *out_len,
- const char *in, size_t in_len,
- compress_method_t method);
-
-int tor_lzma_uncompress(char **out, size_t *out_len,
- const char *in, size_t in_len,
- compress_method_t method,
- int complete_only,
- int protocol_warn_level);
-
/** Internal state for an incremental LZMA compression/decompression. */
typedef struct tor_lzma_compress_state_t tor_lzma_compress_state_t;
diff --git a/src/common/compress_zlib.c b/src/common/compress_zlib.c
index 5425f205a0..3fc574ce9a 100644
--- a/src/common/compress_zlib.c
+++ b/src/common/compress_zlib.c
@@ -56,6 +56,7 @@ memory_level(compression_level_t level)
{
switch (level) {
default:
+ case BEST_COMPRESSION: return 9;
case HIGH_COMPRESSION: return 8;
case MEDIUM_COMPRESSION: return 7;
case LOW_COMPRESSION: return 6;
@@ -70,6 +71,7 @@ method_bits(compress_method_t method, compression_level_t level)
const int flag = method == GZIP_METHOD ? 16 : 0;
switch (level) {
default:
+ case BEST_COMPRESSION:
case HIGH_COMPRESSION: return flag + 15;
case MEDIUM_COMPRESSION: return flag + 13;
case LOW_COMPRESSION: return flag + 11;
@@ -102,260 +104,6 @@ tor_zlib_get_header_version_str(void)
return ZLIB_VERSION;
}
-/** Given in_len bytes at in, compress them into a newly
- * allocated buffer, using the method described in method. Store the
- * compressed string in *out, and its length in *out_len. Return
- * 0 on success, -1 on failure.
- */
-int
-tor_zlib_compress(char **out, size_t *out_len,
- const char *in, size_t in_len,
- compress_method_t method)
-{
- struct z_stream_s *stream = NULL;
- size_t out_size, old_size;
- off_t offset;
-
- tor_assert(out);
- tor_assert(out_len);
- tor_assert(in);
- tor_assert(in_len < UINT_MAX);
-
- *out = NULL;
-
- stream = tor_malloc_zero(sizeof(struct z_stream_s));
- stream->zalloc = Z_NULL;
- stream->zfree = Z_NULL;
- stream->opaque = NULL;
- stream->next_in = (unsigned char*) in;
- stream->avail_in = (unsigned int)in_len;
-
- if (deflateInit2(stream, Z_BEST_COMPRESSION, Z_DEFLATED,
- method_bits(method, HIGH_COMPRESSION),
- memory_level(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:"");
- goto err;
- //LCOV_EXCL_STOP
- }
-
- /* Guess 50% compression. */
- out_size = in_len / 2;
- if (out_size < 1024) out_size = 1024;
- *out = tor_malloc(out_size);
- stream->next_out = (unsigned char*)*out;
- stream->avail_out = (unsigned int)out_size;
-
- while (1) {
- switch (deflate(stream, Z_FINISH))
- {
- case Z_STREAM_END:
- goto done;
- case Z_OK:
- /* In case zlib doesn't work as I think .... */
- if (stream->avail_out >= stream->avail_in+16)
- break;
- case Z_BUF_ERROR:
- offset = stream->next_out - ((unsigned char*)*out);
- old_size = out_size;
- out_size *= 2;
- if (out_size < old_size) {
- log_warn(LD_GENERAL, "Size overflow in compression.");
- goto err;
- }
- *out = tor_realloc(*out, out_size);
- stream->next_out = (unsigned char*)(*out + offset);
- if (out_size - offset > UINT_MAX) {
- log_warn(LD_BUG, "Ran over unsigned int limit of zlib while "
- "uncompressing.");
- goto err;
- }
- stream->avail_out = (unsigned int)(out_size - offset);
- break;
- default:
- log_warn(LD_GENERAL, "Gzip compression didn't finish: %s",
- stream->msg ? stream->msg : "");
- goto err;
- }
- }
- done:
- *out_len = stream->total_out;
-#if defined(OpenBSD)
- /* "Hey Rocky! Watch me change an unsigned field to a signed field in a
- * third-party API!"
- * "Oh, that trick will just make people do unsafe casts to the unsigned
- * type in their cross-platform code!"
- * "Don't be foolish. I'm _sure_ they'll have the good sense to make sure
- * the newly unsigned field isn't negative." */
- tor_assert(stream->total_out >= 0);
-#endif
- 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);
-
- if (tor_compress_is_compression_bomb(*out_len, in_len)) {
- log_warn(LD_BUG, "We compressed something and got an insanely high "
- "compression factor; other Tors would think this was a zlib bomb.");
- goto err;
- }
-
- return 0;
- err:
- if (stream) {
- deflateEnd(stream);
- tor_free(stream);
- }
- tor_free(*out);
- return -1;
-}
-
-/** Given an Zlib/Gzip compressed string of total length in_len bytes
- * at in, uncompress them into a newly allocated buffer. Store the
- * uncompressed string in *out, and its length in *out_len.
- * Return 0 on success, -1 on failure.
- *
- * If complete_only is true, we consider a truncated input as a failure;
- * otherwise we decompress as much as we can. Warn about truncated or corrupt
- * inputs at protocol_warn_level.
- */
-int
-tor_zlib_uncompress(char **out, size_t *out_len,
- const char *in, size_t in_len,
- compress_method_t method,
- int complete_only,
- int protocol_warn_level)
-{
- struct z_stream_s *stream = NULL;
- size_t out_size, old_size;
- off_t offset;
- int r;
-
- tor_assert(out);
- tor_assert(out_len);
- tor_assert(in);
- tor_assert(in_len < UINT_MAX);
-
- *out = NULL;
-
- stream = tor_malloc_zero(sizeof(struct z_stream_s));
- stream->zalloc = Z_NULL;
- stream->zfree = Z_NULL;
- stream->opaque = NULL;
- stream->next_in = (unsigned char*) in;
- stream->avail_in = (unsigned int)in_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:"");
- goto err;
- // LCOV_EXCL_STOP
- }
-
- out_size = in_len * 2; /* guess 50% compression. */
- if (out_size < 1024) out_size = 1024;
- if (out_size >= SIZE_T_CEILING || out_size > UINT_MAX)
- goto err;
-
- *out = tor_malloc(out_size);
- stream->next_out = (unsigned char*)*out;
- stream->avail_out = (unsigned int)out_size;
-
- while (1) {
- switch (inflate(stream, complete_only ? Z_FINISH : Z_SYNC_FLUSH))
- {
- case Z_STREAM_END:
- if (stream->avail_in == 0)
- goto done;
- /* There may be more compressed data here. */
- if ((r = inflateEnd(stream)) != Z_OK) {
- log_warn(LD_BUG, "Error freeing gzip structures");
- goto err;
- }
- if (inflateInit2(stream,
- method_bits(method,HIGH_COMPRESSION)) != Z_OK) {
- log_warn(LD_GENERAL, "Error from second inflateInit2: %s",
- stream->msg?stream->msg:"");
- goto err;
- }
- break;
- case Z_OK:
- if (!complete_only && stream->avail_in == 0)
- goto done;
- /* In case zlib doesn't work as I think.... */
- if (stream->avail_out >= stream->avail_in+16)
- break;
- case Z_BUF_ERROR:
- if (stream->avail_out > 0) {
- log_fn(protocol_warn_level, LD_PROTOCOL,
- "possible truncated or corrupt zlib data");
- goto err;
- }
- offset = stream->next_out - (unsigned char*)*out;
- old_size = out_size;
- out_size *= 2;
- if (out_size < old_size) {
- log_warn(LD_GENERAL, "Size overflow in uncompression.");
- goto err;
- }
- if (tor_compress_is_compression_bomb(in_len, out_size)) {
- log_warn(LD_GENERAL, "Input looks like a possible zlib bomb; "
- "not proceeding.");
- goto err;
- }
- if (out_size >= SIZE_T_CEILING) {
- log_warn(LD_BUG, "Hit SIZE_T_CEILING limit while uncompressing.");
- goto err;
- }
- *out = tor_realloc(*out, out_size);
- stream->next_out = (unsigned char*)(*out + offset);
- if (out_size - offset > UINT_MAX) {
- log_warn(LD_BUG, "Ran over unsigned int limit of zlib while "
- "uncompressing.");
- goto err;
- }
- stream->avail_out = (unsigned int)(out_size - offset);
- break;
- default:
- log_warn(LD_GENERAL, "Gzip decompression returned an error: %s",
- stream->msg ? stream->msg : "");
- goto err;
- }
- }
- done:
- *out_len = stream->next_out - (unsigned char*)*out;
- r = inflateEnd(stream);
- tor_free(stream);
- if (r != Z_OK) {
- log_warn(LD_BUG, "Error freeing gzip structures");
- goto err;
- }
-
- /* NUL-terminate output. */
- if (out_size == *out_len)
- *out = tor_realloc(*out, out_size + 1);
- (*out)[*out_len] = '\0';
-
- return 0;
- err:
- if (stream) {
- inflateEnd(stream);
- tor_free(stream);
- }
- if (*out) {
- tor_free(*out);
- }
- return -1;
-}
-
/** Internal zlib state for an incremental compression/decompression.
* The body of this struct is not exposed. */
struct tor_zlib_compress_state_t {
@@ -416,7 +164,7 @@ tor_zlib_compress_new(int compress_,
if (! compress_) {
/* use this setting for decompression, since we might have the
* max number of window bits */
- compression_level = HIGH_COMPRESSION;
+ compression_level = BEST_COMPRESSION;
}
out = tor_malloc_zero(sizeof(tor_zlib_compress_state_t));
@@ -465,8 +213,11 @@ tor_zlib_compress_process(tor_zlib_compress_state_t *state,
{
int err;
tor_assert(state != NULL);
- tor_assert(*in_len <= UINT_MAX);
- tor_assert(*out_len <= UINT_MAX);
+ if (*in_len > UINT_MAX ||
+ *out_len > UINT_MAX) {
+ return TOR_COMPRESS_ERROR;
+ }
+
state->stream.next_in = (unsigned char*) *in;
state->stream.avail_in = (unsigned int)*in_len;
state->stream.next_out = (unsigned char*) *out;
diff --git a/src/common/compress_zlib.h b/src/common/compress_zlib.h
index 817ca2785b..df5c196ac7 100644
--- a/src/common/compress_zlib.h
+++ b/src/common/compress_zlib.h
@@ -17,16 +17,6 @@ const char *tor_zlib_get_version_str(void);
const char *tor_zlib_get_header_version_str(void);
-int tor_zlib_compress(char **out, size_t *out_len,
- const char *in, size_t in_len,
- compress_method_t method);
-
-int tor_zlib_uncompress(char **out, size_t *out_len,
- const char *in, size_t in_len,
- compress_method_t method,
- int complete_only,
- int protocol_warn_level);
-
/** Internal state for an incremental zlib/gzip compression/decompression. */
typedef struct tor_zlib_compress_state_t tor_zlib_compress_state_t;
diff --git a/src/common/compress_zstd.c b/src/common/compress_zstd.c
index 8725c10538..8b8aea1d01 100644
--- a/src/common/compress_zstd.c
+++ b/src/common/compress_zstd.c
@@ -33,6 +33,7 @@ memory_level(compression_level_t level)
{
switch (level) {
default:
+ case BEST_COMPRESSION:
case HIGH_COMPRESSION: return 9;
case MEDIUM_COMPRESSION: return 8;
case LOW_COMPRESSION: return 7;
@@ -85,289 +86,6 @@ tor_zstd_get_header_version_str(void)
#endif
}
-/** Given in_len bytes at in, compress them into a newly
- * allocated buffer, using the Zstandard method. Store the compressed string
- * in *out, and its length in *out_len. Return 0 on success, -1
- * on failure.
- */
-int
-tor_zstd_compress(char **out, size_t *out_len,
- const char *in, size_t in_len,
- compress_method_t method)
-{
-#ifdef HAVE_ZSTD
- ZSTD_CStream *stream = NULL;
- size_t out_size, old_size;
- size_t retval;
-
- tor_assert(out);
- tor_assert(out_len);
- tor_assert(in);
- tor_assert(in_len < UINT_MAX);
- tor_assert(method == ZSTD_METHOD);
-
- *out = NULL;
-
- stream = ZSTD_createCStream();
-
- if (stream == NULL) {
- // Zstandard does not give us any useful error message to why this
- // happened. See https://github.com/facebook/zstd/issues/398
- log_warn(LD_GENERAL, "Error while creating Zstandard stream");
- goto err;
- }
-
- retval = ZSTD_initCStream(stream,
- memory_level(HIGH_COMPRESSION));
-
- if (ZSTD_isError(retval)) {
- log_warn(LD_GENERAL, "Zstandard stream initialization error: %s",
- ZSTD_getErrorName(retval));
- goto err;
- }
-
- // Assume 50% compression and update our buffer in case we need to.
- out_size = in_len / 2;
- if (out_size < 1024)
- out_size = 1024;
-
- *out = tor_malloc(out_size);
- *out_len = 0;
-
- ZSTD_inBuffer input = { in, in_len, 0 };
- ZSTD_outBuffer output = { *out, out_size, 0 };
-
- while (input.pos < input.size) {
- retval = ZSTD_compressStream(stream, &output, &input);
-
- if (ZSTD_isError(retval)) {
- log_warn(LD_GENERAL, "Zstandard stream compression error: %s",
- ZSTD_getErrorName(retval));
- goto err;
- }
-
- if (input.pos < input.size && output.pos == output.size) {
- old_size = out_size;
- out_size *= 2;
-
- if (out_size < old_size) {
- log_warn(LD_GENERAL, "Size overflow in Zstandard compression.");
- goto err;
- }
-
- if (out_size - output.pos > UINT_MAX) {
- log_warn(LD_BUG, "Ran over unsigned int limit of Zstandard while "
- "compressing.");
- goto err;
- }
-
- output.dst = *out = tor_realloc(*out, out_size);
- output.size = out_size;
- }
- }
-
- while (1) {
- retval = ZSTD_endStream(stream, &output);
-
- if (retval == 0)
- break;
-
- if (ZSTD_isError(retval)) {
- log_warn(LD_GENERAL, "Zstandard stream error: %s",
- ZSTD_getErrorName(retval));
- goto err;
- }
-
- if (output.pos == output.size) {
- old_size = out_size;
- out_size *= 2;
-
- if (out_size < old_size) {
- log_warn(LD_GENERAL, "Size overflow in Zstandard compression.");
- goto err;
- }
-
- if (out_size - output.pos > UINT_MAX) {
- log_warn(LD_BUG, "Ran over unsigned int limit of Zstandard while "
- "compressing.");
- goto err;
- }
-
- output.dst = *out = tor_realloc(*out, out_size);
- output.size = out_size;
- }
- }
-
- *out_len = output.pos;
-
- if (tor_compress_is_compression_bomb(*out_len, in_len)) {
- log_warn(LD_BUG, "We compressed something and got an insanely high "
- "compression factor; other Tor instances would think "
- "this is a compression bomb.");
- goto err;
- }
-
- if (stream != NULL) {
- ZSTD_freeCStream(stream);
- }
-
- return 0;
-
- err:
- if (stream != NULL) {
- ZSTD_freeCStream(stream);
- }
-
- tor_free(*out);
- return -1;
-#else // HAVE_ZSTD.
- (void)out;
- (void)out_len;
- (void)in;
- (void)in_len;
- (void)method;
-
- return -1;
-#endif // HAVE_ZSTD.
-}
-
-/** Given a Zstandard compressed string of total length in_len bytes at
- * in, uncompress them into a newly allocated buffer. Store the
- * uncompressed string in *out, and its length in *out_len.
- * Return 0 on success, -1 on failure.
- *
- * If complete_only is true, we consider a truncated input as a failure;
- * otherwise we decompress as much as we can. Warn about truncated or corrupt
- * inputs at protocol_warn_level.
- */
-int
-tor_zstd_uncompress(char **out, size_t *out_len,
- const char *in, size_t in_len,
- compress_method_t method,
- int complete_only,
- int protocol_warn_level)
-{
-#ifdef HAVE_ZSTD
- ZSTD_DStream *stream = NULL;
- size_t retval;
- size_t out_size, old_size;
-
- tor_assert(out);
- tor_assert(out_len);
- tor_assert(in);
- tor_assert(in_len < UINT_MAX);
- tor_assert(method == ZSTD_METHOD);
-
- // FIXME(ahf): Handle this?
- (void)complete_only;
- (void)protocol_warn_level;
-
- *out = NULL;
-
- stream = ZSTD_createDStream();
-
- if (stream == NULL) {
- // Zstandard does not give us any useful error message to why this
- // happened. See https://github.com/facebook/zstd/issues/398
- log_warn(LD_GENERAL, "Error while creating Zstandard stream");
- goto err;
- }
-
- retval = ZSTD_initDStream(stream);
-
- if (ZSTD_isError(retval)) {
- log_warn(LD_GENERAL, "Zstandard stream initialization error: %s",
- ZSTD_getErrorName(retval));
- goto err;
- }
-
- out_size = in_len * 2;
- if (out_size < 1024)
- out_size = 1024;
-
- if (out_size >= SIZE_T_CEILING || out_size > UINT_MAX)
- goto err;
-
- *out = tor_malloc(out_size);
- *out_len = 0;
-
- ZSTD_inBuffer input = { in, in_len, 0 };
- ZSTD_outBuffer output = { *out, out_size, 0 };
-
- while (input.pos < input.size) {
- retval = ZSTD_decompressStream(stream, &output, &input);
-
- if (ZSTD_isError(retval)) {
- log_warn(LD_GENERAL, "Zstandard stream decompression error: %s",
- ZSTD_getErrorName(retval));
- goto err;
- }
-
- if (input.pos < input.size && output.pos == output.size) {
- old_size = out_size;
- out_size *= 2;
-
- if (out_size < old_size) {
- log_warn(LD_GENERAL, "Size overflow in Zstandard compression.");
- goto err;
- }
-
- if (tor_compress_is_compression_bomb(in_len, out_size)) {
- log_warn(LD_GENERAL, "Input looks like a possible Zstandard "
- "compression bomb. Not proceeding.");
- goto err;
- }
-
- if (out_size >= SIZE_T_CEILING) {
- log_warn(LD_BUG, "Hit SIZE_T_CEILING limit while uncompressing "
- "Zstandard data.");
- goto err;
- }
-
- if (out_size - output.pos > UINT_MAX) {
- log_warn(LD_BUG, "Ran over unsigned int limit of Zstandard while "
- "decompressing.");
- goto err;
- }
-
- output.dst = *out = tor_realloc(*out, out_size);
- output.size = out_size;
- }
- }
-
- *out_len = output.pos;
-
- if (stream != NULL) {
- ZSTD_freeDStream(stream);
- }
-
- // NUL-terminate our output.
- if (out_size == *out_len)
- *out = tor_realloc(*out, out_size + 1);
- (*out)[*out_len] = '\0';
-
- return 0;
-
- err:
- if (stream != NULL) {
- ZSTD_freeDStream(stream);
- }
-
- tor_free(*out);
- return -1;
-#else // HAVE_ZSTD.
- (void)out;
- (void)out_len;
- (void)in;
- (void)in_len;
- (void)method;
- (void)complete_only;
- (void)protocol_warn_level;
-
- return -1;
-#endif // HAVE_ZSTD.
-}
-
/** Internal Zstandard state for incremental compression/decompression.
* The body of this struct is not exposed. */
struct tor_zstd_compress_state_t {
diff --git a/src/common/compress_zstd.h b/src/common/compress_zstd.h
index 186dc3be9f..d3e65c2f16 100644
--- a/src/common/compress_zstd.h
+++ b/src/common/compress_zstd.h
@@ -17,16 +17,6 @@ const char *tor_zstd_get_version_str(void);
const char *tor_zstd_get_header_version_str(void);
-int tor_zstd_compress(char **out, size_t *out_len,
- const char *in, size_t in_len,
- compress_method_t method);
-
-int tor_zstd_uncompress(char **out, size_t *out_len,
- const char *in, size_t in_len,
- compress_method_t method,
- int complete_only,
- int protocol_warn_level);
-
/** Internal state for an incremental Zstandard compression/decompression. */
typedef struct tor_zstd_compress_state_t tor_zstd_compress_state_t;
diff --git a/src/test/test_util.c b/src/test/test_util.c
index 1e33de82ae..d47475a24c 100644
--- a/src/test/test_util.c
+++ b/src/test/test_util.c
@@ -2377,7 +2377,7 @@ test_util_gzip_compression_bomb(void *arg)
expect_single_log_msg_containing(
"We compressed something and got an insanely high "
"compression factor; other Tors would think this "
- "was a zlib bomb.");
+ "was a compression bomb.");
teardown_capture_of_logs();
/* Here's a compression bomb that we made manually. */