Merge branch 'tor-gitlab/mr/568'
[tor.git] / src / lib / compress / compress_lzma.c
blob7e5c1a28835b7aee5a81158dad7959ede07e5c3d
1 /* Copyright (c) 2004, Roger Dingledine.
2 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
3 * Copyright (c) 2007-2021, The Tor Project, Inc. */
4 /* See LICENSE for licensing information */
6 /**
7 * \file compress_lzma.c
8 * \brief Compression backend for LZMA.
10 * This module should never be invoked directly. Use the compress module
11 * instead.
12 **/
14 #include "orconfig.h"
16 #include "lib/compress/compress.h"
17 #include "lib/compress/compress_lzma.h"
18 #include "lib/log/log.h"
19 #include "lib/log/util_bug.h"
20 #include "lib/malloc/malloc.h"
21 #include "lib/thread/threads.h"
23 #ifdef HAVE_LZMA
24 #include <lzma.h>
25 #endif
27 /** The maximum amount of memory we allow the LZMA decoder to use, in bytes. */
28 #define MEMORY_LIMIT (16 * 1024 * 1024)
30 /** Total number of bytes allocated for LZMA state. */
31 static atomic_counter_t total_lzma_allocation;
33 #ifdef HAVE_LZMA
34 /** Given <b>level</b> return the memory level. */
35 static int
36 memory_level(compression_level_t level)
38 switch (level) {
39 default:
40 case BEST_COMPRESSION:
41 case HIGH_COMPRESSION: return 6;
42 case MEDIUM_COMPRESSION: return 4;
43 case LOW_COMPRESSION: return 2;
47 /** Convert a given <b>error</b> to a human readable error string. */
48 static const char *
49 lzma_error_str(lzma_ret error)
51 switch (error) {
52 case LZMA_OK:
53 return "Operation completed successfully";
54 case LZMA_STREAM_END:
55 return "End of stream";
56 case LZMA_NO_CHECK:
57 return "Input stream lacks integrity check";
58 case LZMA_UNSUPPORTED_CHECK:
59 return "Unable to calculate integrity check";
60 case LZMA_GET_CHECK:
61 return "Integrity check available";
62 case LZMA_MEM_ERROR:
63 return "Unable to allocate memory";
64 case LZMA_MEMLIMIT_ERROR:
65 return "Memory limit reached";
66 case LZMA_FORMAT_ERROR:
67 return "Unknown file format";
68 case LZMA_OPTIONS_ERROR:
69 return "Unsupported options";
70 case LZMA_DATA_ERROR:
71 return "Corrupt input data";
72 case LZMA_BUF_ERROR:
73 return "Unable to progress";
74 case LZMA_PROG_ERROR:
75 return "Programming error";
76 default:
77 return "Unknown LZMA error";
80 #endif /* defined(HAVE_LZMA) */
82 /** Return 1 if LZMA compression is supported; otherwise 0. */
83 int
84 tor_lzma_method_supported(void)
86 #ifdef HAVE_LZMA
87 return 1;
88 #else
89 return 0;
90 #endif
93 /** Return a string representation of the version of the currently running
94 * version of liblzma. Returns NULL if LZMA is unsupported. */
95 const char *
96 tor_lzma_get_version_str(void)
98 #ifdef HAVE_LZMA
99 return lzma_version_string();
100 #else
101 return NULL;
102 #endif
105 /** Return a string representation of the version of liblzma used at
106 * compilation time. Returns NULL if LZMA is unsupported. */
107 const char *
108 tor_lzma_get_header_version_str(void)
110 #ifdef HAVE_LZMA
111 return LZMA_VERSION_STRING;
112 #else
113 return NULL;
114 #endif
117 /** Internal LZMA state for incremental compression/decompression.
118 * The body of this struct is not exposed. */
119 struct tor_lzma_compress_state_t {
120 #ifdef HAVE_LZMA
121 lzma_stream stream; /**< The LZMA stream. */
122 #endif
124 int compress; /**< True if we are compressing; false if we are inflating */
126 /** Number of bytes read so far. Used to detect compression bombs. */
127 size_t input_so_far;
128 /** Number of bytes written so far. Used to detect compression bombs. */
129 size_t output_so_far;
131 /** Approximate number of bytes allocated for this object. */
132 size_t allocation;
135 #ifdef HAVE_LZMA
136 /** Return an approximate number of bytes stored in memory to hold the LZMA
137 * encoder/decoder state. */
138 static size_t
139 tor_lzma_state_size_precalc(int compress, compression_level_t level)
141 uint64_t memory_usage;
143 if (compress)
144 memory_usage = lzma_easy_encoder_memusage(memory_level(level));
145 else
146 memory_usage = lzma_easy_decoder_memusage(memory_level(level));
148 if (memory_usage == UINT64_MAX) {
149 // LCOV_EXCL_START
150 log_warn(LD_GENERAL, "Unsupported compression level passed to LZMA %s",
151 compress ? "encoder" : "decoder");
152 goto err;
153 // LCOV_EXCL_STOP
156 if (memory_usage + sizeof(tor_lzma_compress_state_t) > SIZE_MAX)
157 memory_usage = SIZE_MAX;
158 else
159 memory_usage += sizeof(tor_lzma_compress_state_t);
161 return (size_t)memory_usage;
163 // LCOV_EXCL_START
164 err:
165 return 0;
166 // LCOV_EXCL_STOP
168 #endif /* defined(HAVE_LZMA) */
170 /** Construct and return a tor_lzma_compress_state_t object using
171 * <b>method</b>. If <b>compress</b>, it's for compression; otherwise it's for
172 * decompression. */
173 tor_lzma_compress_state_t *
174 tor_lzma_compress_new(int compress,
175 compress_method_t method,
176 compression_level_t level)
178 tor_assert(method == LZMA_METHOD);
180 #ifdef HAVE_LZMA
181 tor_lzma_compress_state_t *result;
182 lzma_ret retval;
183 lzma_options_lzma stream_options;
185 // Note that we do not explicitly initialize the lzma_stream object here,
186 // since the LZMA_STREAM_INIT "just" initializes all members to 0, which is
187 // also what `tor_malloc_zero()` does.
188 result = tor_malloc_zero(sizeof(tor_lzma_compress_state_t));
189 result->compress = compress;
190 result->allocation = tor_lzma_state_size_precalc(compress, level);
192 if (compress) {
193 lzma_lzma_preset(&stream_options, memory_level(level));
195 retval = lzma_alone_encoder(&result->stream, &stream_options);
197 if (retval != LZMA_OK) {
198 // LCOV_EXCL_START
199 log_warn(LD_GENERAL, "Error from LZMA encoder: %s (%u).",
200 lzma_error_str(retval), retval);
201 goto err;
202 // LCOV_EXCL_STOP
204 } else {
205 retval = lzma_alone_decoder(&result->stream, MEMORY_LIMIT);
207 if (retval != LZMA_OK) {
208 // LCOV_EXCL_START
209 log_warn(LD_GENERAL, "Error from LZMA decoder: %s (%u).",
210 lzma_error_str(retval), retval);
211 goto err;
212 // LCOV_EXCL_STOP
216 atomic_counter_add(&total_lzma_allocation, result->allocation);
217 return result;
219 /* LCOV_EXCL_START */
220 err:
221 tor_free(result);
222 return NULL;
223 /* LCOV_EXCL_STOP */
224 #else /* !defined(HAVE_LZMA) */
225 (void)compress;
226 (void)method;
227 (void)level;
229 return NULL;
230 #endif /* defined(HAVE_LZMA) */
233 /** Compress/decompress some bytes using <b>state</b>. Read up to
234 * *<b>in_len</b> bytes from *<b>in</b>, and write up to *<b>out_len</b> bytes
235 * to *<b>out</b>, adjusting the values as we go. If <b>finish</b> is true,
236 * we've reached the end of the input.
238 * Return TOR_COMPRESS_DONE if we've finished the entire
239 * compression/decompression.
240 * Return TOR_COMPRESS_OK if we're processed everything from the input.
241 * Return TOR_COMPRESS_BUFFER_FULL if we're out of space on <b>out</b>.
242 * Return TOR_COMPRESS_ERROR if the stream is corrupt.
244 tor_compress_output_t
245 tor_lzma_compress_process(tor_lzma_compress_state_t *state,
246 char **out, size_t *out_len,
247 const char **in, size_t *in_len,
248 int finish)
250 #ifdef HAVE_LZMA
251 lzma_ret retval;
252 lzma_action action;
254 tor_assert(state != NULL);
255 tor_assert(*in_len <= UINT_MAX);
256 tor_assert(*out_len <= UINT_MAX);
258 state->stream.next_in = (unsigned char *)*in;
259 state->stream.avail_in = *in_len;
260 state->stream.next_out = (unsigned char *)*out;
261 state->stream.avail_out = *out_len;
263 action = finish ? LZMA_FINISH : LZMA_RUN;
265 retval = lzma_code(&state->stream, action);
267 state->input_so_far += state->stream.next_in - ((unsigned char *)*in);
268 state->output_so_far += state->stream.next_out - ((unsigned char *)*out);
270 *out = (char *)state->stream.next_out;
271 *out_len = state->stream.avail_out;
272 *in = (const char *)state->stream.next_in;
273 *in_len = state->stream.avail_in;
275 if (! state->compress &&
276 tor_compress_is_compression_bomb(state->input_so_far,
277 state->output_so_far)) {
278 log_warn(LD_DIR, "Possible compression bomb; abandoning stream.");
279 return TOR_COMPRESS_ERROR;
282 switch (retval) {
283 case LZMA_OK:
284 if (state->stream.avail_out == 0 || finish)
285 return TOR_COMPRESS_BUFFER_FULL;
287 return TOR_COMPRESS_OK;
289 case LZMA_BUF_ERROR:
290 if (state->stream.avail_in == 0 && !finish)
291 return TOR_COMPRESS_OK;
293 return TOR_COMPRESS_BUFFER_FULL;
295 case LZMA_STREAM_END:
296 return TOR_COMPRESS_DONE;
298 // We list all the possible values of `lzma_ret` here to silence the
299 // `switch-enum` warning and to detect if a new member was added.
300 case LZMA_NO_CHECK:
301 case LZMA_UNSUPPORTED_CHECK:
302 case LZMA_GET_CHECK:
303 case LZMA_MEM_ERROR:
304 case LZMA_MEMLIMIT_ERROR:
305 case LZMA_FORMAT_ERROR:
306 case LZMA_OPTIONS_ERROR:
307 case LZMA_DATA_ERROR:
308 case LZMA_PROG_ERROR:
309 default:
310 log_warn(LD_GENERAL, "LZMA %s didn't finish: %s.",
311 state->compress ? "compression" : "decompression",
312 lzma_error_str(retval));
313 return TOR_COMPRESS_ERROR;
315 #else /* !defined(HAVE_LZMA) */
316 (void)state;
317 (void)out;
318 (void)out_len;
319 (void)in;
320 (void)in_len;
321 (void)finish;
322 return TOR_COMPRESS_ERROR;
323 #endif /* defined(HAVE_LZMA) */
326 /** Deallocate <b>state</b>. */
327 void
328 tor_lzma_compress_free_(tor_lzma_compress_state_t *state)
330 if (state == NULL)
331 return;
333 atomic_counter_sub(&total_lzma_allocation, state->allocation);
335 #ifdef HAVE_LZMA
336 lzma_end(&state->stream);
337 #endif
339 tor_free(state);
342 /** Return the approximate number of bytes allocated for <b>state</b>. */
343 size_t
344 tor_lzma_compress_state_size(const tor_lzma_compress_state_t *state)
346 tor_assert(state != NULL);
347 return state->allocation;
350 /** Return the approximate number of bytes allocated for all LZMA states. */
351 size_t
352 tor_lzma_get_total_allocation(void)
354 return atomic_counter_get(&total_lzma_allocation);
357 /** Initialize the lzma module */
358 void
359 tor_lzma_init(void)
361 atomic_counter_init(&total_lzma_allocation);