diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/common/compress.c | 52 | ||||
-rw-r--r-- | src/common/compress_none.c | 2 | ||||
-rw-r--r-- | src/common/sandbox.c | 10 | ||||
-rw-r--r-- | src/common/storagedir.c | 8 | ||||
-rw-r--r-- | src/or/buffers.c | 11 | ||||
-rw-r--r-- | src/or/channelpadding.c | 16 | ||||
-rw-r--r-- | src/or/connection_or.c | 4 | ||||
-rw-r--r-- | src/or/consdiffmgr.c | 24 | ||||
-rw-r--r-- | src/or/consdiffmgr.h | 3 | ||||
-rw-r--r-- | src/or/control.c | 2 | ||||
-rw-r--r-- | src/or/cpuworker.c | 2 | ||||
-rw-r--r-- | src/or/directory.c | 56 | ||||
-rw-r--r-- | src/or/dirserv.c | 6 | ||||
-rw-r--r-- | src/or/entrynodes.c | 36 | ||||
-rw-r--r-- | src/or/main.c | 4 | ||||
-rw-r--r-- | src/test/fuzz/include.am | 2 | ||||
-rw-r--r-- | src/test/test_channelpadding.c | 2 | ||||
-rw-r--r-- | src/test/test_util.c | 63 |
18 files changed, 249 insertions, 54 deletions
diff --git a/src/common/compress.c b/src/common/compress.c index e65894d9d2..7926faaa60 100644 --- a/src/common/compress.c +++ b/src/common/compress.c @@ -105,8 +105,8 @@ tor_compress_impl(int compress, if (stream == NULL) { log_warn(LD_GENERAL, "NULL stream while %scompressing", compress?"":"de"); - log_debug(LD_GENERAL, "method: %d level: %d at len: %zd", - method, compression_level, in_len); + log_debug(LD_GENERAL, "method: %d level: %d at len: %lu", + method, compression_level, (unsigned long)in_len); return -1; } @@ -146,17 +146,11 @@ tor_compress_impl(int compress, "Unexpected %s while %scompressing", complete_only?"end of input":"result", compress?"":"de"); - log_debug(LD_GENERAL, "method: %d level: %d at len: %zd", - method, compression_level, in_len); + log_debug(LD_GENERAL, "method: %d level: %d at len: %lu", + method, compression_level, (unsigned long)in_len); goto err; } else { - if (in_len != 0) { - log_fn(protocol_warn_level, LD_PROTOCOL, - "Unexpected extra input while decompressing"); - log_debug(LD_GENERAL, "method: %d level: %d at len: %zd", - method, compression_level, in_len); - goto err; - } else { + if (in_len == 0) { goto done; } } @@ -548,28 +542,42 @@ tor_compress_process(tor_compress_state_t *state, int finish) { tor_assert(state != NULL); + const size_t in_len_orig = *in_len; + const size_t out_len_orig = *out_len; + tor_compress_output_t rv; switch (state->method) { case GZIP_METHOD: case ZLIB_METHOD: - return tor_zlib_compress_process(state->u.zlib_state, - out, out_len, in, in_len, - finish); + rv = tor_zlib_compress_process(state->u.zlib_state, + out, out_len, in, in_len, + finish); + break; case LZMA_METHOD: - return tor_lzma_compress_process(state->u.lzma_state, - out, out_len, in, in_len, - finish); + rv =tor_lzma_compress_process(state->u.lzma_state, + out, out_len, in, in_len, + finish); + break; case ZSTD_METHOD: - return tor_zstd_compress_process(state->u.zstd_state, - out, out_len, in, in_len, - finish); + rv = tor_zstd_compress_process(state->u.zstd_state, + out, out_len, in, in_len, + finish); + break; case NO_METHOD: - return tor_cnone_compress_process(out, out_len, in, in_len, - finish); + rv = tor_cnone_compress_process(out, out_len, in, in_len, + finish); + break; + default: case UNKNOWN_METHOD: goto err; } + if (BUG((rv == TOR_COMPRESS_OK) && + *in_len == in_len_orig && + *out_len == out_len_orig)) { + return TOR_COMPRESS_ERROR; + } + return rv; err: return TOR_COMPRESS_ERROR; } diff --git a/src/common/compress_none.c b/src/common/compress_none.c index b76e6010ec..34314e4af7 100644 --- a/src/common/compress_none.c +++ b/src/common/compress_none.c @@ -4,7 +4,7 @@ /* See LICENSE for licensing information */ /** - * \file compress_lzma.c + * \file compress_none.c * \brief Compression backend for identity compression. * * We actually define this backend so that we can treat the identity transform diff --git a/src/common/sandbox.c b/src/common/sandbox.c index aae0705af4..52caa4fcc6 100644 --- a/src/common/sandbox.c +++ b/src/common/sandbox.c @@ -19,8 +19,14 @@ #define _LARGEFILE64_SOURCE #endif -/** Malloc mprotect limit in bytes. */ -#define MALLOC_MP_LIM (16*1024*1024) +/** Malloc mprotect limit in bytes. + * + * 28/06/2017: This value was increased from 16 MB to 20 MB after we introduced + * LZMA support in Tor (0.3.1.1-alpha). We limit our LZMA coder to 16 MB, but + * liblzma have a small overhead that we need to compensate for to avoid being + * killed by the sandbox. + */ +#define MALLOC_MP_LIM (20*1024*1024) #include <stdio.h> #include <string.h> diff --git a/src/common/storagedir.c b/src/common/storagedir.c index befcfe693f..31933f64c2 100644 --- a/src/common/storagedir.c +++ b/src/common/storagedir.c @@ -119,7 +119,8 @@ storage_dir_clean_tmpfiles(storage_dir_t *d) char *path = NULL; tor_asprintf(&path, "%s/%s", d->directory, fname); if (unlink(sandbox_intern_string(path))) { - log_warn(LD_FS, "Unable to unlink %s", escaped(path)); + log_warn(LD_FS, "Unable to unlink %s while cleaning " + "temporary files: %s", escaped(path), strerror(errno)); tor_free(path); continue; } @@ -210,7 +211,9 @@ storage_dir_read(storage_dir_t *d, const char *fname, int bin, size_t *sz_out) char *contents = read_file_to_str(path, flags, &st); if (contents && sz_out) { // it fits in RAM, so we know its size is less than SIZE_MAX +#if UINT64_MAX > SIZE_MAX tor_assert((uint64_t)st.st_size <= SIZE_MAX); +#endif *sz_out = (size_t) st.st_size; } @@ -453,7 +456,8 @@ storage_dir_remove_file(storage_dir_t *d, if (unlink(ipath) == 0) { storage_dir_reduce_usage(d, size); } else { - log_warn(LD_FS, "Unable to unlink %s", escaped(path)); + log_warn(LD_FS, "Unable to unlink %s while removing file: %s", + escaped(path), strerror(errno)); tor_free(path); return; } diff --git a/src/or/buffers.c b/src/or/buffers.c index 3692ed4d08..12a6c0239b 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -2092,7 +2092,7 @@ fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len) int write_to_buf_compress(buf_t *buf, tor_compress_state_t *state, const char *data, size_t data_len, - int done) + const int done) { char *next; size_t old_avail, avail; @@ -2114,8 +2114,10 @@ write_to_buf_compress(buf_t *buf, tor_compress_state_t *state, case TOR_COMPRESS_ERROR: return -1; case TOR_COMPRESS_OK: - if (data_len == 0) + if (data_len == 0) { + tor_assert_nonfatal(!done); over = 1; + } break; case TOR_COMPRESS_BUFFER_FULL: if (avail) { @@ -2124,6 +2126,11 @@ write_to_buf_compress(buf_t *buf, tor_compress_state_t *state, * whether were going to or not. */ need_new_chunk = 1; } + if (data_len == 0 && !done) { + /* We've consumed all the input data, though, so there's no + * point in forging ahead right now. */ + over = 1; + } break; } buf->datalen += old_avail - avail; diff --git a/src/or/channelpadding.c b/src/or/channelpadding.c index f5248e8960..bed2489837 100644 --- a/src/or/channelpadding.c +++ b/src/or/channelpadding.c @@ -530,10 +530,20 @@ channelpadding_compute_time_until_pad_for_netflow(channel_t *chan) >= chan->next_padding_time_ms) { int64_t ms_until_pad_for_netflow = chan->next_padding_time_ms - long_now; + /* If the padding time is in the past, that means that libevent delayed + * calling the once-per-second callback due to other work taking too long. + * See https://bugs.torproject.org/22212 and + * https://bugs.torproject.org/16585. This is a systemic problem + * with being single-threaded, but let's emit a notice if this + * is long enough in the past that we might have missed a netflow window, + * and allowed a router to emit a netflow frame, just so we don't forget + * about it entirely.. */ +#define NETFLOW_MISSED_WINDOW (150000 - DFLT_NETFLOW_INACTIVE_KEEPALIVE_HIGH) if (ms_until_pad_for_netflow < 0) { - log_warn(LD_BUG, - "Channel padding timeout scheduled "I64_FORMAT"ms in the past. " - "Did the monotonic clock just jump?", + int severity = (ms_until_pad_for_netflow < -NETFLOW_MISSED_WINDOW) + ? LOG_NOTICE : LOG_INFO; + log_fn(severity, LD_OR, + "Channel padding timeout scheduled "I64_FORMAT"ms in the past. ", I64_PRINTF_ARG(-ms_until_pad_for_netflow)); return 0; /* Clock jumped: Send padding now */ } diff --git a/src/or/connection_or.c b/src/or/connection_or.c index ab0f411cc5..753148291c 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -420,9 +420,11 @@ cell_pack(packed_cell_t *dst, const cell_t *src, int wide_circ_ids) set_uint32(dest, htonl(src->circ_id)); dest += 4; } else { + /* Clear the last two bytes of dest, in case we can accidentally + * send them to the network somehow. */ + memset(dest+CELL_MAX_NETWORK_SIZE-2, 0, 2); set_uint16(dest, htons(src->circ_id)); dest += 2; - memset(dest+CELL_MAX_NETWORK_SIZE-2, 0, 2); /*make sure it's clear */ } set_uint8(dest, src->command); memcpy(dest+1, src->payload, CELL_PAYLOAD_SIZE); diff --git a/src/or/consdiffmgr.c b/src/or/consdiffmgr.c index 2af104733b..638fcd6794 100644 --- a/src/or/consdiffmgr.c +++ b/src/or/consdiffmgr.c @@ -325,7 +325,8 @@ cdm_diff_ht_purge(consensus_flavor_t flav, if ((*diff)->cdm_diff_status == CDM_DIFF_PRESENT && flav == (*diff)->flavor) { - if (consensus_cache_entry_handle_get((*diff)->entry) == NULL) { + if (BUG((*diff)->entry == NULL) || + consensus_cache_entry_handle_get((*diff)->entry) == NULL) { /* the underlying entry has gone away; drop this. */ next = HT_NEXT_RMV(cdm_diff_ht, &cdm_diff_ht, diff); cdm_diff_free(this); @@ -622,6 +623,9 @@ consdiffmgr_find_diff_from(consensus_cache_entry_t **entry_out, return CONSDIFF_IN_PROGRESS; } + if (BUG(ent->entry == NULL)) { + return CONSDIFF_NOT_FOUND; + } *entry_out = consensus_cache_entry_handle_get(ent->entry); return (*entry_out) ? CONSDIFF_AVAILABLE : CONSDIFF_NOT_FOUND; @@ -1840,3 +1844,21 @@ consensus_cache_entry_get_valid_until(const consensus_cache_entry_t *ent, return 0; } +/** Read the valid after timestamp from the cached object <b>ent</b> into + * *<b>out</b> and return 0, or return -1 if no such time was recorded. */ +int +consensus_cache_entry_get_valid_after(const consensus_cache_entry_t *ent, + time_t *out) +{ + tor_assert(ent); + tor_assert(out); + + const char *s; + s = consensus_cache_entry_get_value(ent, LABEL_VALID_AFTER); + + if (s == NULL || parse_iso_time_nospace(s, out) < 0) + return -1; + else + return 0; +} + diff --git a/src/or/consdiffmgr.h b/src/or/consdiffmgr.h index fe4f9ee239..079f9fe2d2 100644 --- a/src/or/consdiffmgr.h +++ b/src/or/consdiffmgr.h @@ -44,6 +44,9 @@ int consensus_cache_entry_get_fresh_until( int consensus_cache_entry_get_valid_until( const struct consensus_cache_entry_t *ent, time_t *out); +int consensus_cache_entry_get_valid_after( + const struct consensus_cache_entry_t *ent, + time_t *out); void consdiffmgr_rescan(void); int consdiffmgr_cleanup(void); diff --git a/src/or/control.c b/src/or/control.c index 9454a7ab67..9bcf1ee364 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -6506,7 +6506,7 @@ monitor_owning_controller_process(const char *process_spec) msg); owning_controller_process_spec = NULL; tor_cleanup(); - exit(0); + exit(1); } } diff --git a/src/or/cpuworker.c b/src/or/cpuworker.c index af79fafaa6..1013fa555e 100644 --- a/src/or/cpuworker.c +++ b/src/or/cpuworker.c @@ -475,7 +475,7 @@ queue_pending_tasks(void) return; if (assign_onionskin_to_cpuworker(circ, onionskin)) - log_warn(LD_OR,"assign_to_cpuworker failed. Ignoring."); + log_info(LD_OR,"assign_to_cpuworker failed. Ignoring."); } } diff --git a/src/or/directory.c b/src/or/directory.c index ac40e54ceb..45fbd1dd33 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -3920,6 +3920,30 @@ find_best_compression_method(unsigned compression_methods, int stream) return NO_METHOD; } +/** Check if any of the digests in <b>digests</b> matches the latest consensus + * flavor (given in <b>flavor</b>) that we have available. */ +static int +digest_list_contains_best_consensus(consensus_flavor_t flavor, + const smartlist_t *digests) +{ + const networkstatus_t *ns = NULL; + + if (digests == NULL) + return 0; + + ns = networkstatus_get_latest_consensus_by_flavor(flavor); + + if (ns == NULL) + return 0; + + SMARTLIST_FOREACH_BEGIN(digests, const uint8_t *, digest) { + if (tor_memeq(ns->digest_sha3_as_signed, digest, DIGEST256_LEN)) + return 1; + } SMARTLIST_FOREACH_END(digest); + + return 0; +} + /** Check if the given compression method is allowed for a connection that is * supposed to be anonymous. Returns 1 if the compression method is allowed, * otherwise 0. */ @@ -4089,6 +4113,13 @@ handle_get_current_consensus(dir_connection_t *conn, goto done; } + if (digest_list_contains_best_consensus(req.flav, + req.diff_from_digests)) { + write_http_status_line(conn, 304, "Not modified"); + geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED); + goto done; + } + struct consensus_cache_entry_t *cached_consensus = NULL; compress_method_t compression_used = NO_METHOD; @@ -4227,13 +4258,14 @@ static int handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) { const char *url = args->url; - const compress_method_t compress_method = - find_best_compression_method(args->compression_supported, 1); { int current; ssize_t body_len = 0; ssize_t estimated_len = 0; + /* This smartlist holds strings that we can compress on the fly. */ smartlist_t *items = smartlist_new(); + /* This smartlist holds cached_dir_t objects that have a precompressed + * deflated version. */ smartlist_t *dir_items = smartlist_new(); int lifetime = 60; /* XXXX?? should actually use vote intervals. */ url += strlen("/tor/status-vote/"); @@ -4284,6 +4316,26 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) write_http_status_line(conn, 404, "Not found"); goto vote_done; } + + /* We're sending items from at most one kind of source */ + tor_assert_nonfatal(smartlist_len(items) == 0 || + smartlist_len(dir_items) == 0); + + int streaming; + unsigned mask; + if (smartlist_len(items)) { + /* We're taking strings and compressing them on the fly. */ + streaming = 1; + mask = ~0u; + } else { + /* We're taking cached_dir_t objects. We only have them uncompressed + * or deflated. */ + streaming = 0; + mask = (1u<<NO_METHOD) | (1u<<ZLIB_METHOD); + } + const compress_method_t compress_method = find_best_compression_method( + args->compression_supported&mask, streaming); + SMARTLIST_FOREACH(dir_items, cached_dir_t *, d, body_len += compress_method != NO_METHOD ? d->dir_compressed_len : d->dir_len); diff --git a/src/or/dirserv.c b/src/or/dirserv.c index 408f58b22b..4954471c6a 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -14,6 +14,7 @@ #include "connection.h" #include "connection_or.h" #include "conscache.h" +#include "consdiffmgr.h" #include "control.h" #include "directory.h" #include "dirserv.h" @@ -3518,6 +3519,11 @@ spooled_resource_estimate_size(const spooled_resource_t *spooled, } else { cached_dir_t *cached; if (spooled->consensus_cache_entry) { + if (published_out) { + consensus_cache_entry_get_valid_after( + spooled->consensus_cache_entry, published_out); + } + return spooled->cce_len; } if (spooled->cached_dir_ref) { diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index dc2cab28f7..be9f85a89f 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -1093,6 +1093,18 @@ select_and_add_guard_item_for_sample(guard_selection_t *gs, return added_guard; } +/** Return true iff we need a consensus to maintain our */ +static int +live_consensus_is_missing(const guard_selection_t *gs) +{ + tor_assert(gs); + if (gs->type == GS_TYPE_BRIDGE) { + /* We don't update bridges from the consensus; they aren't there. */ + return 0; + } + return networkstatus_get_live_consensus(approx_time()) == NULL; +} + /** * Add new guards to the sampled guards in <b>gs</b> until there are * enough usable filtered guards, but never grow the sample beyond its @@ -1104,6 +1116,13 @@ entry_guards_expand_sample(guard_selection_t *gs) { tor_assert(gs); const or_options_t *options = get_options(); + + if (live_consensus_is_missing(gs)) { + log_info(LD_GUARD, "Not expanding the sample guard set; we have " + "no live consensus."); + return NULL; + } + int n_sampled = smartlist_len(gs->sampled_entry_guards); entry_guard_t *added_guard = NULL; int n_usable_filtered_guards = num_reachable_filtered_guards(gs, NULL); @@ -1212,18 +1231,13 @@ sampled_guards_update_from_consensus(guard_selection_t *gs) // It's important to use only a live consensus here; we don't want to // make changes based on anything expired or old. - if (gs->type != GS_TYPE_BRIDGE) { - networkstatus_t *ns = networkstatus_get_live_consensus(approx_time()); - - if (! ns) { - log_info(LD_GUARD, "No live consensus; can't update " - "sampled entry guards."); - return; - } else { - log_info(LD_GUARD, "Updating sampled guard status based on received " - "consensus."); - } + if (live_consensus_is_missing(gs)) { + log_info(LD_GUARD, "Not updating the sample guard set; we have " + "no live consensus."); + return; } + log_info(LD_GUARD, "Updating sampled guard status based on received " + "consensus."); int n_changes = 0; diff --git a/src/or/main.c b/src/or/main.c index 9699c8d381..cb24fd18c8 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -1556,7 +1556,7 @@ check_ed_keys_callback(time_t now, const or_options_t *options) generate_ed_link_cert(options, now, new_signing_key > 0)) { log_err(LD_OR, "Unable to update Ed25519 keys! Exiting."); tor_cleanup(); - exit(0); + exit(1); } } return 30; @@ -3168,7 +3168,7 @@ try_locking(const or_options_t *options, int err_if_locked) r = try_locking(options, 0); if (r<0) { log_err(LD_GENERAL, "No, it's still there. Exiting."); - exit(0); + exit(1); } return r; } diff --git a/src/test/fuzz/include.am b/src/test/fuzz/include.am index 6008238bba..2961dab56f 100644 --- a/src/test/fuzz/include.am +++ b/src/test/fuzz/include.am @@ -36,7 +36,7 @@ oss-fuzz-prereqs: \ noinst_HEADERS += \ src/test/fuzz/fuzzing.h -LIBFUZZER = /home/nickm/build/libfuzz/libFuzzer.a +LIBFUZZER = -lFuzzer LIBFUZZER_CPPFLAGS = $(FUZZING_CPPFLAGS) -DLLVM_FUZZ LIBFUZZER_CFLAGS = $(FUZZING_CFLAGS) LIBFUZZER_LDFLAG = $(FUZZING_LDFLAG) diff --git a/src/test/test_channelpadding.c b/src/test/test_channelpadding.c index 0a70184f86..3b889991b6 100644 --- a/src/test/test_channelpadding.c +++ b/src/test/test_channelpadding.c @@ -795,8 +795,6 @@ test_channelpadding_decide_to_pad_channel(void *arg) tried_to_write_cell = 0; chan->next_padding_time_ms = monotime_coarse_absolute_msec() - 100; decision = channelpadding_decide_to_pad_channel(chan); - expect_log_msg("Channel padding timeout scheduled 100ms in the past. " - "Did the monotonic clock just jump?\n"); tt_int_op(decision, OP_EQ, CHANNELPADDING_PADDING_SENT); tt_int_op(tried_to_write_cell, OP_EQ, 1); tt_assert(!chan->pending_padding_callback); diff --git a/src/test/test_util.c b/src/test/test_util.c index 0d9dd974df..7db93324d1 100644 --- a/src/test/test_util.c +++ b/src/test/test_util.c @@ -2399,6 +2399,59 @@ test_util_compress(void *arg) } static void +test_util_decompress_concatenated_impl(compress_method_t method) +{ + char input[4096]; + char *c1 = NULL, *c2 = NULL, *c3 = NULL; + char *result = NULL; + size_t sz1, sz2, sz3, szr; + int r; + + crypto_rand(input, sizeof(input)); + + /* Compress the input in two chunks. */ + r = tor_compress(&c1, &sz1, input, 2048, method); + tt_int_op(r, OP_EQ, 0); + r = tor_compress(&c2, &sz2, input+2048, 2048, method); + tt_int_op(r, OP_EQ, 0); + + /* concatenate the chunks. */ + sz3 = sz1 + sz2; + c3 = tor_malloc(sz3); + memcpy(c3, c1, sz1); + memcpy(c3+sz1, c2, sz2); + + /* decompress the concatenated result */ + r = tor_uncompress(&result, &szr, c3, sz3, method, 0, LOG_WARN); + tt_int_op(r, OP_EQ, 0); + tt_int_op(szr, OP_EQ, sizeof(input)); + tt_mem_op(result, OP_EQ, input, sizeof(input)); + + done: + tor_free(c1); + tor_free(c2); + tor_free(c3); + tor_free(result); +} + +static void +test_util_decompress_concatenated(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_concatenated_impl(method); + done: + ; +} + +static void test_util_gzip_compression_bomb(void *arg) { /* A 'compression bomb' is a very small object that uncompresses to a huge @@ -5819,6 +5872,11 @@ test_util_get_unquoted_path(void *arg) { "compress/" #name, test_util_compress, 0, &passthrough_setup, \ (char*)(identifier) } +#define COMPRESS_CONCAT(name, identifier) \ + { "compress_concat/" #name, test_util_decompress_concatenated, 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)) @@ -5848,6 +5906,11 @@ struct testcase_t util_tests[] = { COMPRESS(lzma, "x-tor-lzma"), COMPRESS(zstd, "x-zstd"), COMPRESS(none, "identity"), + COMPRESS_CONCAT(zlib, "deflate"), + COMPRESS_CONCAT(gzip, "gzip"), + COMPRESS_CONCAT(lzma, "x-tor-lzma"), + COMPRESS_CONCAT(zstd, "x-zstd"), + COMPRESS_CONCAT(none, "identity"), UTIL_TEST(gzip_compression_bomb, TT_FORK), UTIL_LEGACY(datadir), UTIL_LEGACY(memarea), |