diff --git a/rust/src/lzma.rs b/rust/src/lzma.rs index e10d803a6e..e347b83eb5 100644 --- a/rust/src/lzma.rs +++ b/rust/src/lzma.rs @@ -19,12 +19,13 @@ use lzma_rs::decompress::{Options, Stream}; use lzma_rs::error::Error; -use std::io::{Cursor, Write}; +use std::io::{Cursor, ErrorKind, Write}; /// Propagate lzma crate errors #[repr(C)] pub enum LzmaStatus { LzmaOk, + LzmaOutputFull, LzmaIoError, LzmaHeaderTooShortError, LzmaError, @@ -35,6 +36,7 @@ pub enum LzmaStatus { impl From for LzmaStatus { fn from(e: Error) -> LzmaStatus { match e { + Error::IoError(e) if e.kind() == ErrorKind::WriteZero => LzmaStatus::LzmaOutputFull, Error::IoError(_) => LzmaStatus::LzmaIoError, Error::HeaderTooShort(_) => LzmaStatus::LzmaHeaderTooShortError, Error::LzmaError(e) => { @@ -50,8 +52,12 @@ impl From for LzmaStatus { } impl From for LzmaStatus { - fn from(_e: std::io::Error) -> LzmaStatus { - LzmaStatus::LzmaIoError + fn from(e: std::io::Error) -> LzmaStatus { + if e.kind() == ErrorKind::WriteZero { + LzmaStatus::LzmaOutputFull + } else { + LzmaStatus::LzmaIoError + } } } @@ -85,3 +91,4 @@ pub unsafe extern "C" fn lzma_decompress( Err(e) => e.into(), } } + diff --git a/src/tests/detect-file-data.c b/src/tests/detect-file-data.c index cb0168691f..623de42620 100644 --- a/src/tests/detect-file-data.c +++ b/src/tests/detect-file-data.c @@ -28,6 +28,162 @@ #include "../stream-tcp.h" #include "../detect.h" #include "../detect-isdataat.h" +#include "../detect-engine-inspect-buffer.h" +#include "../util-file-decompression.h" +#include "../util-file-swf-decompression.h" +#include "../app-layer-events.h" +#include "../app-layer-htp.h" + +#include + +static uint8_t *DetectFiledataSwfCreateCws(const uint8_t *plain, uint32_t plain_len, + uint32_t file_len, int compression_level, uint32_t *cws_len) +{ + uLongf compressed_len = compressBound(plain_len); + uint8_t *cws = SCMalloc(SWF_HEADER_LEN + compressed_len); + if (cws == NULL) + return NULL; + + cws[0] = 'C'; + cws[1] = 'W'; + cws[2] = 'S'; + cws[3] = 0x06; + cws[4] = (uint8_t)file_len; + cws[5] = (uint8_t)(file_len >> 8); + cws[6] = (uint8_t)(file_len >> 16); + cws[7] = (uint8_t)(file_len >> 24); + + int r = compress2(cws + SWF_HEADER_LEN, &compressed_len, plain, plain_len, compression_level); + if (r != Z_OK || compressed_len > UINT32_MAX - SWF_HEADER_LEN) { + SCFree(cws); + return NULL; + } + *cws_len = SWF_HEADER_LEN + (uint32_t)compressed_len; + return cws; +} + +/** + * \test A malicious SWF whose header claims a huge decompressed size but + * carries only a few bytes of body must not drive a large allocation + * of the inspection buffer. + */ +static int DetectFiledataSwfDecompressAllocTest01(void) +{ + uint8_t buffer[] = { 'C', 'W', 'S', 0x06, 0x80, 0xF0, 0xFA, 0x02, 0x00, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07 }; + + DetectEngineThreadCtx *det_ctx = SCCalloc(1, sizeof(*det_ctx)); + FAIL_IF_NULL(det_ctx); + + InspectionBuffer out_buffer; + memset(&out_buffer, 0, sizeof(out_buffer)); + + int r = FileSwfDecompression(buffer, (uint32_t)sizeof(buffer), det_ctx, &out_buffer, + HTTP_SWF_COMPRESSION_ZLIB, 0, 0); + + FAIL_IF(r != 0); + FAIL_IF(out_buffer.size > SWF_DECOMPRESS_INITIAL_BUFFER_LEN); + + InspectionBufferFree(&out_buffer); + AppLayerDecoderEventsFreeEvents(&det_ctx->decoder_events); + SCFree(det_ctx); + PASS; +} + +/** + * \test A valid, small CWS with a forged large FileLength must only retain the + * buffer needed for its actual output. + */ +static int DetectFiledataSwfDecompressAllocTest02(void) +{ + const uint8_t plain[] = "small compressed SWF body"; + const uint32_t file_len = MAX_SWF_DECOMPRESSED_LEN; + uint32_t cws_len = 0; + uint8_t *cws = DetectFiledataSwfCreateCws( + plain, sizeof(plain), file_len, Z_BEST_COMPRESSION, &cws_len); + FAIL_IF_NULL(cws); + + DetectEngineThreadCtx *det_ctx = SCCalloc(1, sizeof(*det_ctx)); + FAIL_IF_NULL(det_ctx); + InspectionBuffer out_buffer = { 0 }; + + int r = FileSwfDecompression( + cws, cws_len, det_ctx, &out_buffer, HTTP_SWF_COMPRESSION_ZLIB, 0, 0); + + FAIL_IF(r != 1); + FAIL_IF(out_buffer.size > SWF_DECOMPRESS_INITIAL_BUFFER_LEN); + FAIL_IF(out_buffer.len != SWF_HEADER_LEN + sizeof(plain)); + FAIL_IF(memcmp(out_buffer.buf + 4, cws + 4, 4) != 0); + FAIL_IF(memcmp(out_buffer.buf + SWF_HEADER_LEN, plain, sizeof(plain)) != 0); + + InspectionBufferFree(&out_buffer); + AppLayerDecoderEventsFreeEvents(&det_ctx->decoder_events); + SCFree(det_ctx); + SCFree(cws); + PASS; +} + +/** + * \test A weakly compressed CWS must not reserve the maximum possible zlib + * expansion in the per-thread inspection buffer. + */ +static int DetectFiledataSwfDecompressAllocTest03(void) +{ + const uint32_t plain_len = 48 * 1024; + uint8_t *plain = SCMalloc(plain_len); + FAIL_IF_NULL(plain); + for (uint32_t i = 0; i < plain_len; i++) + plain[i] = (uint8_t)i; + + uint32_t cws_len = 0; + uint8_t *cws = DetectFiledataSwfCreateCws( + plain, plain_len, plain_len + SWF_HEADER_LEN, Z_NO_COMPRESSION, &cws_len); + FAIL_IF_NULL(cws); + + DetectEngineThreadCtx *det_ctx = SCCalloc(1, sizeof(*det_ctx)); + FAIL_IF_NULL(det_ctx); + InspectionBuffer out_buffer = { 0 }; + + int r = FileSwfDecompression( + cws, cws_len, det_ctx, &out_buffer, HTTP_SWF_COMPRESSION_ZLIB, 0, 0); + + FAIL_IF(r != 1); + FAIL_IF(out_buffer.size > 64 * 1024); + FAIL_IF(out_buffer.len != SWF_HEADER_LEN + plain_len); + FAIL_IF(memcmp(out_buffer.buf + SWF_HEADER_LEN, plain, plain_len) != 0); + + InspectionBufferFree(&out_buffer); + AppLayerDecoderEventsFreeEvents(&det_ctx->decoder_events); + SCFree(det_ctx); + SCFree(cws); + SCFree(plain); + PASS; +} + +/** + * \test A ZWS with a forged large FileLength and invalid LZMA properties must + * fail without growing the inspection buffer. + */ +static int DetectFiledataSwfDecompressAllocTest04(void) +{ + uint8_t buffer[] = { 'Z', 'W', 'S', 0x0D, 0x80, 0xF0, 0xFA, 0x02, 0x01, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00 }; + + DetectEngineThreadCtx *det_ctx = SCCalloc(1, sizeof(*det_ctx)); + FAIL_IF_NULL(det_ctx); + InspectionBuffer out_buffer = { 0 }; + + int r = FileSwfDecompression(buffer, (uint32_t)sizeof(buffer), det_ctx, &out_buffer, + HTTP_SWF_COMPRESSION_LZMA, 0, 0); + + FAIL_IF(r != 0); + FAIL_IF(out_buffer.size > SWF_DECOMPRESS_INITIAL_BUFFER_LEN); + + InspectionBufferFree(&out_buffer); + AppLayerDecoderEventsFreeEvents(&det_ctx->decoder_events); + SCFree(det_ctx); + PASS; +} static int DetectEngineSMTPFiledataTest02(void) { @@ -63,6 +219,14 @@ static int DetectFiledataParseTest04(void) void DetectFiledataRegisterTests(void) { + UtRegisterTest( + "DetectFiledataSwfDecompressAllocTest01", DetectFiledataSwfDecompressAllocTest01); + UtRegisterTest( + "DetectFiledataSwfDecompressAllocTest02", DetectFiledataSwfDecompressAllocTest02); + UtRegisterTest( + "DetectFiledataSwfDecompressAllocTest03", DetectFiledataSwfDecompressAllocTest03); + UtRegisterTest( + "DetectFiledataSwfDecompressAllocTest04", DetectFiledataSwfDecompressAllocTest04); UtRegisterTest("DetectEngineSMTPFiledataTest02", DetectEngineSMTPFiledataTest02); UtRegisterTest("DetectFiledataParseTest04", DetectFiledataParseTest04); } diff --git a/src/util-file-decompression.c b/src/util-file-decompression.c index aabd428c90..2ddbb3d6d8 100644 --- a/src/util-file-decompression.c +++ b/src/util-file-decompression.c @@ -34,6 +34,7 @@ #include "util-file-swf-decompression.h" #include "util-misc.h" #include "util-print.h" +#include "util-validate.h" #define SWF_ZLIB_MIN_VERSION 0x06 #define SWF_LZMA_MIN_VERSION 0x0D @@ -59,8 +60,7 @@ int FileIsSwfFile(const uint8_t *buffer, uint32_t buffer_len) * * \param buffer compressed buffer * \param buffer_len compressed buffer length - * \param decompressed_buffer buffer that store decompressed data - * \param decompressed_buffer_len decompressed data length + * \param out_buffer inspection buffer that stores decompressed data * \param swf_type decompression algorithm to use * \param decompress_depth how much decompressed data we want to store * \param compress_depth how much compressed data we want to decompress @@ -124,23 +124,16 @@ int FileSwfDecompression(const uint8_t *buffer, uint32_t buffer_len, decompressed_swf_len = MIN_SWF_LEN; } - /* if decompress_depth is 0, keep the flash file length */ - uint32_t decompressed_data_len = decompressed_swf_len; - // only restrict the decompressed_data_len, a too big decompress_depth does not make us more - // than we need - if (decompress_depth > 0 && decompress_depth < decompressed_data_len) - decompressed_data_len = decompress_depth; + uint32_t decompressed_data_limit = decompressed_swf_len; + if (decompress_depth > 0 && decompress_depth < decompressed_data_limit) + decompressed_data_limit = decompress_depth; - // cannot overflow as decompressed_swf_len maxed out at MAX_SWF_DECOMPRESSED_LEN 50000000 - decompressed_data_len += 8; - - /* make sure the inspection buffer has enough space */ - SCInspectionBufferCheckAndExpand(out_buffer, decompressed_data_len); - if (out_buffer->size < decompressed_data_len) { + uint32_t initial_buffer_len = + MIN(SWF_DECOMPRESS_INITIAL_BUFFER_LEN, decompressed_data_limit + SWF_HEADER_LEN); + if (SCInspectionBufferCheckAndExpand(out_buffer, initial_buffer_len) == NULL) { DetectEngineSetEvent(det_ctx, FILE_DECODER_EVENT_NO_MEM); return 0; } - out_buffer->len = decompressed_data_len; /* * FWS format @@ -152,7 +145,8 @@ int FileSwfDecompression(const uint8_t *buffer, uint32_t buffer_len, out_buffer->buf[2] = 'S'; out_buffer->buf[3] = swf_version; memcpy(out_buffer->buf + 4, &decompressed_swf_len, 4); - memset(out_buffer->buf + 8, 0, decompressed_data_len - 8); + + uint32_t decompressed_data_produced = 0; if ((swf_type == HTTP_SWF_COMPRESSION_ZLIB || swf_type == HTTP_SWF_COMPRESSION_BOTH) && compression_type == FILE_SWF_ZLIB_COMPRESSION) @@ -160,9 +154,8 @@ int FileSwfDecompression(const uint8_t *buffer, uint32_t buffer_len, /* the first 8 bytes represents the fws header, see 'FWS format' above. * data will start from 8th bytes */ - r = FileSwfZlibDecompression(det_ctx, - (uint8_t *)buffer + offset, compressed_data_len, - out_buffer->buf + 8, out_buffer->len - 8); + r = FileSwfZlibDecompression(det_ctx, buffer + offset, compressed_data_len, out_buffer, + decompressed_data_limit, &decompressed_data_produced); if (r == 0) goto error; @@ -177,6 +170,7 @@ int FileSwfDecompression(const uint8_t *buffer, uint32_t buffer_len, compressed_data_len += 13; uint8_t *compressed_data = SCCalloc(1, compressed_data_len); if (compressed_data == NULL) { + DetectEngineSetEvent(det_ctx, FILE_DECODER_EVENT_NO_MEM); goto error; } /* put lzma properties */ @@ -189,9 +183,8 @@ int FileSwfDecompression(const uint8_t *buffer, uint32_t buffer_len, /* the first 8 bytes represents the fws header, see 'FWS format' above. * data will start from 8th bytes */ - r = FileSwfLzmaDecompression(det_ctx, - compressed_data, compressed_data_len, - out_buffer->buf + 8, out_buffer->len - 8); + r = FileSwfLzmaDecompression(det_ctx, compressed_data, compressed_data_len, out_buffer, + decompressed_data_limit, &decompressed_data_produced); SCFree(compressed_data); if (r == 0) goto error; @@ -199,6 +192,9 @@ int FileSwfDecompression(const uint8_t *buffer, uint32_t buffer_len, goto error; } + DEBUG_VALIDATE_BUG_ON(decompressed_data_produced > decompressed_data_limit); + out_buffer->len = SWF_HEADER_LEN + decompressed_data_produced; + /* all went well so switch the buffer's inspect pointer/size * to use the new data. */ out_buffer->inspect = out_buffer->buf; diff --git a/src/util-file-swf-decompression.c b/src/util-file-swf-decompression.c index 378b4f96e9..8e54bde975 100644 --- a/src/util-file-swf-decompression.c +++ b/src/util-file-swf-decompression.c @@ -37,7 +37,6 @@ #include -#define MAX_SWF_DECOMPRESSED_LEN 50000000 /* * Return uncompressed file length * in little-endian order @@ -75,16 +74,49 @@ uint8_t FileGetSwfVersion(const uint8_t *buffer, const uint32_t buffer_len) return 0; } +static bool FileSwfDecompressionBufferGrow(DetectEngineThreadCtx *det_ctx, + InspectionBuffer *out_buffer, uint32_t decompressed_data_limit) +{ + const uint32_t maximum_buffer_len = decompressed_data_limit + SWF_HEADER_LEN; + uint32_t requested_len = out_buffer->size; + + if (requested_len >= maximum_buffer_len) + return true; + + if (requested_len == 0) { + requested_len = MIN(SWF_DECOMPRESS_INITIAL_BUFFER_LEN, maximum_buffer_len); + } else if (requested_len > maximum_buffer_len / 2) { + requested_len = maximum_buffer_len; + } else { + requested_len *= 2; + } + + if (SCInspectionBufferCheckAndExpand(out_buffer, requested_len) == NULL) { + DetectEngineSetEvent(det_ctx, FILE_DECODER_EVENT_NO_MEM); + return false; + } + return true; +} + +static uint32_t FileSwfDecompressionOutputCapacity( + const InspectionBuffer *out_buffer, uint32_t decompressed_data_limit) +{ + if (out_buffer->size <= SWF_HEADER_LEN) + return 0; + return MIN(out_buffer->size - SWF_HEADER_LEN, decompressed_data_limit); +} + /* CWS format */ /* * | 4 bytes | 4 bytes | n bytes | * | 'CWS' + version | script len | compressed data | */ -int FileSwfZlibDecompression(DetectEngineThreadCtx *det_ctx, - uint8_t *compressed_data, uint32_t compressed_data_len, - uint8_t *decompressed_data, uint32_t decompressed_data_len) +int FileSwfZlibDecompression(DetectEngineThreadCtx *det_ctx, const uint8_t *compressed_data, + uint32_t compressed_data_len, InspectionBuffer *out_buffer, + uint32_t decompressed_data_limit, uint32_t *decompressed_data_produced) { int ret = 1; + *decompressed_data_produced = 0; z_stream infstream; memset(&infstream, 0, sizeof(infstream)); infstream.zalloc = Z_NULL; @@ -93,8 +125,6 @@ int FileSwfZlibDecompression(DetectEngineThreadCtx *det_ctx, infstream.avail_in = (uInt)compressed_data_len; infstream.next_in = (Bytef *)compressed_data; - infstream.avail_out = (uInt)decompressed_data_len; - infstream.next_out = (Bytef *)decompressed_data; int result = inflateInit(&infstream); if (result != Z_OK) { @@ -102,28 +132,41 @@ int FileSwfZlibDecompression(DetectEngineThreadCtx *det_ctx, return 0; } - result = inflate(&infstream, Z_NO_FLUSH); - switch(result) { - case Z_STREAM_END: + while (true) { + uint32_t output_capacity = + FileSwfDecompressionOutputCapacity(out_buffer, decompressed_data_limit); + DEBUG_VALIDATE_BUG_ON(infstream.total_out > output_capacity); + infstream.avail_out = (uInt)(output_capacity - infstream.total_out); + infstream.next_out = out_buffer->buf + SWF_HEADER_LEN + infstream.total_out; + + result = inflate(&infstream, Z_NO_FLUSH); + *decompressed_data_produced = (uint32_t)infstream.total_out; + + if (result == Z_STREAM_END || *decompressed_data_produced == decompressed_data_limit) break; - case Z_OK: + + if (result == Z_OK && infstream.avail_in == 0) break; - case Z_DATA_ERROR: + + if (result == Z_OK && infstream.avail_out == 0) { + if (!FileSwfDecompressionBufferGrow(det_ctx, out_buffer, decompressed_data_limit)) { + ret = 0; + break; + } + continue; + } + + if (result == Z_DATA_ERROR) { DetectEngineSetEvent(det_ctx, FILE_DECODER_EVENT_Z_DATA_ERROR); - ret = 0; - break; - case Z_STREAM_ERROR: + } else if (result == Z_STREAM_ERROR) { DetectEngineSetEvent(det_ctx, FILE_DECODER_EVENT_Z_STREAM_ERROR); - ret = 0; - break; - case Z_BUF_ERROR: + } else if (result == Z_BUF_ERROR) { DetectEngineSetEvent(det_ctx, FILE_DECODER_EVENT_Z_BUF_ERROR); - ret = 0; - break; - default: + } else { DetectEngineSetEvent(det_ctx, FILE_DECODER_EVENT_Z_UNKNOWN_ERROR); - ret = 0; - break; + } + ret = 0; + break; } inflateEnd(&infstream); @@ -135,48 +178,56 @@ int FileSwfZlibDecompression(DetectEngineThreadCtx *det_ctx, * | 4 bytes | 4 bytes | 4 bytes | 5 bytes | n bytes | 6 bytes | * | 'ZWS' + version | script len | compressed len | LZMA props | LZMA data | LZMA end marker | */ -int FileSwfLzmaDecompression(DetectEngineThreadCtx *det_ctx, - uint8_t *compressed_data, uint32_t compressed_data_len, - uint8_t *decompressed_data, uint32_t decompressed_data_len) +int FileSwfLzmaDecompression(DetectEngineThreadCtx *det_ctx, const uint8_t *compressed_data, + uint32_t compressed_data_len, InspectionBuffer *out_buffer, + uint32_t decompressed_data_limit, uint32_t *decompressed_data_produced) { int ret = 0; + *decompressed_data_produced = 0; - size_t inprocessed = compressed_data_len; - size_t outprocessed = decompressed_data_len; + while (true) { + size_t inprocessed = compressed_data_len; + size_t outprocessed = + FileSwfDecompressionOutputCapacity(out_buffer, decompressed_data_limit); - ret = lzma_decompress(compressed_data, &inprocessed, decompressed_data, &outprocessed, - MAX_SWF_DECOMPRESSED_LEN); + ret = lzma_decompress(compressed_data, &inprocessed, out_buffer->buf + SWF_HEADER_LEN, + &outprocessed, MAX_SWF_DECOMPRESSED_LEN); - switch(ret) { - case LzmaOk: + if (ret == LzmaOk) { + *decompressed_data_produced = (uint32_t)outprocessed; ret = 1; break; - case LzmaIoError: + } + if (ret == LzmaOutputFull) { + *decompressed_data_produced = (uint32_t)outprocessed; + if (*decompressed_data_produced == decompressed_data_limit) { + ret = 1; + break; + } + if (!FileSwfDecompressionBufferGrow(det_ctx, out_buffer, decompressed_data_limit)) { + ret = 0; + break; + } + continue; + } + + if (ret == LzmaIoError) { DetectEngineSetEvent(det_ctx, FILE_DECODER_EVENT_LZMA_IO_ERROR); - ret = 0; - break; - case LzmaHeaderTooShortError: + } else if (ret == LzmaHeaderTooShortError) { DetectEngineSetEvent(det_ctx, FILE_DECODER_EVENT_LZMA_HEADER_TOO_SHORT_ERROR); - ret = 0; - break; - case LzmaError: + } else if (ret == LzmaError) { DetectEngineSetEvent(det_ctx, FILE_DECODER_EVENT_LZMA_DECODER_ERROR); - ret = 0; - break; - case LzmaMemoryError: + } else if (ret == LzmaMemoryError) { DetectEngineSetEvent(det_ctx, FILE_DECODER_EVENT_LZMA_MEMLIMIT_ERROR); - ret = 0; - break; - case LzmaXzError: + } else if (ret == LzmaXzError) { /* We should not see XZ compressed SWF files */ DEBUG_VALIDATE_BUG_ON(ret == LzmaXzError); DetectEngineSetEvent(det_ctx, FILE_DECODER_EVENT_LZMA_XZ_ERROR); - ret = 0; - break; - default: + } else { DetectEngineSetEvent(det_ctx, FILE_DECODER_EVENT_LZMA_UNKNOWN_ERROR); - ret = 0; - break; + } + ret = 0; + break; } return ret; diff --git a/src/util-file-swf-decompression.h b/src/util-file-swf-decompression.h index be46aec9b8..478ec88346 100644 --- a/src/util-file-swf-decompression.h +++ b/src/util-file-swf-decompression.h @@ -28,15 +28,20 @@ /* If we don't have the decompressed data len, * we use a default value. */ -#define MIN_SWF_LEN 2920 +#define MIN_SWF_LEN 2920 + +#define MAX_SWF_DECOMPRESSED_LEN 50000000 + +#define SWF_HEADER_LEN 8 +#define SWF_DECOMPRESS_INITIAL_BUFFER_LEN 4096 uint8_t FileGetSwfVersion(const uint8_t *buffer, const uint32_t buffer_len); uint32_t FileGetSwfDecompressedLen(const uint8_t *buffer, uint32_t buffer_len); -int FileSwfZlibDecompression(DetectEngineThreadCtx *det_ctx, - uint8_t *compressed_data, uint32_t compressed_data_len, - uint8_t *decompressed_data, uint32_t decompressed_data_len); -int FileSwfLzmaDecompression(DetectEngineThreadCtx *det_ctx, - uint8_t *compressed_data, uint32_t compressed_data_len, - uint8_t *decompressed_data, uint32_t decompressed_data_len); +int FileSwfZlibDecompression(DetectEngineThreadCtx *det_ctx, const uint8_t *compressed_data, + uint32_t compressed_data_len, InspectionBuffer *out_buffer, + uint32_t decompressed_data_limit, uint32_t *decompressed_data_produced); +int FileSwfLzmaDecompression(DetectEngineThreadCtx *det_ctx, const uint8_t *compressed_data, + uint32_t compressed_data_len, InspectionBuffer *out_buffer, + uint32_t decompressed_data_limit, uint32_t *decompressed_data_produced); #endif /* SURICATA_UTIL_FILE_SWF_DECOMPRESSION_H */