LCOV - code coverage report
Current view: top level - lib/compress - compress_lzma.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 83 106 78.3 %
Date: 2021-11-24 03:28:48 Functions: 12 12 100.0 %

          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 : }

Generated by: LCOV version 1.14