Line data Source code
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 */
5 :
6 : /**
7 : * \file compress_lzma.c
8 : * \brief Compression backend for LZMA.
9 : *
10 : * This module should never be invoked directly. Use the compress module
11 : * instead.
12 : **/
13 :
14 : #include "orconfig.h"
15 :
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"
22 :
23 : #ifdef HAVE_LZMA
24 : #include <lzma.h>
25 : #endif
26 :
27 : /** The maximum amount of memory we allow the LZMA decoder to use, in bytes. */
28 : #define MEMORY_LIMIT (16 * 1024 * 1024)
29 :
30 : /** Total number of bytes allocated for LZMA state. */
31 : static atomic_counter_t total_lzma_allocation;
32 :
33 : #ifdef HAVE_LZMA
34 : /** Given <b>level</b> return the memory level. */
35 : static int
36 150 : memory_level(compression_level_t level)
37 : {
38 150 : switch (level) {
39 : default:
40 : case BEST_COMPRESSION:
41 : case HIGH_COMPRESSION: return 6;
42 8 : case MEDIUM_COMPRESSION: return 4;
43 8 : case LOW_COMPRESSION: return 2;
44 : }
45 : }
46 :
47 : /** Convert a given <b>error</b> to a human readable error string. */
48 : static const char *
49 2 : lzma_error_str(lzma_ret error)
50 : {
51 2 : switch (error) {
52 : case LZMA_OK:
53 : return "Operation completed successfully";
54 0 : case LZMA_STREAM_END:
55 0 : return "End of stream";
56 0 : case LZMA_NO_CHECK:
57 0 : return "Input stream lacks integrity check";
58 0 : case LZMA_UNSUPPORTED_CHECK:
59 0 : return "Unable to calculate integrity check";
60 0 : case LZMA_GET_CHECK:
61 0 : return "Integrity check available";
62 0 : case LZMA_MEM_ERROR:
63 0 : return "Unable to allocate memory";
64 0 : case LZMA_MEMLIMIT_ERROR:
65 0 : return "Memory limit reached";
66 1 : case LZMA_FORMAT_ERROR:
67 1 : return "Unknown file format";
68 0 : case LZMA_OPTIONS_ERROR:
69 0 : return "Unsupported options";
70 1 : case LZMA_DATA_ERROR:
71 1 : return "Corrupt input data";
72 0 : case LZMA_BUF_ERROR:
73 0 : return "Unable to progress";
74 0 : case LZMA_PROG_ERROR:
75 0 : return "Programming error";
76 0 : default:
77 0 : return "Unknown LZMA error";
78 : }
79 : }
80 : #endif /* defined(HAVE_LZMA) */
81 :
82 : /** Return 1 if LZMA compression is supported; otherwise 0. */
83 : int
84 245 : tor_lzma_method_supported(void)
85 : {
86 : #ifdef HAVE_LZMA
87 245 : return 1;
88 : #else
89 : return 0;
90 : #endif
91 : }
92 :
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 236 : tor_lzma_get_version_str(void)
97 : {
98 : #ifdef HAVE_LZMA
99 236 : return lzma_version_string();
100 : #else
101 : return NULL;
102 : #endif
103 : }
104 :
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 1 : tor_lzma_get_header_version_str(void)
109 : {
110 : #ifdef HAVE_LZMA
111 1 : return LZMA_VERSION_STRING;
112 : #else
113 : return NULL;
114 : #endif
115 : }
116 :
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
123 :
124 : int compress; /**< True if we are compressing; false if we are inflating */
125 :
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;
130 :
131 : /** Approximate number of bytes allocated for this object. */
132 : size_t allocation;
133 : };
134 :
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 88 : tor_lzma_state_size_precalc(int compress, compression_level_t level)
140 : {
141 88 : uint64_t memory_usage;
142 :
143 88 : if (compress)
144 62 : memory_usage = lzma_easy_encoder_memusage(memory_level(level));
145 : else
146 26 : memory_usage = lzma_easy_decoder_memusage(memory_level(level));
147 :
148 88 : 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
154 : }
155 :
156 88 : if (memory_usage + sizeof(tor_lzma_compress_state_t) > SIZE_MAX)
157 : memory_usage = SIZE_MAX;
158 : else
159 88 : memory_usage += sizeof(tor_lzma_compress_state_t);
160 :
161 88 : return (size_t)memory_usage;
162 :
163 : // LCOV_EXCL_START
164 : err:
165 : return 0;
166 : // LCOV_EXCL_STOP
167 : }
168 : #endif /* defined(HAVE_LZMA) */
169 :
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 88 : tor_lzma_compress_new(int compress,
175 : compress_method_t method,
176 : compression_level_t level)
177 : {
178 88 : tor_assert(method == LZMA_METHOD);
179 :
180 : #ifdef HAVE_LZMA
181 88 : tor_lzma_compress_state_t *result;
182 88 : lzma_ret retval;
183 88 : lzma_options_lzma stream_options;
184 :
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 88 : result = tor_malloc_zero(sizeof(tor_lzma_compress_state_t));
189 88 : result->compress = compress;
190 88 : result->allocation = tor_lzma_state_size_precalc(compress, level);
191 :
192 88 : if (compress) {
193 62 : lzma_lzma_preset(&stream_options, memory_level(level));
194 :
195 62 : retval = lzma_alone_encoder(&result->stream, &stream_options);
196 :
197 62 : 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
203 : }
204 : } else {
205 26 : retval = lzma_alone_decoder(&result->stream, MEMORY_LIMIT);
206 :
207 26 : 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
213 : }
214 : }
215 :
216 88 : atomic_counter_add(&total_lzma_allocation, result->allocation);
217 88 : return result;
218 :
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;
228 :
229 : return NULL;
230 : #endif /* defined(HAVE_LZMA) */
231 : }
232 :
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.
237 : *
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.
243 : */
244 : tor_compress_output_t
245 153 : 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)
249 : {
250 : #ifdef HAVE_LZMA
251 153 : lzma_ret retval;
252 153 : lzma_action action;
253 :
254 153 : tor_assert(state != NULL);
255 153 : tor_assert(*in_len <= UINT_MAX);
256 153 : tor_assert(*out_len <= UINT_MAX);
257 :
258 153 : state->stream.next_in = (unsigned char *)*in;
259 153 : state->stream.avail_in = *in_len;
260 153 : state->stream.next_out = (unsigned char *)*out;
261 153 : state->stream.avail_out = *out_len;
262 :
263 153 : action = finish ? LZMA_FINISH : LZMA_RUN;
264 :
265 153 : retval = lzma_code(&state->stream, action);
266 :
267 153 : state->input_so_far += state->stream.next_in - ((unsigned char *)*in);
268 153 : state->output_so_far += state->stream.next_out - ((unsigned char *)*out);
269 :
270 153 : *out = (char *)state->stream.next_out;
271 153 : *out_len = state->stream.avail_out;
272 153 : *in = (const char *)state->stream.next_in;
273 153 : *in_len = state->stream.avail_in;
274 :
275 185 : if (! state->compress &&
276 32 : tor_compress_is_compression_bomb(state->input_so_far,
277 : state->output_so_far)) {
278 1 : log_warn(LD_DIR, "Possible compression bomb; abandoning stream.");
279 1 : return TOR_COMPRESS_ERROR;
280 : }
281 :
282 152 : switch (retval) {
283 67 : case LZMA_OK:
284 67 : if (state->stream.avail_out == 0 || finish)
285 34 : return TOR_COMPRESS_BUFFER_FULL;
286 :
287 : return TOR_COMPRESS_OK;
288 :
289 0 : case LZMA_BUF_ERROR:
290 0 : if (state->stream.avail_in == 0 && !finish)
291 0 : return TOR_COMPRESS_OK;
292 :
293 : return TOR_COMPRESS_BUFFER_FULL;
294 :
295 : case LZMA_STREAM_END:
296 : return TOR_COMPRESS_DONE;
297 :
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 2 : 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 2 : log_warn(LD_GENERAL, "LZMA %s didn't finish: %s.",
311 : state->compress ? "compression" : "decompression",
312 : lzma_error_str(retval));
313 2 : return TOR_COMPRESS_ERROR;
314 : }
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) */
324 : }
325 :
326 : /** Deallocate <b>state</b>. */
327 : void
328 88 : tor_lzma_compress_free_(tor_lzma_compress_state_t *state)
329 : {
330 88 : if (state == NULL)
331 : return;
332 :
333 88 : atomic_counter_sub(&total_lzma_allocation, state->allocation);
334 :
335 : #ifdef HAVE_LZMA
336 88 : lzma_end(&state->stream);
337 : #endif
338 :
339 88 : tor_free(state);
340 : }
341 :
342 : /** Return the approximate number of bytes allocated for <b>state</b>. */
343 : size_t
344 4 : tor_lzma_compress_state_size(const tor_lzma_compress_state_t *state)
345 : {
346 4 : tor_assert(state != NULL);
347 4 : return state->allocation;
348 : }
349 :
350 : /** Return the approximate number of bytes allocated for all LZMA states. */
351 : size_t
352 26 : tor_lzma_get_total_allocation(void)
353 : {
354 26 : return atomic_counter_get(&total_lzma_allocation);
355 : }
356 :
357 : /** Initialize the lzma module */
358 : void
359 10862 : tor_lzma_init(void)
360 : {
361 10862 : atomic_counter_init(&total_lzma_allocation);
362 10862 : }
|