summaryrefslogtreecommitdiffstats
path: root/htp/htp_decompressors.c
diff options
context:
space:
mode:
Diffstat (limited to 'htp/htp_decompressors.c')
-rw-r--r--htp/htp_decompressors.c490
1 files changed, 490 insertions, 0 deletions
diff --git a/htp/htp_decompressors.c b/htp/htp_decompressors.c
new file mode 100644
index 0000000..fe12833
--- /dev/null
+++ b/htp/htp_decompressors.c
@@ -0,0 +1,490 @@
+/***************************************************************************
+ * Copyright (c) 2009-2010 Open Information Security Foundation
+ * Copyright (c) 2010-2013 Qualys, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+
+ * - Neither the name of the Qualys, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ***************************************************************************/
+
+/**
+ * @file
+ * @author Ivan Ristic <ivanr@webkreator.com>
+ */
+
+#include "htp_config_auto.h"
+
+#include "htp_private.h"
+
+
+static void *SzAlloc(ISzAllocPtr p, size_t size) { return malloc(size); }
+static void SzFree(ISzAllocPtr p, void *address) { free(address); }
+const ISzAlloc lzma_Alloc = { SzAlloc, SzFree };
+
+
+/**
+ * @brief See if the header has extensions
+ * @return number of bytes to skip
+ */
+static size_t htp_gzip_decompressor_probe(const unsigned char *data,
+ size_t data_len)
+{
+ if (data_len < 4)
+ return 0;
+
+ size_t consumed = 0;
+
+ if (data[0] == 0x1f && data[1] == 0x8b && data[3] != 0) {
+ if (data[3] & (1 << 3) || data[3] & (1 << 4)) {
+ /* skip past
+ * - FNAME extension, which is a name ended in a NUL terminator
+ * or
+ * - FCOMMENT extension, which is a commend ended in a NULL terminator
+ */
+
+ size_t len;
+ for (len = 10; len < data_len && data[len] != '\0'; len++);
+ consumed = len + 1;
+
+ //printf("skipped %u bytes for FNAME/FCOMMENT header (GZIP)\n", (uint)consumed);
+
+ } else if (data[3] & (1 << 1)) {
+ consumed = 12;
+ //printf("skipped %u bytes for FHCRC header (GZIP)\n", 12);
+
+ } else {
+ //printf("GZIP unknown/unsupported flags %02X\n", data[3]);
+ consumed = 10;
+ }
+ }
+
+ if (consumed > data_len)
+ return 0;
+
+ return consumed;
+}
+
+/**
+ * @brief restart the decompressor
+ * @return 1 if it restarted, 0 otherwise
+ */
+static int htp_gzip_decompressor_restart(htp_decompressor_gzip_t *drec,
+ const unsigned char *data,
+ size_t data_len, size_t *consumed_back)
+{
+ size_t consumed = 0;
+ int rc = 0;
+
+ if (drec->restart < 3) {
+
+ // first retry with the existing type, but now consider the
+ // extensions
+ if (drec->restart == 0) {
+ consumed = htp_gzip_decompressor_probe(data, data_len);
+
+ if (drec->zlib_initialized == HTP_COMPRESSION_GZIP) {
+ //printf("GZIP restart, consumed %u\n", (uint)consumed);
+ rc = inflateInit2(&drec->stream, 15 + 32);
+ } else {
+ //printf("DEFLATE restart, consumed %u\n", (uint)consumed);
+ rc = inflateInit2(&drec->stream, -15);
+ }
+ if (rc != Z_OK)
+ return 0;
+
+ goto restart;
+
+ // if that still fails, try the other method we support
+
+ } else if (drec->zlib_initialized == HTP_COMPRESSION_DEFLATE) {
+ rc = inflateInit2(&drec->stream, 15 + 32);
+ if (rc != Z_OK)
+ return 0;
+
+ drec->zlib_initialized = HTP_COMPRESSION_GZIP;
+ consumed = htp_gzip_decompressor_probe(data, data_len);
+#if 0
+ printf("DEFLATE -> GZIP consumed %u\n", (uint)consumed);
+#endif
+ goto restart;
+
+ } else if (drec->zlib_initialized == HTP_COMPRESSION_GZIP) {
+ rc = inflateInit2(&drec->stream, -15);
+ if (rc != Z_OK)
+ return 0;
+
+ drec->zlib_initialized = HTP_COMPRESSION_DEFLATE;
+ consumed = htp_gzip_decompressor_probe(data, data_len);
+#if 0
+ printf("GZIP -> DEFLATE consumed %u\n", (uint)consumed);
+#endif
+ goto restart;
+ }
+ }
+ return 0;
+
+restart:
+#if 0
+ gz_header y;
+ gz_headerp x = &y;
+ int res = inflateGetHeader(&drec->stream, x);
+ printf("HEADER res %d x.os %d x.done %d\n", res, x->os, x->done);
+#endif
+ *consumed_back = consumed;
+ drec->restart++;
+ return 1;
+}
+
+/**
+ * Ends decompressor.
+ *
+ * @param[in] drec
+ */
+static void htp_gzip_decompressor_end(htp_decompressor_gzip_t *drec) {
+ if (drec->zlib_initialized == HTP_COMPRESSION_LZMA) {
+ LzmaDec_Free(&drec->state, &lzma_Alloc);
+ drec->zlib_initialized = 0;
+ } else if (drec->zlib_initialized) {
+ inflateEnd(&drec->stream);
+ drec->zlib_initialized = 0;
+ }
+}
+
+/**
+ * Decompress a chunk of gzip-compressed data.
+ * If we have more than one decompressor, call this function recursively.
+ *
+ * @param[in] drec
+ * @param[in] d
+ * @return HTP_OK on success, HTP_ERROR or some other negative integer on failure.
+ */
+htp_status_t htp_gzip_decompressor_decompress(htp_decompressor_t *drec1, htp_tx_data_t *d) {
+ size_t consumed = 0;
+ int rc = 0;
+ htp_status_t callback_rc;
+ htp_decompressor_gzip_t *drec = (htp_decompressor_gzip_t*) drec1;
+
+ // Pass-through the NULL chunk, which indicates the end of the stream.
+
+ if (drec->super.passthrough) {
+ htp_tx_data_t d2;
+ d2.tx = d->tx;
+ d2.data = d->data;
+ d2.len = d->len;
+ d2.is_last = d->is_last;
+
+ callback_rc = drec->super.callback(&d2);
+ if (callback_rc != HTP_OK) {
+ return HTP_ERROR;
+ }
+
+ return HTP_OK;
+ }
+
+ if (d->data == NULL) {
+ // Prepare data for callback.
+ htp_tx_data_t dout;
+ dout.tx = d->tx;
+ // This is last call, so output uncompressed data so far
+ dout.len = GZIP_BUF_SIZE - drec->stream.avail_out;
+ if (dout.len > 0) {
+ dout.data = drec->buffer;
+ } else {
+ dout.data = NULL;
+ }
+ dout.is_last = d->is_last;
+ if (drec->super.next != NULL && drec->zlib_initialized) {
+ return htp_gzip_decompressor_decompress(drec->super.next, &dout);
+ } else {
+ // Send decompressed data to the callback.
+ callback_rc = drec->super.callback(&dout);
+ if (callback_rc != HTP_OK) {
+ htp_gzip_decompressor_end(drec);
+ return callback_rc;
+ }
+ }
+
+ return HTP_OK;
+ }
+
+restart:
+ if (consumed > d->len || d->len > UINT32_MAX ) {
+ htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "GZip decompressor: consumed > d->len");
+ return HTP_ERROR;
+ }
+ drec->stream.next_in = (unsigned char *) (d->data + consumed);
+ drec->stream.avail_in = (uint32_t) (d->len - consumed);
+
+ while (drec->stream.avail_in != 0) {
+ // If there's no more data left in the
+ // buffer, send that information out.
+ if (drec->stream.avail_out == 0) {
+ drec->crc = crc32(drec->crc, drec->buffer, GZIP_BUF_SIZE);
+
+ // Prepare data for callback.
+ htp_tx_data_t d2;
+ d2.tx = d->tx;
+ d2.data = drec->buffer;
+ d2.len = GZIP_BUF_SIZE;
+ d2.is_last = d->is_last;
+
+ if (drec->super.next != NULL && drec->zlib_initialized) {
+ callback_rc = htp_gzip_decompressor_decompress(drec->super.next, &d2);
+ } else {
+ // Send decompressed data to callback.
+ callback_rc = drec->super.callback(&d2);
+ }
+ if (callback_rc != HTP_OK) {
+ htp_gzip_decompressor_end(drec);
+ return callback_rc;
+ }
+
+ drec->stream.next_out = drec->buffer;
+ drec->stream.avail_out = GZIP_BUF_SIZE;
+ }
+
+ if (drec->zlib_initialized == HTP_COMPRESSION_LZMA) {
+ if (drec->header_len < LZMA_PROPS_SIZE + 8) {
+ consumed = LZMA_PROPS_SIZE + 8 - drec->header_len;
+ if (consumed > drec->stream.avail_in) {
+ consumed = drec->stream.avail_in;
+ }
+ memcpy(drec->header + drec->header_len, drec->stream.next_in, consumed);
+ drec->stream.next_in = (unsigned char *) (d->data + consumed);
+ drec->stream.avail_in = (uint32_t) (d->len - consumed);
+ drec->header_len += consumed;
+ }
+ if (drec->header_len == LZMA_PROPS_SIZE + 8) {
+ rc = LzmaDec_Allocate(&drec->state, drec->header, LZMA_PROPS_SIZE, &lzma_Alloc);
+ if (rc != SZ_OK)
+ return rc;
+ LzmaDec_Init(&drec->state);
+ // hacky to get to next step end retry allocate in case of failure
+ drec->header_len++;
+ }
+ if (drec->header_len > LZMA_PROPS_SIZE + 8) {
+ size_t inprocessed = drec->stream.avail_in;
+ size_t outprocessed = drec->stream.avail_out;
+ ELzmaStatus status;
+ rc = LzmaDec_DecodeToBuf(&drec->state, drec->stream.next_out, &outprocessed,
+ drec->stream.next_in, &inprocessed, LZMA_FINISH_ANY, &status, d->tx->cfg->lzma_memlimit);
+ drec->stream.avail_in -= inprocessed;
+ drec->stream.next_in += inprocessed;
+ drec->stream.avail_out -= outprocessed;
+ drec->stream.next_out += outprocessed;
+ switch (rc) {
+ case SZ_OK:
+ rc = Z_OK;
+ if (status == LZMA_STATUS_FINISHED_WITH_MARK) {
+ rc = Z_STREAM_END;
+ }
+ break;
+ case SZ_ERROR_MEM:
+ htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "LZMA decompressor: memory limit reached");
+ // fall through
+ default:
+ rc = Z_DATA_ERROR;
+ }
+ }
+ } else if (drec->zlib_initialized) {
+ rc = inflate(&drec->stream, Z_NO_FLUSH);
+ } else {
+ // no initialization means previous error on stream
+ return HTP_ERROR;
+ }
+ if (GZIP_BUF_SIZE > drec->stream.avail_out) {
+ if (rc == Z_DATA_ERROR) {
+ // There is data even if there is an error
+ // So use this data and log a warning
+ htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "GZip decompressor: inflate failed with %d", rc);
+ rc = Z_STREAM_END;
+ }
+ }
+ if (rc == Z_STREAM_END) {
+ // How many bytes do we have?
+ size_t len = GZIP_BUF_SIZE - drec->stream.avail_out;
+
+ // Update CRC
+
+ // Prepare data for the callback.
+ htp_tx_data_t d2;
+ d2.tx = d->tx;
+ d2.data = drec->buffer;
+ d2.len = len;
+ d2.is_last = d->is_last;
+
+ if (drec->super.next != NULL && drec->zlib_initialized) {
+ callback_rc = htp_gzip_decompressor_decompress(drec->super.next, &d2);
+ } else {
+ // Send decompressed data to the callback.
+ callback_rc = drec->super.callback(&d2);
+ }
+ if (callback_rc != HTP_OK) {
+ htp_gzip_decompressor_end(drec);
+ return callback_rc;
+ }
+ drec->stream.avail_out = GZIP_BUF_SIZE;
+ drec->stream.next_out = drec->buffer;
+ // TODO Handle trailer.
+
+ return HTP_OK;
+ }
+ else if (rc != Z_OK) {
+ htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "GZip decompressor: inflate failed with %d", rc);
+ if (drec->zlib_initialized == HTP_COMPRESSION_LZMA) {
+ LzmaDec_Free(&drec->state, &lzma_Alloc);
+ // so as to clean zlib ressources after restart
+ drec->zlib_initialized = HTP_COMPRESSION_NONE;
+ } else {
+ inflateEnd(&drec->stream);
+ }
+
+ // see if we want to restart the decompressor
+ if (htp_gzip_decompressor_restart(drec,
+ d->data, d->len, &consumed) == 1)
+ {
+ // we'll be restarting the compressor
+ goto restart;
+ }
+
+ drec->zlib_initialized = 0;
+
+ // all our inflate attempts have failed, simply
+ // pass the raw data on to the callback in case
+ // it's not compressed at all
+
+ htp_tx_data_t d2;
+ d2.tx = d->tx;
+ d2.data = d->data;
+ d2.len = d->len;
+ d2.is_last = d->is_last;
+
+ callback_rc = drec->super.callback(&d2);
+ if (callback_rc != HTP_OK) {
+ return HTP_ERROR;
+ }
+
+ drec->stream.avail_out = GZIP_BUF_SIZE;
+ drec->stream.next_out = drec->buffer;
+
+ /* successfully passed through, lets continue doing that */
+ drec->super.passthrough = 1;
+ return HTP_OK;
+ }
+ }
+
+ return HTP_OK;
+}
+
+/**
+ * Shut down gzip decompressor.
+ *
+ * @param[in] drec
+ */
+void htp_gzip_decompressor_destroy(htp_decompressor_t *drec1) {
+ htp_decompressor_gzip_t *drec = (htp_decompressor_gzip_t*) drec1;
+ if (drec == NULL) return;
+
+ htp_gzip_decompressor_end(drec);
+
+ free(drec->buffer);
+ free(drec);
+}
+
+/**
+ * Create a new decompressor instance.
+ *
+ * @param[in] connp
+ * @param[in] format
+ * @return New htp_decompressor_t instance on success, or NULL on failure.
+ */
+htp_decompressor_t *htp_gzip_decompressor_create(htp_connp_t *connp, enum htp_content_encoding_t format) {
+ htp_decompressor_gzip_t *drec = calloc(1, sizeof (htp_decompressor_gzip_t));
+ if (drec == NULL) return NULL;
+
+ drec->super.decompress = NULL;
+ drec->super.destroy = NULL;
+ drec->super.next = NULL;
+
+ drec->buffer = malloc(GZIP_BUF_SIZE);
+ if (drec->buffer == NULL) {
+ free(drec);
+ return NULL;
+ }
+
+ // Initialize zlib.
+ int rc;
+
+ switch (format) {
+ case HTP_COMPRESSION_LZMA:
+ if (connp->cfg->lzma_memlimit > 0 &&
+ connp->cfg->response_lzma_layer_limit > 0) {
+ LzmaDec_Construct(&drec->state);
+ } else {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "LZMA decompression disabled");
+ drec->super.passthrough = 1;
+ }
+ rc = Z_OK;
+ break;
+ case HTP_COMPRESSION_DEFLATE:
+ // Negative values activate raw processing,
+ // which is what we need for deflate.
+ rc = inflateInit2(&drec->stream, -15);
+ break;
+ case HTP_COMPRESSION_GZIP:
+ // Increased windows size activates gzip header processing.
+ rc = inflateInit2(&drec->stream, 15 + 32);
+ break;
+ default:
+ // do nothing
+ rc = Z_DATA_ERROR;
+ }
+
+ if (rc != Z_OK) {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "GZip decompressor: inflateInit2 failed with code %d", rc);
+
+ if (format == HTP_COMPRESSION_DEFLATE || format == HTP_COMPRESSION_GZIP) {
+ inflateEnd(&drec->stream);
+ }
+ free(drec->buffer);
+ free(drec);
+
+ return NULL;
+ }
+
+ drec->zlib_initialized = format;
+ drec->stream.avail_out = GZIP_BUF_SIZE;
+ drec->stream.next_out = drec->buffer;
+
+ #if 0
+ if (format == COMPRESSION_DEFLATE) {
+ drec->initialized = 1;
+ }
+ #endif
+
+ return (htp_decompressor_t *) drec;
+}