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. */