]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #5155: http_inspect: decompress optimization
authorOleksandr Fedorych -X (ofedoryc - SOFTSERVE INC at Cisco) <ofedoryc@cisco.com>
Fri, 13 Mar 2026 21:44:23 +0000 (21:44 +0000)
committerRayen Mohanty (ramohant) <ramohant@cisco.com>
Fri, 13 Mar 2026 21:44:23 +0000 (21:44 +0000)
Merge in SNORT/snort3 from ~OFEDORYC/snort3:decompress-optimization to master

Squashed commit of the following:

commit 15145a7b4b29ac92b439ea045afac6a44eb66e7f
Author: ofedoryc <ofedoryc@cisco.com>
Date:   Mon Feb 9 02:11:11 2026 -0500

    http_inspect: decompress optimization

25 files changed:
src/pub_sub/test/CMakeLists.txt
src/pub_sub/test/pub_sub_http_transaction_end_event_test.cc
src/service_inspectors/http_inspect/CMakeLists.txt
src/service_inspectors/http_inspect/dev_notes_partial_inspection.txt
src/service_inspectors/http_inspect/http_common.h
src/service_inspectors/http_inspect/http_compress_stream.cc [new file with mode: 0644]
src/service_inspectors/http_inspect/http_compress_stream.h [new file with mode: 0644]
src/service_inspectors/http_inspect/http_cutter.cc
src/service_inspectors/http_inspect/http_cutter.h
src/service_inspectors/http_inspect/http_enum.h
src/service_inspectors/http_inspect/http_flow_data.cc
src/service_inspectors/http_inspect/http_flow_data.h
src/service_inspectors/http_inspect/http_module.cc
src/service_inspectors/http_inspect/http_module.h
src/service_inspectors/http_inspect/http_msg_body.cc
src/service_inspectors/http_inspect/http_msg_header.cc
src/service_inspectors/http_inspect/http_msg_section.cc
src/service_inspectors/http_inspect/http_stream_splitter.h
src/service_inspectors/http_inspect/http_stream_splitter_reassemble.cc
src/service_inspectors/http_inspect/http_stream_splitter_scan.cc
src/service_inspectors/http_inspect/http_tables.cc
src/service_inspectors/http_inspect/test/CMakeLists.txt
src/service_inspectors/http_inspect/test/http_decompression_test.cc [new file with mode: 0644]
src/service_inspectors/http_inspect/test/http_transaction_test.cc
src/service_inspectors/http_inspect/test/http_unit_test_helpers.h

index 69b46cd03123cd56e6ff529c3ce5d93a94f1434a..36964a47bd082b289128b0e89d49a445b1e0b1ec 100644 (file)
@@ -38,6 +38,7 @@ add_cpputest( pub_sub_http_transaction_end_event_test
         ../../service_inspectors/http_inspect/http_test_manager.cc
         ../../service_inspectors/http_inspect/http_test_input.cc
         ../../service_inspectors/http_inspect/http_field.cc
+        ../../service_inspectors/http_inspect/http_compress_stream.cc
     LIBS ${ZLIB_LIBRARIES}
 )
 add_cpputest( pub_sub_ftp_events_test
index 8402cfbe144108431e603631cea8de211a237c0d..fa87e709cd9b61a6c54c12766703c053c4d9f2b8 100644 (file)
@@ -67,8 +67,13 @@ const StreamBuffer StreamSplitter::reassemble(snort::Flow*, unsigned int, unsign
     return buf;
 }
 unsigned StreamSplitter::max(snort::Flow*) { return 0; }
+uint8_t TraceApi::get_constraints_generation() { return 0; }
+void TraceApi::filter(const Packet&) { }
+void trace_vprintf(const char*, TraceLevel, const char*, const snort::Packet*, const char*, va_list) { }
 }
 
+THREAD_LOCAL const snort::Trace* http_trace = nullptr;
+
 HttpParaList::UriParam::UriParam() { }
 HttpParaList::JsNormParam::~JsNormParam() { }
 HttpParaList::~HttpParaList() { }
index 062c28339dcf7f8973fa70ead8d8507af37fbda3..e4d32db07948cf470209ddea3be075af9770d0d3 100644 (file)
@@ -59,6 +59,8 @@ set (FILE_LIST
     http_module.h
     http_test_input.cc
     http_test_input.h
+    http_compress_stream.cc
+    http_compress_stream.h
     http_flow_data.cc
     http_flow_data.h
     http_context_data.cc
index b25574acf45d5b401375687af128e1ef715655c8..ca5c4cf645db269ce7a96943689fd0547bca1844 100644 (file)
@@ -40,7 +40,10 @@ Update: the previous sentence has been discovered to be incorrect. The memory re
 zlib are very large. It would save a lot of memory and some processing time for script detection
 to unzip one time in scan() and store the result for eventual use by reassemble(). The memory
 lost by storing partial message sections in HI while waiting for reassemble() would be more than
-compensated for by not having two instances of zlib.
+compensated for by not having two instances of zlib. Decompression now always happens
+once during scan() for all HTTP bodies, regardless of whether script detection is enabled.
+Doing decompression in scan() changes the order of events, so all decompression events are
+triggered after scan().
 
 For request bodies, when partial_depth_body parameter is set to a non zero value, a partial body
 will be subjected to partial inspection if its length is below partial_depth_body value. When
index 16b26e9432fc46b9ae3342646b22b28f1ea0ce26..cc32f061d5a7d62d1c03f54469ef3476c21b348e 100644 (file)
@@ -49,6 +49,11 @@ inline bool is_body(SectionType st)
 enum HXBodyState { HX_BODY_NOT_COMPLETE, HX_BODY_LAST_SEG, HX_BODY_COMPLETE,
     HX_BODY_COMPLETE_EXPECT_TRAILERS, HX_BODY_NO_BODY };
 
+enum
+{
+    TRACE_COMPRESS,
+};
+
 } // end namespace HttpCommon
 
 #endif
diff --git a/src/service_inspectors/http_inspect/http_compress_stream.cc b/src/service_inspectors/http_inspect/http_compress_stream.cc
new file mode 100644 (file)
index 0000000..b76e380
--- /dev/null
@@ -0,0 +1,319 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2026-2026 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// http_compress_stream.cc author Oleksandr Fedorych <ofedoryc@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "http_compress_stream.h"
+
+#include "http_common.h"
+#include "http_module.h"
+
+using namespace HttpEnums;
+using namespace HttpCommon;
+
+HttpCompressStream::HttpCompressStream()
+    : compress_stream { nullptr }
+    , gzip_header_bytes_processed { 0 }
+    , compression_id { CMP_NONE }
+    , gzip_state { GZIP_TBD }
+{ }
+
+HttpCompressStream::~HttpCompressStream()
+{
+    if ( compress_stream == nullptr )
+        return;
+
+    inflateEnd(compress_stream);
+    delete compress_stream;
+
+    debug_logf(http_trace, TRACE_COMPRESS, nullptr, "Compress: zlib cleared\n");
+}
+
+bool HttpCompressStream::setup(HttpEnums::CompressId compression)
+{
+    if ( compress_stream != nullptr )
+    {
+        assert(false);
+
+        compression_id = CMP_NONE;
+        delete compress_stream;
+        compress_stream = nullptr;
+
+        return false;
+    }
+
+    compression_id = compression;
+
+    switch ( compression )
+    {
+    case CMP_DEFLATE:
+    case CMP_GZIP:
+        compress_stream = new z_stream;
+
+        compress_stream->zalloc = Z_NULL;
+        compress_stream->zfree = Z_NULL;
+        compress_stream->opaque = Z_NULL;
+        compress_stream->next_in = Z_NULL;
+        compress_stream->avail_in = 0;
+
+        if ( const int window_bits = compression == CMP_GZIP ? GZIP_WINDOW_BITS : DEFLATE_WINDOW_BITS;
+            inflateInit2(compress_stream, window_bits) == Z_OK )
+        {
+            debug_logf(http_trace, TRACE_COMPRESS, nullptr, "Compress: zlib setup is successful\n");
+
+            return true;
+        }
+
+        assert(false);
+
+        compression_id = CMP_NONE;
+        delete compress_stream;
+        compress_stream = nullptr;
+
+        return false;
+    default:
+        assert(false);
+
+        compression_id = CMP_NONE;
+
+        return false;
+    }
+}
+
+std::optional<uint32_t> HttpCompressStream::decompress(const uint8_t* src, uint32_t src_size,
+    uint8_t* dst, uint32_t& dst_size, bool at_start,
+    HttpInfractions* infractions, HttpEventGen* const events)
+{
+    switch ( get_compression_id() )
+    {
+    case CMP_NONE:
+        return std::nullopt;
+    case CMP_DEFLATE:
+    case CMP_GZIP:
+        return decompress_zlib(src, src_size, dst, dst_size,
+            at_start, infractions, events);
+    default:
+        assert(false);
+        return std::nullopt;
+    }
+}
+
+uint8_t* HttpCompressStream::process_gzip_header(const uint8_t* data, uint32_t length,
+    HttpInfractions* const infractions, HttpEventGen* const events)
+{
+    uint32_t input_bytes_processed = 0;
+    uint8_t* modified_data = nullptr;
+
+    if ( gzip_state == GZIP_TBD )
+    {
+        static constexpr uint8_t gzip_magic[] = { 0x1f, 0x8b, 0x08 };
+        static constexpr uint8_t magic_length = 3;
+
+        const uint32_t magic_cmp_len = (magic_length - gzip_header_bytes_processed) < length ?
+            (magic_length - gzip_header_bytes_processed) : length;
+
+        if ( memcmp(data, gzip_magic + gzip_header_bytes_processed, magic_cmp_len) != 0 )
+            gzip_state = GZIP_MAGIC_BAD;
+        else if ( gzip_header_bytes_processed + length >= magic_length )
+            gzip_state = GZIP_MAGIC_GOOD;
+
+        gzip_header_bytes_processed += magic_cmp_len;
+        input_bytes_processed += magic_cmp_len;
+    }
+
+    if ( gzip_state == GZIP_MAGIC_GOOD and length > input_bytes_processed )
+    {
+        const uint8_t gzip_flags = data[input_bytes_processed];
+
+        if ( gzip_flags & GZIP_FLAG_FEXTRA )
+        {
+            *infractions += INF_GZIP_FEXTRA;
+            events->create_event(EVENT_GZIP_FEXTRA);
+        }
+
+        if ( gzip_flags & GZIP_RESERVED_FLAGS )
+        {
+            *infractions += INF_GZIP_RESERVED_FLAGS;
+            events->create_event(EVENT_GZIP_RESERVED_FLAGS);
+
+            modified_data = new uint8_t[length];
+            memcpy(modified_data, data, length);
+            modified_data[input_bytes_processed] &= ~GZIP_RESERVED_FLAGS;
+        }
+
+        gzip_header_bytes_processed++;
+        gzip_state = GZIP_FLAGS_PROCESSED;
+    }
+
+    return modified_data;
+}
+
+bool HttpCompressStream::gzip_header_check_done() const
+{
+    return gzip_state == HttpEnums::GZIP_MAGIC_BAD or
+           gzip_state == HttpEnums::GZIP_FLAGS_PROCESSED;
+}
+
+std::optional<std::uint32_t> HttpCompressStream::decompress_zlib(const uint8_t* src, uint32_t src_size,
+    uint8_t* dst, uint32_t& dst_size, bool at_start,
+    HttpInfractions* const infractions, HttpEventGen* const events)
+{
+    uint8_t* data_w_updated_hdr = nullptr;
+
+    if ( get_compression_id() == CMP_GZIP and !gzip_header_check_done() )
+        data_w_updated_hdr = process_gzip_header(src, src_size, infractions, events);
+
+    if ( data_w_updated_hdr != nullptr )
+        compress_stream->next_in = const_cast<Bytef*>(data_w_updated_hdr);
+    else
+        compress_stream->next_in = const_cast<Bytef*>(src);
+
+    compress_stream->avail_in = src_size;
+    compress_stream->next_out = dst + dst_size;
+    compress_stream->avail_out = MAX_OCTETS - dst_size;
+
+    const int result = inflate(compress_stream, Z_SYNC_FLUSH);
+
+    delete[] data_w_updated_hdr;
+
+    switch ( result )
+    {
+    case Z_OK:
+    case Z_STREAM_END:
+        dst_size = MAX_OCTETS - compress_stream->avail_out;
+
+        debug_logf(http_trace, TRACE_COMPRESS, nullptr, "Compress: decompressed %u/%u, used %u/%u\n",
+            src_size - compress_stream->avail_in, src_size, dst_size, MAX_OCTETS);
+
+        if ( compress_stream->avail_in > 0 )
+        {
+            if ( result == Z_STREAM_END )
+            {
+                // The zipped data stream ended but there is more input data
+                if ( get_compression_id() == CMP_GZIP )
+                {
+                    *infractions += INF_GZIP_EARLY_END;
+                    events->create_event(EVENT_GZIP_EARLY_END);
+                }
+                else
+                {
+                    *infractions += INF_DEFLATE_EARLY_END;
+                    events->create_event(EVENT_DEFLATE_EARLY_END);
+                }
+
+                const uInt num_copy = (compress_stream->avail_in <= compress_stream->avail_out) ?
+                    compress_stream->avail_in : compress_stream->avail_out;
+
+                memcpy(dst + dst_size, src + (src_size - compress_stream->avail_in), num_copy);
+                dst_size += num_copy;
+
+                debug_logf(http_trace, TRACE_COMPRESS, nullptr,
+                    "Compress: compressed data ended, copied %u, used %u/%u\n", num_copy, dst_size, MAX_OCTETS);
+
+                compress_stream->avail_in -= num_copy;
+                compression_id = CMP_NONE;
+            }
+            else
+            {
+                assert(compress_stream->avail_out == 0);
+
+                // The data expanded too much
+                debug_logf(http_trace, TRACE_COMPRESS, nullptr, "Compress: data caused buffer to overrun\n");
+            }
+
+            // FIXIT-E - Will need to clear gzip header processing state here when we implement
+            // processing multiple gzip members in a message section
+        }
+
+        return compress_stream->avail_in;
+    case Z_DATA_ERROR:
+        if ( get_compression_id() == CMP_DEFLATE and at_start )
+        {
+            // Some incorrect implementations of deflate don't use the expected header. Feed a
+            // dummy header to zlib and retry the inflate.
+            static constexpr uint8_t zlib_header[2] = { 0x78, 0x01 };
+
+            inflateReset(compress_stream);
+
+            compress_stream->next_in = const_cast<Bytef*>(zlib_header);
+            compress_stream->avail_in = sizeof(zlib_header);
+
+            const int ret = inflate(compress_stream, Z_SYNC_FLUSH);
+
+            if ( ret == Z_OK or ret == Z_STREAM_END )
+            {
+                debug_log(http_trace, TRACE_COMPRESS, nullptr, "Compress: deflate header substituted\n");
+
+                // Start over at the beginning
+                const auto decompressed = decompress_zlib(src, src_size, dst, dst_size, false, infractions, events);
+
+                if ( decompressed )
+                    HttpModule::increment_peg_counts(PEG_INCORRECT_DEFLATE_HEADER);
+
+                return decompressed;
+            }
+
+            assert(false);
+            return std::nullopt;
+        }
+
+        [[fallthrough]];
+    default:
+        if ( get_compression_id() == CMP_GZIP )
+        {
+            *infractions += INF_GZIP_FAILURE;
+            events->create_event(EVENT_GZIP_FAILURE);
+            HttpModule::increment_peg_counts(PEG_COMPRESSED_GZIP_FAILED);
+        }
+        else
+        {
+            *infractions += INF_DEFLATE_FAILURE;
+            events->create_event(EVENT_DEFLATE_FAILURE);
+            HttpModule::increment_peg_counts(PEG_COMPRESSED_DEFLATE_FAILED);
+        }
+
+        compression_id = CMP_NONE;
+
+        debug_logf(http_trace, TRACE_COMPRESS, nullptr, "Compress: decompress failed, %s\n",
+            compress_stream->msg ? compress_stream->msg : "unknown error");
+
+        return std::nullopt;
+    }
+}
+
+void HttpCompressStream::copy_compressed(const uint8_t* src, uint32_t src_size, uint8_t* dst, uint32_t& dst_size)
+{
+    copy_raw(src, src_size, dst, dst_size);
+
+    debug_logf(http_trace, TRACE_COMPRESS, nullptr, "Compress: data copied, used %u/%u\n", dst_size, MAX_OCTETS);
+}
+
+void HttpCompressStream::copy_raw(const uint8_t* src, uint32_t src_size, uint8_t* dst, uint32_t& dst_size)
+{
+    // The following precaution is necessary because mixed compressed and uncompressed data can
+    // cause the buffer to overrun even though we are not decompressing right now
+
+    if ( src_size > MAX_OCTETS - dst_size )
+        src_size = MAX_OCTETS - dst_size;
+
+    memcpy(dst + dst_size, src, src_size);
+    dst_size += src_size;
+}
diff --git a/src/service_inspectors/http_inspect/http_compress_stream.h b/src/service_inspectors/http_inspect/http_compress_stream.h
new file mode 100644 (file)
index 0000000..16fffc6
--- /dev/null
@@ -0,0 +1,66 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2026-2026 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// http_compress_stream.h author Oleksandr Fedorych <ofedoryc@cisco.com>
+
+#ifndef HTTP_COMPRESS_STREAM_H
+#define HTTP_COMPRESS_STREAM_H
+
+#include <optional>
+#include <zlib.h>
+
+#include "http_enum.h"
+#include "http_event.h"
+
+class HttpCompressStream
+{
+public:
+    HttpCompressStream();
+    HttpCompressStream(const HttpCompressStream& other) = delete;
+    HttpCompressStream& operator=(const HttpCompressStream& other) = delete;
+    HttpCompressStream(HttpCompressStream&& other) = delete;
+    HttpCompressStream& operator=(HttpCompressStream&& other) = delete;
+    ~HttpCompressStream();
+
+    bool setup(HttpEnums::CompressId compression);
+
+    std::optional<std::uint32_t> decompress(const uint8_t* src, uint32_t src_size,
+        uint8_t* dst, uint32_t& dst_size, bool at_start,
+        HttpInfractions* const infractions, HttpEventGen* const events);
+
+    void copy_compressed(const uint8_t* src, uint32_t src_size, uint8_t* dst, uint32_t& dst_size);
+    static void copy_raw(const uint8_t* src, uint32_t src_size, uint8_t* dst, uint32_t& dst_size);
+
+    HttpEnums::CompressId get_compression_id() const
+    { return compression_id; }
+
+private:
+    std::optional<std::uint32_t> decompress_zlib(const uint8_t* src, uint32_t src_size,
+        uint8_t* dst, uint32_t& dst_size, bool at_start,
+        HttpInfractions* const infractions, HttpEventGen* const events);
+
+    uint8_t* process_gzip_header(const uint8_t* data, uint32_t length,
+        HttpInfractions* const infractions, HttpEventGen* const events);
+    bool gzip_header_check_done() const;
+
+    z_stream* compress_stream;
+    uint32_t gzip_header_bytes_processed;
+    HttpEnums::CompressId compression_id;
+    HttpEnums::GzipVerificationState gzip_state;
+};
+
+#endif
index 4b54fea962043b10c56d5e79dc785192fcdd6ec2..2cf6fa43c8b7944c06bdab5d87639b207bd32cba 100644 (file)
@@ -24,6 +24,7 @@
 #include "http_cutter.h"
 
 #include "http_common.h"
+#include "http_compress_stream.h"
 #include "http_enum.h"
 #include "http_flow_data.h"
 #include "http_module.h"
@@ -297,30 +298,41 @@ ScanResult HttpHeaderCutter::cut(const uint8_t* buffer, uint32_t length,
     return SCAN_NOT_FOUND;
 }
 
+HttpBodyCutter::DecompressOutput HttpBodyCutter::decompress(const uint8_t* src, uint32_t src_size,
+    HttpInfractions* infractions, HttpEventGen* events)
+{
+    auto* compress = session_data->compress[source_id];
+
+    if ( compress == nullptr )
+        return { Buffer { src, src_size }, src_size };
+
+    auto& dst = session_data->partial_buffer[source_id];
+    auto& dst_size = session_data->partial_buffer_length[source_id];
+
+    if ( dst == nullptr )
+        dst = new uint8_t[MAX_OCTETS];
+
+    const bool at_start = (octets_seen + session_data->partial_raw_bytes[source_id]) == 0 and dst_size == 0;
+
+    const auto prev_size = dst_size;
+
+    if ( const auto ret = compress->decompress(src, src_size, dst, dst_size, at_start, infractions, events) )
+        return { Buffer { dst + prev_size, dst_size - prev_size }, src_size - *ret };
+
+    compress->copy_compressed(src, src_size, dst, dst_size);
+
+    return { std::nullopt, src_size };
+}
+
 HttpBodyCutter::HttpBodyCutter(bool accelerated_blocking_, ScriptFinder* finder_,
-    CompressId compression_)
-    : accelerated_blocking(accelerated_blocking_), compression(compression_), finder(finder_)
+    HttpFlowData* const session_data, SourceId source_id)
+    : accelerated_blocking(accelerated_blocking_)
+    , finder(finder_)
+    , session_data(session_data)
+    , source_id(source_id)
 {
-    if (accelerated_blocking)
+    if ( accelerated_blocking )
     {
-        if ((compression == CMP_GZIP) || (compression == CMP_DEFLATE))
-        {
-            compress_stream = new z_stream();
-            compress_stream->zalloc = Z_NULL;
-            compress_stream->zfree = Z_NULL;
-            compress_stream->next_in = Z_NULL;
-            compress_stream->avail_in = 0;
-            const int window_bits = (compression == CMP_GZIP) ?
-                GZIP_WINDOW_BITS : DEFLATE_WINDOW_BITS;
-            if (inflateInit2(compress_stream, window_bits) != Z_OK)
-            {
-                assert(false);
-                compression = CMP_NONE;
-                delete compress_stream;
-                compress_stream = nullptr;
-            }
-        }
-
         static const uint8_t inspect_string[] = { '<', '/', 's', 'c', 'r', 'i', 'p', 't', '>' };
         static const uint8_t inspect_upper[] = { '<', '/', 'S', 'C', 'R', 'I', 'P', 'T', '>' };
 
@@ -330,17 +342,9 @@ HttpBodyCutter::HttpBodyCutter(bool accelerated_blocking_, ScriptFinder* finder_
     }
 }
 
-HttpBodyCutter::~HttpBodyCutter()
-{
-    if (compress_stream != nullptr)
-    {
-        inflateEnd(compress_stream);
-        delete compress_stream;
-    }
-}
-
-ScanResult HttpBodyClCutter::cut(const uint8_t* buffer, uint32_t length, HttpInfractions*,
-    HttpEventGen*, uint32_t flow_target, bool stretch, HXBodyState)
+ScanResult HttpBodyClCutter::cut(const uint8_t* buffer, uint32_t length,
+    HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch,
+    HXBodyState)
 {
     assert(remaining > octets_seen);
 
@@ -370,54 +374,74 @@ ScanResult HttpBodyClCutter::cut(const uint8_t* buffer, uint32_t length, HttpInf
 
     if (octets_seen + length < flow_target)
     {
+        const auto [accelerate, consumed] = analyze_body(buffer, length, infractions, events);
+
+        if ( consumed < length )
+        {
+            num_flush = consumed;
+            remaining -= octets_seen + num_flush;
+            return SCAN_FOUND_PIECE;
+        }
+
         octets_seen += length;
-        return need_accelerated_blocking(buffer, length) ?
-            SCAN_NOT_FOUND_ACCELERATE : SCAN_NOT_FOUND;
+        return accelerate ? SCAN_NOT_FOUND_ACCELERATE : SCAN_NOT_FOUND;
     }
 
     if (!stretch)
     {
-        remaining -= flow_target;
-        num_flush = flow_target - octets_seen;
-        if (remaining > 0)
-        {
-            need_accelerated_blocking(buffer, num_flush);
-            return SCAN_FOUND_PIECE;
-        }
-        else
-            return SCAN_FOUND;
+        assert(flow_target >= octets_seen);
+        const uint32_t planned_flush = flow_target - octets_seen;
+        const auto [_, consumed] = analyze_body(buffer, planned_flush, infractions, events);
+
+        num_flush = consumed;
+        remaining -= octets_seen + num_flush;
+
+        return remaining > 0 ? SCAN_FOUND_PIECE : SCAN_FOUND;
     }
 
     if (octets_seen + length < remaining)
     {
         // The message body continues beyond this segment
         // Stretch the section to include this entire segment provided it is not too big
+        uint32_t planned_flush = 0;
         if (octets_seen + length <= flow_target + MAX_SECTION_STRETCH)
-            num_flush = length;
+            planned_flush = length;
         else
-            num_flush = flow_target - octets_seen;
+            planned_flush = flow_target - octets_seen;
+
+        const auto [_, consumed] = analyze_body(buffer, planned_flush, infractions, events);
+
+        num_flush = consumed;
         remaining -= octets_seen + num_flush;
-        need_accelerated_blocking(buffer, num_flush);
+
         return SCAN_FOUND_PIECE;
     }
 
     if (remaining - flow_target <= MAX_SECTION_STRETCH)
     {
         // Stretch the section to finish the message body
-        num_flush = remaining - octets_seen;
-        remaining = 0;
-        return SCAN_FOUND;
+        const uint32_t planned_flush = remaining - octets_seen;
+        const auto [_, consumed] = decompress(buffer, planned_flush, infractions, events);
+
+        num_flush = consumed;
+        remaining -= octets_seen + num_flush;
+
+        return remaining > 0 ? SCAN_FOUND_PIECE : SCAN_FOUND;
     }
 
     // Cannot stretch to the end of the message body. Cut at the original target.
-    num_flush = flow_target - octets_seen;
-    remaining -= flow_target;
-    need_accelerated_blocking(buffer, num_flush);
+    const uint32_t planned_flush = flow_target - octets_seen;
+    const auto [_, consumed] = decompress(buffer, planned_flush, infractions, events);
+
+    num_flush = consumed;
+    remaining -= octets_seen + num_flush;
+
     return SCAN_FOUND_PIECE;
 }
 
-ScanResult HttpBodyOldCutter::cut(const uint8_t* buffer, uint32_t length, HttpInfractions*,
-    HttpEventGen*, uint32_t flow_target, bool stretch, HXBodyState)
+ScanResult HttpBodyOldCutter::cut(const uint8_t* buffer, uint32_t length,
+    HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch,
+    HXBodyState)
 {
     if (flow_target == 0)
     {
@@ -432,24 +456,37 @@ ScanResult HttpBodyOldCutter::cut(const uint8_t* buffer, uint32_t length, HttpIn
 
     if (octets_seen + length < flow_target)
     {
+        const auto [accelerate, consumed] = analyze_body(buffer, length, infractions, events);
+
+        if ( consumed < length )
+        {
+            num_flush = consumed;
+            return SCAN_FOUND_PIECE;
+        }
+
         // Not enough data yet to create a message section
         octets_seen += length;
-        return need_accelerated_blocking(buffer, length) ?
-            SCAN_NOT_FOUND_ACCELERATE : SCAN_NOT_FOUND;
+        return accelerate ? SCAN_NOT_FOUND_ACCELERATE : SCAN_NOT_FOUND;
     }
     else if (stretch && (octets_seen + length <= flow_target + MAX_SECTION_STRETCH))
     {
         // Cut the section at the end of this TCP segment to avoid splitting a packet
-        num_flush = length;
-        need_accelerated_blocking(buffer, num_flush);
+        const uint32_t planned_flush = length;
+        const auto [_, consumed] = analyze_body(buffer, planned_flush, infractions, events);
+
+        num_flush = consumed;
+
         return SCAN_FOUND_PIECE;
     }
     else
     {
         // Cut the section at the target length. Either stretching is not allowed or the end of
         // the segment is too far away.
-        num_flush = flow_target - octets_seen;
-        need_accelerated_blocking(buffer, num_flush);
+        const uint32_t planned_flush = flow_target - octets_seen;
+        const auto [_, consumed] = analyze_body(buffer, planned_flush, infractions, events);
+
+        num_flush = consumed;
+
         return SCAN_FOUND_PIECE;
     }
 }
@@ -659,8 +696,23 @@ ScanResult HttpBodyChunkCutter::cut(const uint8_t* buffer, uint32_t length,
                 skip_amount = adjusted_target-data_seen;
             }
 
-            accelerate_this_packet = need_accelerated_blocking(buffer+k, skip_amount) ||
-                accelerate_this_packet;
+            if ( accelerated_blocking or !discard_mode )
+            {
+                const auto [accelerate, consumed] = analyze_body(buffer + k, skip_amount,
+                    infractions, events);
+                accelerate_this_packet = accelerate or accelerate_this_packet;
+
+                if ( consumed < skip_amount and !discard_mode )
+                {
+                    expected -= consumed;
+
+                    assert(expected != 0);
+
+                    num_flush = k + consumed;
+                    data_seen = 0;
+                    return SCAN_FOUND_PIECE;
+                }
+            }
 
             k += skip_amount - 1;
             if ((expected -= skip_amount) == 0)
@@ -671,7 +723,7 @@ ScanResult HttpBodyChunkCutter::cut(const uint8_t* buffer, uint32_t length,
             {
                 data_seen = 0;
                 num_flush = k+1;
-                return SCAN_FOUND_PIECE;
+                return discard_mode ? SCAN_DISCARD_PIECE : SCAN_FOUND_PIECE;
             }
             break;
           }
@@ -731,11 +783,22 @@ ScanResult HttpBodyChunkCutter::cut(const uint8_t* buffer, uint32_t length,
             // planned to delete them during reassembly. Because they are not part of a valid chunk
             // they will be reassembled after all. This will overrun the adjusted_target making the
             // message section a little bigger than planned. It's not important.
+          {
             uint32_t skip_amount = length-k;
             skip_amount = (skip_amount <= adjusted_target-data_seen) ? skip_amount :
                 adjusted_target-data_seen;
-            accelerate_this_packet = need_accelerated_blocking(buffer+k, skip_amount) ||
-                accelerate_this_packet;
+
+            const auto [accelerate, consumed] = analyze_body(buffer + k,
+                skip_amount, infractions, events);
+            accelerate_this_packet = accelerate or accelerate_this_packet;
+
+            if ( consumed < skip_amount )
+            {
+                num_flush = k + consumed;
+                data_seen = 0;
+                return SCAN_FOUND_PIECE;
+            }
+
             k += skip_amount - 1;
             if ((data_seen += skip_amount) == adjusted_target)
             {
@@ -744,6 +807,7 @@ ScanResult HttpBodyChunkCutter::cut(const uint8_t* buffer, uint32_t length,
                 return SCAN_FOUND_PIECE;
             }
             break;
+          }
         }
     }
     if (discard_mode)
@@ -769,9 +833,8 @@ ScanResult HttpBodyChunkCutter::cut(const uint8_t* buffer, uint32_t length,
     return SCAN_NOT_FOUND;
 }
 
-ScanResult HttpBodyHXCutter::cut(const uint8_t* buffer, uint32_t length,
-    HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch,
-    HXBodyState state)
+ScanResult HttpBodyHXCutter::cut(const uint8_t* buffer, uint32_t length, HttpInfractions* infractions,
+    HttpEventGen* events, uint32_t flow_target, bool stretch, HXBodyState state)
 {
     // If the headers included a content length header (expected length >= 0), check it against the
     // actual message body length. Alert if it does not match at the end of the message body or if
@@ -807,31 +870,49 @@ ScanResult HttpBodyHXCutter::cut(const uint8_t* buffer, uint32_t length,
         if (octets_seen + length < flow_target)
         {
             // Not enough data yet to create a message section
+            const auto [accelerate, consumed] = analyze_body(buffer, length, infractions, events);
+
+            if ( consumed < length )
+            {
+                num_flush = consumed;
+                total_octets_scanned += num_flush;
+                return SCAN_FOUND_PIECE;
+            }
+
             octets_seen += length;
             total_octets_scanned += length;
-            return need_accelerated_blocking(buffer, length) ?
-                SCAN_NOT_FOUND_ACCELERATE : SCAN_NOT_FOUND;
+            return accelerate ? SCAN_NOT_FOUND_ACCELERATE : SCAN_NOT_FOUND;
         }
         else
         {
+            uint32_t planned_flush = 0;
             if (stretch && (octets_seen + length <= flow_target + MAX_SECTION_STRETCH))
-                num_flush = length;
+                planned_flush = length;
             else
-                num_flush = flow_target - octets_seen;
+                planned_flush = flow_target - octets_seen;
+
+            const auto [_, consumed] = analyze_body(buffer, planned_flush, infractions, events);
+
+            num_flush = consumed;
             total_octets_scanned += num_flush;
-            need_accelerated_blocking(buffer, num_flush);
+
             return SCAN_FOUND_PIECE;
         }
     }
     else if (state == HX_BODY_LAST_SEG)
     {
         const uint32_t adjusted_target = stretch ? MAX_SECTION_STRETCH + flow_target : flow_target;
+        uint32_t planned_flush = 0;
         if (octets_seen + length <= adjusted_target)
-            num_flush = length;
+            planned_flush = length;
         else
-            num_flush = flow_target - octets_seen;
+            planned_flush = flow_target - octets_seen;
+
+        const auto [_, consumed] = decompress(buffer, planned_flush, infractions, events);
 
+        num_flush = consumed;
         total_octets_scanned += num_flush;
+
         if (num_flush == length)
             return SCAN_FOUND;
         else
@@ -850,18 +931,32 @@ ScanResult HttpBodyHXCutter::cut(const uint8_t* buffer, uint32_t length,
 // This method searches the input stream looking for a script or other dangerous content that
 // requires script detection. Exactly what we are looking for is encapsulated in dangerous().
 //
-// Return value true indicates a match and enables the packet that completes the matching sequence
-// to be sent for partial inspection.
+// Return value FlushContext contains:
+//   accelerate: true indicates a match and enables the packet that completes the matching sequence
+//               to be sent for partial inspection.
+//   consumed: how many input bytes the decompressor actually processed. When less than the
+//             input length the decompressor buffer is full and the caller should flush early.
 //
 // Any attempt to optimize this code should be mindful that once you skip any part of the message
 // body, dangerous() loses the ability to unzip subsequent data.
 
-bool HttpBodyCutter::need_accelerated_blocking(const uint8_t* data, uint32_t length)
+HttpBodyCutter::FlushContext HttpBodyCutter::analyze_body(const uint8_t* data, uint32_t length,
+    HttpInfractions* infractions, HttpEventGen* events)
 {
-    const bool need_accelerated_blocking = accelerated_blocking && dangerous(data, length);
-    if (need_accelerated_blocking)
+    if ( !accelerated_blocking )
+    {
+        const auto [_, consumed] = decompress(data, length, infractions, events);
+        assert(consumed <= length);
+        return { false, consumed };
+    }
+
+    const auto result = dangerous(data, length, infractions, events);
+    assert(result.consumed <= length);
+
+    if ( result.accelerate )
         HttpModule::increment_peg_counts(PEG_SCRIPT_DETECTION);
-    return need_accelerated_blocking;
+
+    return result;
 }
 
 bool HttpBodyCutter::find_partial(const uint8_t* input_buf, uint32_t input_length, bool end)
@@ -889,52 +984,23 @@ bool HttpBodyCutter::find_partial(const uint8_t* input_buf, uint32_t input_lengt
 }
 
 // Currently we do accelerated blocking when we see a javascript
-bool HttpBodyCutter::dangerous(const uint8_t* data, uint32_t length)
+HttpBodyCutter::FlushContext HttpBodyCutter::dangerous(const uint8_t* data, uint32_t length,
+    HttpInfractions* infractions, HttpEventGen* events)
 {
-    const uint8_t* input_buf = data;
-    uint32_t input_length = length;
-    uint8_t* decomp_output = nullptr;
-
-    // Zipped flows must be decompressed before we can check them. Unzipping for accelerated
-    // blocking is completely separate from the unzipping done later in reassemble().
-    if ((compression == CMP_GZIP) || (compression == CMP_DEFLATE))
-    {
-        // Previous decompression failures make it impossible to search for scripts
-        if (decompress_failed)
-            return true;
+    const auto [decompressed, consumed] = decompress(data, length, infractions, events);
 
-        const uint32_t decomp_buffer_size = MAX_OCTETS;
-        decomp_output = new uint8_t[decomp_buffer_size];
+    if ( !decompressed )
+        return { true, consumed };
 
-        compress_stream->next_in = const_cast<Bytef*>(data);
-        compress_stream->avail_in = length;
-        compress_stream->next_out = decomp_output;
-        compress_stream->avail_out = decomp_buffer_size;
-
-        int ret_val = inflate(compress_stream, Z_SYNC_FLUSH);
-
-        // Not going to be subtle about this and try to fix decompression problems. If it doesn't
-        // work out we assume it could be dangerous.
-        if (((ret_val != Z_OK) && (ret_val != Z_STREAM_END)) || (compress_stream->avail_in > 0))
-        {
-            decompress_failed = true;
-            delete[] decomp_output;
-            return true;
-        }
-
-        input_buf = decomp_output;
-        input_length = decomp_buffer_size - compress_stream->avail_out;
-    }
-
-    std::unique_ptr<uint8_t[]> uniq(decomp_output);
+    auto [input_buf, input_length] = *decompressed;
 
     if ( input_length > string_length )
     {
         if ( partial_match and find_partial(input_buf, input_length, true) )
-            return true;
+            return { true, consumed };
 
         if ( finder->search(input_buf, input_length) >= 0 )
-            return true;
+            return { true, consumed };
 
         uint32_t delta = input_length - string_length + 1;
         input_buf += delta;
@@ -942,9 +1008,9 @@ bool HttpBodyCutter::dangerous(const uint8_t* data, uint32_t length)
     }
 
     if ( find_partial(input_buf, input_length, false) )
-        return true;
+        return { true, consumed };
 
-    return false;
+    return { false, consumed };
 }
 
 uint8_t HttpZeroNineCutter::match[] = { 'H', 'T', 'T', 'P', '/' };
index 25048b30989c9c9d6ff785df85b9db7081adaa5e..37f4a34749cb3370aea3fdeebceaaabea79054ac 100644 (file)
@@ -21,7 +21,7 @@
 #define HTTP_CUTTER_H
 
 #include <cassert>
-#include <zlib.h>
+#include <optional>
 
 #include "http_common.h"
 #include "http_enum.h"
@@ -119,39 +119,55 @@ private:
 class HttpBodyCutter : public HttpCutter
 {
 public:
+    using Buffer = std::pair<const uint8_t*, uint32_t>;
+
     HttpBodyCutter(bool accelerated_blocking_, ScriptFinder* finder,
-        HttpEnums::CompressId compression_);
-    ~HttpBodyCutter() override;
+        HttpFlowData* const session_data, HttpCommon::SourceId source_id);
     void soft_reset() override { octets_seen = 0; }
 
+private:
+    struct FlushContext
+    {
+        bool accelerate = false;
+        uint32_t consumed = 0;
+    };
+
+    struct DecompressOutput
+    {
+        std::optional<Buffer> decompressed;
+        uint32_t consumed = 0;
+    };
+
 protected:
-    bool need_accelerated_blocking(const uint8_t* data, uint32_t length);
+    FlushContext analyze_body(const uint8_t* data, uint32_t length,
+        HttpInfractions* infractions, HttpEventGen* events);
+    DecompressOutput decompress(const uint8_t* src, uint32_t src_size,
+        HttpInfractions* infractions, HttpEventGen* events);
 
+    const bool accelerated_blocking;
 private:
-    bool dangerous(const uint8_t* data, uint32_t length);
+    FlushContext dangerous(const uint8_t* data, uint32_t length,
+        HttpInfractions* infractions, HttpEventGen* events);
     bool find_partial(const uint8_t*, uint32_t, bool);
 
-    const bool accelerated_blocking;
     uint8_t partial_match = 0;
-    HttpEnums::CompressId compression = HttpEnums::CompressId::CMP_NONE;
-    bool decompress_failed = false;
     uint8_t string_length = 0;
-    z_stream* compress_stream = nullptr;
     ScriptFinder* const finder;
     const uint8_t* match_string = nullptr;
     const uint8_t* match_string_upper = nullptr;
+    HttpFlowData* const session_data;
+    const HttpCommon::SourceId source_id;
 };
 
 class HttpBodyClCutter : public HttpBodyCutter
 {
 public:
-    HttpBodyClCutter(int64_t expected_length,
-        bool accelerated_blocking,
-        ScriptFinder* finder,
-        HttpEnums::CompressId compression) :
-        HttpBodyCutter(accelerated_blocking, finder, compression),
-        remaining(expected_length)
-        { assert(remaining > 0); }
+    HttpBodyClCutter(int64_t expected_length, bool accelerated_blocking_, ScriptFinder* finder,
+        HttpFlowData* const session_data, HttpCommon::SourceId source_id)
+        : HttpBodyCutter(accelerated_blocking_, finder, session_data, source_id)
+        , remaining(expected_length)
+    { assert(remaining > 0); }
+
     HttpEnums::ScanResult cut(const uint8_t*, uint32_t length, HttpInfractions*, HttpEventGen*,
         uint32_t flow_target, bool stretch, HttpCommon::HXBodyState) override;
 
@@ -162,10 +178,10 @@ private:
 class HttpBodyOldCutter : public HttpBodyCutter
 {
 public:
-    HttpBodyOldCutter(bool accelerated_blocking, ScriptFinder* finder,
-        HttpEnums::CompressId compression) :
-        HttpBodyCutter(accelerated_blocking, finder, compression)
-        {}
+    HttpBodyOldCutter(bool accelerated_blocking_, ScriptFinder* finder,
+        HttpFlowData* const session_data, HttpCommon::SourceId source_id)
+        : HttpBodyCutter(accelerated_blocking_, finder, session_data, source_id)
+    }
     HttpEnums::ScanResult cut(const uint8_t*, uint32_t, HttpInfractions*, HttpEventGen*,
         uint32_t flow_target, bool stretch, HttpCommon::HXBodyState) override;
 };
@@ -173,11 +189,12 @@ public:
 class HttpBodyChunkCutter : public HttpBodyCutter
 {
 public:
-    HttpBodyChunkCutter(int64_t maximum_chunk_length_, bool accelerated_blocking,
-        ScriptFinder* finder, HttpEnums::CompressId compression) :
-        HttpBodyCutter(accelerated_blocking, finder, compression),
-        maximum_chunk_length(maximum_chunk_length_)
-        {}
+    HttpBodyChunkCutter(int64_t maximum_chunk_length_, bool accelerated_blocking_, ScriptFinder* finder,
+        HttpFlowData* const session_data, HttpCommon::SourceId source_id)
+        : HttpBodyCutter(accelerated_blocking_, finder, session_data, source_id)
+        , maximum_chunk_length(maximum_chunk_length_)
+    { }
+
     HttpEnums::ScanResult cut(const uint8_t* buffer, uint32_t length,
         HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch,
         HttpCommon::HXBodyState) override;
@@ -203,11 +220,11 @@ private:
 class HttpBodyHXCutter : public HttpBodyCutter
 {
 public:
-    HttpBodyHXCutter(int64_t expected_length, bool accelerated_blocking, ScriptFinder* finder,
-        HttpEnums::CompressId compression) :
-        HttpBodyCutter(accelerated_blocking, finder, compression),
-            expected_body_length(expected_length)
-        {}
+    HttpBodyHXCutter(int64_t expected_length, bool accelerated_blocking_, ScriptFinder* finder,
+        HttpFlowData* const session_data, HttpCommon::SourceId source_id)
+        : HttpBodyCutter(accelerated_blocking_, finder, session_data, source_id)
+        , expected_body_length(expected_length)
+    }
     HttpEnums::ScanResult cut(const uint8_t* buffer, uint32_t length, HttpInfractions*,
         HttpEventGen*, uint32_t flow_target, bool stretch, HttpCommon::HXBodyState state) override;
 private:
index 98fb8d9cfb83b96c5d0e486ce1babcac47ed9c45..81cffcfb512a0a7f6e0b1067e43906516fe224cb 100755 (executable)
@@ -72,7 +72,8 @@ enum PEG_COUNT { PEG_FLOW = 0, PEG_SCAN, PEG_REASSEMBLE, PEG_INSPECT, PEG_REQUES
     PEG_CONCURRENT_SESSIONS, PEG_MAX_CONCURRENT_SESSIONS, PEG_SCRIPT_DETECTION,
     PEG_PARTIAL_INSPECT, PEG_EXCESS_PARAMS, PEG_PARAMS, PEG_CUTOVERS, PEG_SSL_SEARCH_ABND_EARLY,
     PEG_PIPELINED_FLOWS, PEG_PIPELINED_REQUESTS, PEG_TOTAL_BYTES, PEG_JS_INLINE, PEG_JS_EXTERNAL,
-    PEG_JS_PDF, PEG_SKIP_MIME_ATTACH, PEG_COMPRESSED_GZIP, PEG_COMPRESSED_NOT_SUPPORTED,
+    PEG_JS_PDF, PEG_SKIP_MIME_ATTACH, PEG_COMPRESSED_GZIP, PEG_COMPRESSED_GZIP_FAILED, PEG_COMPRESSED_DEFLATE,
+    PEG_INCORRECT_DEFLATE_HEADER, PEG_COMPRESSED_DEFLATE_FAILED, PEG_COMPRESSED_NOT_SUPPORTED,
     PEG_COMPRESSED_UNKNOWN, PEG_MAX_PUBLISH_DEPTH_HITS, PEG_COUNT_MAX};
 
 // Result of scanning by splitter
@@ -222,7 +223,7 @@ enum Infraction
     INF_PARTIAL_START = 51,
     INF_CHUNK_WHITESPACE = 52,
     INF_HEAD_NAME_WHITESPACE = 53,
-    INF_GZIP_OVERRUN = 54,
+    // INF_GZIP_OVERRUN = 54,                    // Retired. Do not reuse this number
     INF_GZIP_FAILURE = 55,
     INF_GZIP_EARLY_END = 56,
     INF_URI_NEED_NORM_PATH = 57,
@@ -301,6 +302,8 @@ enum Infraction
     INF_METHOD_ON_DISALLOWED_LIST = 137,
     INF_PIPELINE_MAX = 138,
     INF_GZIP_RESERVED_FLAGS = 139,
+    INF_DEFLATE_EARLY_END = 140,
+    INF_DEFLATE_FAILURE = 141,
     INF__MAX_VALUE
 };
 
@@ -376,7 +379,7 @@ enum EventSid
     EVENT_BROKEN_CHUNK = 213,
     EVENT_CHUNK_WHITESPACE = 214,
     EVENT_HEAD_NAME_WHITESPACE = 215,
-    EVENT_GZIP_OVERRUN = 216,
+    // EVENT_GZIP_OVERRUN = 216,                 // Retired. Do not reuse this number
     EVENT_GZIP_FAILURE = 217,
     EVENT_ZERO_NINE_CONTINUE = 218,
     EVENT_ZERO_NINE_NOT_FIRST = 219,
@@ -450,6 +453,8 @@ enum EventSid
     EVENT_DISALLOWED_METHOD = 287,
     EVENT_GZIP_RESERVED_FLAGS = 288,
     EVENT_MAX_PARTIAL_FLUSH = 289,
+    EVENT_DEFLATE_EARLY_END = 290,
+    EVENT_DEFLATE_FAILURE = 291,
     EVENT__MAX_VALUE
 };
 
index 9073167477f0d61fec48850c075b4d372c7a0f64..7f951b96f16d049e584bbeb1e5d3aff2f7174b5e 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "http_cutter.h"
 #include "http_common.h"
+#include "http_compress_stream.h"
 #include "http_enum.h"
 #include "http_js_norm.h"
 #include "http_module.h"
@@ -101,14 +102,10 @@ HttpFlowData::~HttpFlowData()
         delete[] section_buffer[k];
         delete[] partial_buffer[k];
         delete[] partial_detect_buffer[k];
+        delete compress[k];
         delete partial_mime_bufs[k];
         HttpTransaction::delete_transaction(transaction[k], nullptr);
         delete cutter[k];
-        if (compress_stream[k] != nullptr)
-        {
-            inflateEnd(compress_stream[k]);
-            delete compress_stream[k];
-        }
         delete mime_state[k];
         delete utf_state[k];
         if (fd_state[k] != nullptr)
@@ -148,15 +145,8 @@ void HttpFlowData::half_reset(SourceId source_id)
     detect_depth_remaining[source_id] = STAT_NOT_PRESENT;
     publish_depth_remaining[source_id] = STAT_NOT_PRESENT;
 
-    compression[source_id] = CMP_NONE;
-    gzip_state[source_id] = GZIP_TBD;
-    gzip_header_bytes_processed[source_id] = 0;
-    if (compress_stream[source_id] != nullptr)
-    {
-        inflateEnd(compress_stream[source_id]);
-        delete compress_stream[source_id];
-        compress_stream[source_id] = nullptr;
-    }
+    delete compress[source_id];
+    compress[source_id] = nullptr;
     delete mime_state[source_id];
     mime_state[source_id] = nullptr;
     delete utf_state[source_id];
@@ -190,13 +180,8 @@ void HttpFlowData::half_reset(SourceId source_id)
 void HttpFlowData::trailer_prep(SourceId source_id)
 {
     type_expected[source_id] = SEC_TRAILER;
-    compression[source_id] = CMP_NONE;
-    if (compress_stream[source_id] != nullptr)
-    {
-        inflateEnd(compress_stream[source_id]);
-        delete compress_stream[source_id];
-        compress_stream[source_id] = nullptr;
-    }
+    delete compress[source_id];
+    compress[source_id] = nullptr;
     delete mime_state[source_id];
     mime_state[source_id] = nullptr;
     delete utf_state[source_id];
index be679e0174196d82a0647bbaba5f5e86f8f8600c..e4a929abfc0b6b60c707053a72256918ceac4bc4 100644 (file)
@@ -40,6 +40,7 @@ class HttpJSNorm;
 class HttpMsgSection;
 class HttpCutter;
 class HttpQueryParser;
+class HttpCompressStream;
 
 namespace snort
 {
@@ -111,9 +112,6 @@ private:
     uint32_t partial_raw_bytes[2] = { 0, 0 };
     uint8_t* partial_buffer[2] = { nullptr, nullptr };
     uint32_t partial_buffer_length[2] = { 0, 0 };
-    uint32_t gzip_header_bytes_processed[2] = { 0, 0 };
-    HttpEnums::GzipVerificationState gzip_state[2] = { HttpEnums::GZIP_TBD, HttpEnums::GZIP_TBD };
-    bool gzip_header_check_done();
 
     // *** StreamSplitter internal data - scan() => reassemble()
     uint32_t num_excess[2] = { 0, 0 };
@@ -131,6 +129,7 @@ private:
     bool partial_flush[2] = { false, false };
     uint64_t last_connect_trans_w_early_traffic = 0;
 
+    HttpCompressStream* compress[2] = { nullptr, nullptr };
     HttpInfractions* infractions[2] = { new HttpInfractions, new HttpInfractions };
     HttpEventGen* events[2] = { new HttpEventGen, new HttpEventGen };
 
@@ -146,12 +145,10 @@ private:
     bool last_request_was_connect = false;
     bool stretch_section_to_packet[2] = { false, false };
     bool accelerated_blocking[2] = { false, false };
-    z_stream* compress_stream[2] = { nullptr, nullptr };
     uint64_t zero_nine_expected = 0;
     // length of the data from Content-Length field
     int64_t data_length[2] = { HttpCommon::STAT_NOT_PRESENT, HttpCommon::STAT_NOT_PRESENT };
     uint32_t section_size_target[2] = { 0, 0 };
-    HttpEnums::CompressId compression[2] = { HttpEnums::CMP_NONE, HttpEnums::CMP_NONE };
 
     // *** Inspector's internal data about the current message
     struct FdCallbackContext
index 54e80337eec1505a5f07cf2bdc12489ffad5f9f7..508d395db0ab054c877bc210b6ef1b07bdedec9b 100755 (executable)
 
 using namespace snort;
 using namespace HttpEnums;
+using namespace HttpCommon;
+
+THREAD_LOCAL const Trace* http_trace = nullptr;
+
+#ifdef DEBUG_MSGS
+static const TraceOption http_trace_options[] =
+{
+    { "compress", TRACE_COMPRESS, "enable compress trace logging" },
+    { nullptr, 0, nullptr }
+};
+#endif
 
 HttpModule::HttpModule() : Module(HTTP_NAME, HTTP_HELP, http_params),
     script_detection_handle(LiteralSearch::setup())
@@ -495,6 +506,14 @@ HttpParaList::JsNormParam::~JsNormParam()
     delete mpse_attr;
 }
 
+void HttpModule::set_trace(const snort::Trace* trace) const
+{ http_trace = trace; }
+
+#ifdef DEBUG_MSGS
+const snort::TraceOption* HttpModule::get_trace_options() const
+{ return http_trace_options; }
+#endif
+
 void HttpParaList::JsNormParam::configure() const
 {
     mpse_otag = js_create_mpse_open_tag();
index 6d9f4ce1e0b4600c8adf376d79edb1ad735eacca..97f558495050570fec3c326ad9603cac96538c37 100755 (executable)
@@ -30,6 +30,7 @@
 #include "mime/file_mime_config.h"
 #include "profiler/profiler.h"
 #include "search_engines/search_tool.h"
+#include "trace/trace_api.h"
 
 #include "http_enum.h"
 #include "http_str_to_code.h"
@@ -37,6 +38,8 @@
 #define HTTP_NAME "http_inspect"
 #define HTTP_HELP "HTTP inspector"
 
+extern THREAD_LOCAL const snort::Trace* http_trace;
+
 namespace snort
 {
 class Trace;
@@ -187,6 +190,12 @@ public:
     static snort::ProfileStats& get_profile_stats()
     { return http_profile; }
 
+    void set_trace(const snort::Trace*) const override;
+
+#ifdef DEBUG_MSGS
+    const snort::TraceOption* get_trace_options() const override;
+#endif
+
     Usage get_usage() const override
     { return INSPECT; }
 
index 2d18ef0b80301da18d94f54d266ab93e8e01acfc..4796a72de3dedd265cd988cc49d2c11c48f7a3a1 100644 (file)
@@ -63,7 +63,6 @@ static void init_decode_infs()
     decode_infs += INF_STACKED_ENCODINGS;
     decode_infs += INF_CONTENT_ENCODING_CHUNKED;
     decode_infs += INF_GZIP_FAILURE;
-    decode_infs += INF_GZIP_OVERRUN;
 }
 
 static int _init_decode_infs __attribute__((unused)) = (static_cast<void>(init_decode_infs()), 0);
index f16d7bb053deae252057b0b2e359f12281af0210..05903744feba8ee80498e2ffe2fa6b3d34774bcd 100755 (executable)
@@ -39,6 +39,7 @@
 
 #include "http_api.h"
 #include "http_common.h"
+#include "http_compress_stream.h"
 #include "http_enum.h"
 #include "http_inspect.h"
 #include "http_js_norm.h"
@@ -715,7 +716,7 @@ void HttpMsgHeader::setup_encoding_decompression()
     if (!params->unzip)
         return;
 
-    CompressId& compression = session_data->compression[source_id];
+    CompressId compression = CMP_NONE;
 
     // Search the Content-Encoding header to find the type of compression used. We detect and alert
     // on multiple layers of compression but we only decompress the outermost layer. Thus the last
@@ -743,6 +744,7 @@ void HttpMsgHeader::setup_encoding_decompression()
             compression = CMP_GZIP;
             break;
         case CONTENTCODE_DEFLATE:
+            HttpModule::increment_peg_counts(PEG_COMPRESSED_DEFLATE);
             compression = CMP_DEFLATE;
             break;
         case CONTENTCODE_IDENTITY:
@@ -769,22 +771,17 @@ void HttpMsgHeader::setup_encoding_decompression()
         }
     }
 
-    if (compression == CMP_NONE)
+    if ( compression == CMP_NONE )
         return;
 
-    session_data->compress_stream[source_id] = new z_stream;
-    session_data->compress_stream[source_id]->zalloc = Z_NULL;
-    session_data->compress_stream[source_id]->zfree = Z_NULL;
-    session_data->compress_stream[source_id]->next_in = Z_NULL;
-    session_data->compress_stream[source_id]->avail_in = 0;
-    const int window_bits = (compression == CMP_GZIP) ? GZIP_WINDOW_BITS : DEFLATE_WINDOW_BITS;
-    if (inflateInit2(session_data->compress_stream[source_id], window_bits) != Z_OK)
-    {
-        assert(false);
-        session_data->compression[source_id] = CMP_NONE;
-        delete session_data->compress_stream[source_id];
-        session_data->compress_stream[source_id] = nullptr;
-    }
+    session_data->compress[source_id] = new HttpCompressStream;
+
+    if ( session_data->compress[source_id]->setup(compression) )
+        return;
+
+    assert(false);
+    delete session_data->compress[source_id];
+    session_data->compress[source_id] = nullptr;
 }
 
 void HttpMsgHeader::setup_utf_decoding()
index adae22d4d7943cd6d180f4e9188a51e1193112a1..c0d71bea9ea329bcb8cef0c8a73f612f4d34e3b1 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "http_context_data.h"
 #include "http_common.h"
+#include "http_compress_stream.h"
 #include "http_enum.h"
 #include "http_module.h"
 #include "http_msg_body.h"
@@ -288,8 +289,11 @@ void HttpMsgSection::update_depth() const
     const int64_t& detect_depth_remaining = session_data->detect_depth_remaining[source_id];
     const int32_t& publish_depth_remaining = session_data->publish_depth_remaining[source_id];
 
-    const unsigned target_size = (session_data->compression[source_id] == CMP_NONE) ?
-        SnortConfig::get_conf()->max_pdu : GZIP_BLOCK_SIZE;
+    unsigned target_size = SnortConfig::get_conf()->max_pdu;
+
+    if ( session_data->compress[source_id] != nullptr and
+        session_data->compress[source_id]->get_compression_id() != CMP_NONE )
+        target_size = GZIP_BLOCK_SIZE;
 
     if (detect_depth_remaining <= 0)
     {
index 0079cbf5272a943524995b8b2ef436ab840d6a9f..9fb5ec30ecbfd7f2846c4bb01c6c6c4b19f9e18c 100644 (file)
@@ -63,13 +63,8 @@ private:
     HttpCutter* get_cutter(HttpCommon::SectionType type, HttpFlowData* session) const;
     void chunk_spray(HttpFlowData* session_data, uint8_t* buffer, const uint8_t* data,
         unsigned length) const;
-    void decompress_copy(uint8_t* buffer, uint32_t& offset, const uint8_t* data,
-        uint32_t length, HttpEnums::CompressId& compression, z_stream*& compress_stream,
-        bool at_start, HttpInfractions* infractions, HttpEventGen* events,
-        HttpFlowData* session_data) const;
-    uint8_t* process_gzip_header(const uint8_t* data,
-        uint32_t length, HttpFlowData* session_data) const;
-    bool gzip_header_check_done(HttpFlowData* session_data) const;
+    void decompress_copy(const uint8_t* src, uint32_t src_size,
+        uint8_t* dst, uint32_t& dst_size, HttpFlowData* const session_data) const;
     StreamSplitter::Status handle_zero_nine(snort::Flow*, HttpFlowData*, const uint8_t* data,
         uint32_t length, uint32_t* flush_offset, HttpCommon::SectionType&, HttpCutter*&);
     StreamSplitter::Status call_cutter(snort::Flow*, HttpFlowData*, const uint8_t* data,
index 6262f30264a1757eb59bbbae03a47f4f9317ea70..cf2072cc7018237f440ef69c568b3dc10c8f2f8f 100644 (file)
@@ -25,6 +25,7 @@
 
 #include "protocols/packet.h"
 
+#include "http_compress_stream.h"
 #include "http_inspect.h"
 #include "http_module.h"
 #include "http_test_input.h"
@@ -98,12 +99,8 @@ void HttpStreamSplitter::chunk_spray(HttpFlowData* session_data, uint8_t* buffer
         case CHUNK_DATA:
           {
             const uint32_t skip_amount = (length-k <= expected) ? length-k : expected;
-            const bool at_start = (session_data->body_octets[source_id] == 0) &&
-                (session_data->section_offset[source_id] == 0);
-            decompress_copy(buffer, session_data->section_offset[source_id], data+k, skip_amount,
-                session_data->compression[source_id], session_data->compress_stream[source_id],
-                at_start, session_data->get_infractions(source_id),
-                session_data->events[source_id], session_data);
+            decompress_copy(data+k, skip_amount, buffer,
+                session_data->section_offset[source_id], session_data);
             if ((expected -= skip_amount) == 0)
                 curr_state = CHUNK_DCRLF1;
             k += skip_amount-1;
@@ -128,12 +125,8 @@ void HttpStreamSplitter::chunk_spray(HttpFlowData* session_data, uint8_t* buffer
         case CHUNK_BAD:
           {
             const uint32_t skip_amount = length-k;
-            const bool at_start = (session_data->body_octets[source_id] == 0) &&
-                (session_data->section_offset[source_id] == 0);
-            decompress_copy(buffer, session_data->section_offset[source_id], data+k, skip_amount,
-                session_data->compression[source_id], session_data->compress_stream[source_id],
-                at_start, session_data->get_infractions(source_id),
-                session_data->events[source_id], session_data);
+            decompress_copy(data+k, skip_amount, buffer,
+                session_data->section_offset[source_id], session_data);
             k += skip_amount-1;
             break;
           }
@@ -141,148 +134,14 @@ void HttpStreamSplitter::chunk_spray(HttpFlowData* session_data, uint8_t* buffer
     }
 }
 
-uint8_t* HttpStreamSplitter::process_gzip_header(const uint8_t* data,
-    uint32_t length, HttpFlowData* session_data) const
+void HttpStreamSplitter::decompress_copy(const uint8_t* src, uint32_t src_size,
+    uint8_t* dst, uint32_t& dst_size, HttpFlowData* const session_data) const
 {
-    uint32_t& header_bytes_processed = session_data->gzip_header_bytes_processed[source_id];
-    uint32_t input_bytes_processed = 0;
-    uint8_t* modified_data = nullptr;
-    if (session_data->gzip_state[source_id] == GZIP_TBD)
-    {
-        static const uint8_t gzip_magic[] = {0x1f, 0x8b, 0x08};
-        static const uint8_t magic_length = 3;
-        const uint32_t magic_cmp_len = (magic_length - header_bytes_processed) < length ?
-            (magic_length - header_bytes_processed) : length;
-
-        if (memcmp(data, gzip_magic + header_bytes_processed, magic_cmp_len))
-            session_data->gzip_state[source_id] = GZIP_MAGIC_BAD;
-        else if (header_bytes_processed + length >= magic_length)
-            session_data->gzip_state[source_id] = GZIP_MAGIC_GOOD;
-        header_bytes_processed += magic_cmp_len;
-        input_bytes_processed += magic_cmp_len;
-    }
-    if (session_data->gzip_state[source_id] == GZIP_MAGIC_GOOD and length > input_bytes_processed)
-    {
-        const uint8_t gzip_flags = data[input_bytes_processed];
-        if (gzip_flags & GZIP_FLAG_FEXTRA)
-        {
-            *session_data->get_infractions(source_id) += INF_GZIP_FEXTRA;
-            session_data->events[source_id]->create_event(EVENT_GZIP_FEXTRA);
-        }
-        if (gzip_flags & GZIP_RESERVED_FLAGS)
-        {
-            *session_data->get_infractions(source_id) += INF_GZIP_RESERVED_FLAGS;
-            session_data->events[source_id]->create_event(EVENT_GZIP_RESERVED_FLAGS);
-            modified_data = new uint8_t[length];
-            memcpy(modified_data, data, length);
-            modified_data[input_bytes_processed] &= ~GZIP_RESERVED_FLAGS;
-        }
-        header_bytes_processed++;
-        session_data->gzip_state[source_id] = GZIP_FLAGS_PROCESSED;
-    }
-    return modified_data;
-}
-
-bool HttpStreamSplitter::gzip_header_check_done(HttpFlowData* session_data) const
-{
-    return session_data->gzip_state[source_id] == HttpEnums::GZIP_MAGIC_BAD or
-        session_data->gzip_state[source_id] == HttpEnums::GZIP_FLAGS_PROCESSED;
-}
-
-void HttpStreamSplitter::decompress_copy(uint8_t* buffer, uint32_t& offset, const uint8_t* data,
-    uint32_t length, HttpEnums::CompressId& compression, z_stream*& compress_stream,
-    bool at_start, HttpInfractions* infractions, HttpEventGen* events, HttpFlowData* session_data) const
-{
-    if ((compression == CMP_GZIP) || (compression == CMP_DEFLATE))
-    {
-        uint8_t* data_w_updated_hdr = nullptr;
-        if (compression == CMP_GZIP and !gzip_header_check_done(session_data))
-            data_w_updated_hdr = process_gzip_header(data, length, session_data);
-
-        if (data_w_updated_hdr != nullptr)
-            compress_stream->next_in = const_cast<Bytef*>(data_w_updated_hdr);
-        else 
-            compress_stream->next_in = const_cast<Bytef*>(data);
-        compress_stream->avail_in = length;
-        compress_stream->next_out = buffer + offset;
-        compress_stream->avail_out = MAX_OCTETS - offset;
-        int ret_val = inflate(compress_stream, Z_SYNC_FLUSH);
-
-        delete[] data_w_updated_hdr;
-        
-        if ((ret_val == Z_OK) || (ret_val == Z_STREAM_END))
-        {
-            offset = MAX_OCTETS - compress_stream->avail_out;
-            if (compress_stream->avail_in > 0)
-            {
-                // There are two ways not to consume all the input
-                if (ret_val == Z_STREAM_END)
-                {
-                    // The zipped data stream ended but there is more input data
-                    *infractions += INF_GZIP_EARLY_END;
-                    events->create_event(EVENT_GZIP_EARLY_END);
-                    const uInt num_copy =
-                        (compress_stream->avail_in <= compress_stream->avail_out) ?
-                        compress_stream->avail_in : compress_stream->avail_out;
-                    memcpy(buffer + offset, data + (length - compress_stream->avail_in), num_copy);
-                    offset += num_copy;
-                }
-                else
-                {
-                    assert(compress_stream->avail_out == 0);
-                    // The data expanded too much
-                    *infractions += INF_GZIP_OVERRUN;
-                    events->create_event(EVENT_GZIP_OVERRUN);
-                }
-                compression = CMP_NONE;
-                inflateEnd(compress_stream);
-                delete compress_stream;
-                compress_stream = nullptr;
-                // FIXIT-E - Will need to clear gzip header processing state here when we implement
-                // processing multiple gzip members in a message section
-            }
-            return;
-        }
-        else if ((compression == CMP_DEFLATE) && at_start && (ret_val == Z_DATA_ERROR))
-        {
-            // Some incorrect implementations of deflate don't use the expected header. Feed a
-            // dummy header to zlib and retry the inflate.
-            static constexpr uint8_t zlib_header[2] = { 0x78, 0x01 };
-
-            inflateReset(compress_stream);
-            compress_stream->next_in = const_cast<Bytef*>(zlib_header);
-            compress_stream->avail_in = sizeof(zlib_header);
-            int ret = inflate(compress_stream, Z_SYNC_FLUSH);
-            if ( ret == Z_OK or ret == Z_STREAM_END)
-            { 
-                // Start over at the beginning
-                decompress_copy(buffer, offset, data, length, compression, compress_stream, false,
-                    infractions, events, session_data);
-            }
-            return;
-        }
-        else
-        {
-            *infractions += INF_GZIP_FAILURE;
-            events->create_event(EVENT_GZIP_FAILURE);
-            compression = CMP_NONE;
-            inflateEnd(compress_stream);
-            delete compress_stream;
-            compress_stream = nullptr;
-            // Since we failed to uncompress the data, fall through
-        }
-    }
+    if ( session_data->compress[source_id] != nullptr and
+        is_body(session_data->section_type[source_id]) )
+        return;
 
-    // The following precaution is necessary because mixed compressed and uncompressed data can
-    // cause the buffer to overrun even though we are not decompressing right now
-    if (length > MAX_OCTETS - offset)
-    {
-        length = MAX_OCTETS - offset;
-        *infractions += INF_GZIP_OVERRUN;
-        events->create_event(EVENT_GZIP_OVERRUN);
-    }
-    memcpy(buffer + offset, data, length);
-    offset += length;
+    HttpCompressStream::copy_raw(src, src_size, dst, dst_size);
 }
 
 const StreamBuffer HttpStreamSplitter::reassemble(Flow* flow, unsigned total,
@@ -422,11 +281,22 @@ const StreamBuffer HttpStreamSplitter::reassemble(Flow* flow, unsigned total,
     HttpModule::increment_peg_counts(PEG_REASSEMBLE);
 
     uint8_t*& buffer = session_data->section_buffer[source_id];
-    if (buffer == nullptr)
+    if ( buffer == nullptr )
     {
         // Body sections need extra space to accommodate unzipping
-        if (is_body(session_data->section_type[source_id]))
-            buffer = new uint8_t[MAX_OCTETS];
+        if ( is_body(session_data->section_type[source_id]) )
+            if ( session_data->compress[source_id] != nullptr and partial_buffer_length > 0 )
+            {
+                assert(partial_buffer != nullptr);
+
+                buffer = partial_buffer;
+                session_data->section_offset[source_id] = partial_buffer_length;
+
+                partial_buffer = nullptr;
+                partial_buffer_length = 0;
+            }
+            else
+                buffer = new uint8_t[MAX_OCTETS];
         else
         {
             uint32_t buffer_size = (total > 0) ? total : 1;
@@ -446,18 +316,9 @@ const StreamBuffer HttpStreamSplitter::reassemble(Flow* flow, unsigned total,
     }
 
     if (session_data->section_type[source_id] != SEC_BODY_CHUNK)
-    {
-        const bool at_start = (session_data->body_octets[source_id] == 0) &&
-             (session_data->section_offset[source_id] == 0);
-        decompress_copy(buffer, session_data->section_offset[source_id], data, len,
-            session_data->compression[source_id], session_data->compress_stream[source_id],
-            at_start, session_data->get_infractions(source_id),
-            session_data->events[source_id], session_data);
-    }
+        decompress_copy(data, len, buffer, session_data->section_offset[source_id], session_data);
     else
-    {
         chunk_spray(session_data, buffer, data, len);
-    }
 
     StreamBuffer http_buf { nullptr, 0 };
 
@@ -481,7 +342,11 @@ const StreamBuffer HttpStreamSplitter::reassemble(Flow* flow, unsigned total,
             if (session_data->section_offset[source_id] > 0)
             {
                 // Store the data from a partial flush for reuse
-                partial_buffer = new uint8_t[session_data->section_offset[source_id]];
+                if ( is_body(session_data->section_type[source_id]) )
+                    partial_buffer = new uint8_t[MAX_OCTETS];
+                else
+                    partial_buffer = new uint8_t[session_data->section_offset[source_id]];
+
                 memcpy(partial_buffer, buffer, session_data->section_offset[source_id]);
                 partial_buffer_length = session_data->section_offset[source_id];
             }
index 3bf24612a1b45f1e2bbfcc95a8bfdeef1b6d02a8..296addcc1147569b61b08d204f745f2e666d699d 100644 (file)
@@ -27,6 +27,7 @@
 #include "protocols/packet.h"
 
 #include "http_common.h"
+#include "http_compress_stream.h"
 #include "http_context_data.h"
 #include "http_cutter.h"
 #include "http_enum.h"
@@ -51,6 +52,14 @@ void HttpStreamSplitter::prepare_flush(HttpFlowData* session_data, uint32_t* flu
     session_data->num_good_chunks[source_id] = num_good_chunks;
     session_data->octets_expected[source_id] = octets_seen + num_flushed;
 
+    if ( session_data->compress[source_id] != nullptr and section_type == SEC_DISCARD )
+    {
+        delete[] session_data->partial_buffer[source_id];
+
+        session_data->partial_buffer[source_id] = nullptr;
+        session_data->partial_buffer_length[source_id] = 0;
+    }
+
     if (flush_offset != nullptr)
     {
 #ifdef REG_TEST
@@ -70,38 +79,32 @@ HttpCutter* HttpStreamSplitter::get_cutter(SectionType type,
     switch (type)
     {
     case SEC_REQUEST:
-        return (HttpCutter*)new HttpRequestCutter;
+        return new HttpRequestCutter;
     case SEC_STATUS:
         if (session_data->expected_trans_num[SRC_SERVER] == session_data->zero_nine_expected)
-            return (HttpCutter*)new HttpZeroNineCutter;
+            return new HttpZeroNineCutter;
 
-        return (HttpCutter*)new HttpStatusCutter;
+        return new HttpStatusCutter;
     case SEC_HEADER:
     case SEC_TRAILER:
-        return (HttpCutter*)new HttpHeaderCutter;
+        return new HttpHeaderCutter;
     case SEC_BODY_CL:
-        return (HttpCutter*)new HttpBodyClCutter(
-            session_data->data_length[source_id],
+        return new HttpBodyClCutter(session_data->data_length[source_id],
             session_data->accelerated_blocking[source_id],
             my_inspector->script_finder,
-            session_data->compression[source_id]);
+            session_data, source_id);
     case SEC_BODY_CHUNK:
-        return (HttpCutter*)new HttpBodyChunkCutter(
-            my_inspector->params->maximum_chunk_length,
+        return new HttpBodyChunkCutter(my_inspector->params->maximum_chunk_length,
             session_data->accelerated_blocking[source_id],
             my_inspector->script_finder,
-            session_data->compression[source_id]);
+            session_data, source_id);
     case SEC_BODY_OLD:
-        return (HttpCutter*)new HttpBodyOldCutter(
-            session_data->accelerated_blocking[source_id],
-            my_inspector->script_finder,
-            session_data->compression[source_id]);
+        return new HttpBodyOldCutter(session_data->accelerated_blocking[source_id],
+            my_inspector->script_finder, session_data, source_id);
     case SEC_BODY_HX:
-        return (HttpCutter*)new HttpBodyHXCutter(
-            session_data->data_length[source_id],
+        return new HttpBodyHXCutter(session_data->data_length[source_id],
             session_data->accelerated_blocking[source_id],
-            my_inspector->script_finder,
-            session_data->compression[source_id]);
+            my_inspector->script_finder, session_data, source_id);
     default:
         assert(false);
         return nullptr;
index fe70a363d942890d36ed1a5ed04170938d85d7d4..c2fd031b4d299880204a05e137b0d502e5fe6ac3 100755 (executable)
@@ -289,7 +289,6 @@ const RuleMap HttpModule::http_events[] =
     { EVENT_BROKEN_CHUNK,               "HTTP chunk misformatted" },
     { EVENT_CHUNK_WHITESPACE,           "white space adjacent to chunk length" },
     { EVENT_HEAD_NAME_WHITESPACE,       "white space within header name" },
-    { EVENT_GZIP_OVERRUN,               "excessive gzip compression" },
     { EVENT_GZIP_FAILURE,               "gzip decompression failed" },
     { EVENT_ZERO_NINE_CONTINUE,         "HTTP 0.9 requested followed by another request" },
     { EVENT_ZERO_NINE_NOT_FIRST,        "HTTP 0.9 request following a normal request" },
@@ -358,6 +357,8 @@ const RuleMap HttpModule::http_events[] =
                                         "disallowed methods list" },
     { EVENT_GZIP_RESERVED_FLAGS,        "HTTP gzip body with reserved flag set" },
     { EVENT_MAX_PARTIAL_FLUSH,          "Too many partial flushes" },
+    { EVENT_DEFLATE_EARLY_END,          "deflate compressed data followed by unexpected non-deflate data" },
+    { EVENT_DEFLATE_FAILURE,            "deflate decompression failed" },
     { 0, nullptr }
 };
 
@@ -399,6 +400,10 @@ const PegInfo HttpModule::peg_names[PEG_COUNT_MAX+1] =
     { CountType::SUM, "js_pdf_scripts", "total number of PDF files processed" },
     { CountType::SUM, "skip_mime_attach", "total number of HTTP requests with too many MIME attachments to inspect" },
     { CountType::SUM, "compressed_gzip", "total number of HTTP bodies compressed with GZIP" },
+    { CountType::SUM, "compressed_gzip_failed", "total number of HTTP bodies with failed GZIP decompression" },
+    { CountType::SUM, "compressed_deflate", "total number of HTTP bodies compressed with Deflate" },
+    { CountType::SUM, "incorrect_deflate_header", "total number of HTTP bodies compressed with Deflate that had incorrect header" },
+    { CountType::SUM, "compressed_deflate_failed", "total number of HTTP bodies with failed Deflate decompression" },
     { CountType::SUM, "compressed_not_supported", "total number of HTTP bodies compressed with known but not supported methods" },
     { CountType::SUM, "compressed_unknown", "total number of HTTP bodies compressed with unknown methods" },
     { CountType::SUM, "max_publish_depth_hits", "total number of times the maximum publish depth was exceeded" },
index 1f4a2174180412761ea400c7db27409528faa6f1..c83867bc77db3817014154b59d04e2a0cf742a50 100644 (file)
@@ -1,3 +1,14 @@
+add_cpputest( http_decompression_test
+    SOURCES
+        ../http_compress_stream.cc
+        ../http_cutter.cc
+        ../http_flow_data.cc
+        ../http_tables.cc
+        ../http_test_input.cc
+        ../http_test_manager.cc
+    LIBS ${ZLIB_LIBRARIES}
+)
+
 add_cpputest( http_module_test
     SOURCES
         ../http_module.cc
@@ -27,6 +38,7 @@ add_cpputest( http_transaction_test
         ../http_flow_data.cc
         ../http_test_manager.cc
         ../http_test_input.cc
+        ../http_compress_stream.cc
     LIBS ${ZLIB_LIBRARIES}
 )
 
diff --git a/src/service_inspectors/http_inspect/test/http_decompression_test.cc b/src/service_inspectors/http_inspect/test/http_decompression_test.cc
new file mode 100644 (file)
index 0000000..8e98f7e
--- /dev/null
@@ -0,0 +1,578 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2026-2026 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// http_decompression_test.cc author Oleksandr Fedorych <ofedoryc@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <cstring>
+
+#include "pub_sub/http_transaction_end_event.h"
+#include "pub_sub/http_form_data_event.h"
+#include "service_inspectors/http_inspect/http_common.h"
+#include "service_inspectors/http_inspect/http_compress_stream.h"
+#include "service_inspectors/http_inspect/http_cutter.h"
+#include "service_inspectors/http_inspect/http_enum.h"
+#include "service_inspectors/http_inspect/http_event.h"
+#include "service_inspectors/http_inspect/http_flow_data.h"
+#include "service_inspectors/http_inspect/http_inspect.h"
+#include "service_inspectors/http_inspect/http_module.h"
+#include "service_inspectors/http_inspect/http_msg_section.h"
+#include "service_inspectors/http_inspect/http_transaction.h"
+#include "service_inspectors/http2_inspect/http2_flow_data.h"
+
+#include "http_unit_test_helpers.h"
+
+#include <CppUTest/CommandLineTestRunner.h>
+#include <CppUTest/TestHarness.h>
+#include <CppUTestExt/MockSupport.h>
+
+using namespace snort;
+using namespace HttpCommon;
+using namespace HttpEnums;
+
+static const uint8_t deflate_compressed[] = {
+    0x78, 0x9c, 0x63, 0x60, 0xc0, 0x07, 0x00, 0x00, 0x1e, 0x00, 0x01
+};
+static constexpr uint32_t deflate_compressed_size = sizeof(deflate_compressed);
+
+static const uint8_t gzip_compressed[] = {
+    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
+    0x63, 0x60, 0xc0, 0x07, 0x00, 0xb5, 0x3e, 0x04, 0x00, 0x1e,
+    0x00, 0x00, 0x00
+};
+static constexpr uint32_t gzip_compressed_size = sizeof(gzip_compressed);
+
+static constexpr uint32_t scan_buf_size = 2000;
+
+namespace snort
+{
+unsigned FlowData::flow_data_id = 0;
+FlowData::FlowData(unsigned) :   id(0) { }
+FlowData::~FlowData() = default;
+FlowDataStore::~FlowDataStore() = default;
+int DetectionEngine::queue_event(unsigned int, unsigned int) { return 0; }
+fd_status_t File_Decomp_StopFree(fd_session_t*) { return File_Decomp_OK; }
+uint32_t str_to_hash(const uint8_t*, size_t) { return 0; }
+FlowData* FlowDataStore::get(unsigned) const { return nullptr; }
+void FlowDataStore::set(FlowData*) { }
+Flow::~Flow() = default;
+unsigned DataBus::get_id(PubKey const&) { return 0; }
+void DataBus::publish(unsigned int, unsigned int, DataEvent&, Flow*) { }
+HttpTransactionEndEvent::HttpTransactionEndEvent(const HttpTransaction* const trans)
+    :   transaction(trans) { }
+Inspector::Inspector() :   ref_count(nullptr) { }
+Inspector::~Inspector() = default;
+bool Inspector::likes(Packet*) { return true; }
+bool Inspector::get_buf(const char*, Packet*, InspectionBuffer&) { return false; }
+class StreamSplitter* Inspector::get_splitter(bool) { return nullptr; }
+const StreamBuffer StreamSplitter::reassemble(snort::Flow*, unsigned int, unsigned int,
+    unsigned char const*, unsigned int, unsigned int, unsigned int&)
+{
+    StreamBuffer buf { nullptr, 0 };
+    return buf;
+}
+
+unsigned StreamSplitter::max(snort::Flow*) { return 0; }
+uint8_t TraceApi::get_constraints_generation() { return 0; }
+void TraceApi::filter(const Packet&) { }
+void trace_vprintf(const char*, TraceLevel, const char*, const snort::Packet*, const char*,
+    va_list) { }
+}
+
+THREAD_LOCAL const snort::Trace* http_trace = nullptr;
+
+HttpParaList::UriParam::UriParam() :   uri_char{} { }
+HttpParaList::JsNormParam::~JsNormParam() { }
+HttpParaList::~HttpParaList() { }
+
+void HttpFormDataEvent::format_as_uri() const { }
+
+unsigned Http2FlowData::inspector_id = 0;
+uint32_t Http2FlowData::get_processing_stream_id() const { return 0; }
+HttpInspect::HttpInspect(const HttpParaList* para)
+    :   params(para), xtra_trueip_id(0), xtra_uri_id(0), xtra_host_id(0), xtra_jsnorm_id(0) { }
+HttpInspect::~HttpInspect() = default;
+bool HttpInspect::configure(SnortConfig*) { return true; }
+void HttpInspect::show(const SnortConfig*) const { }
+bool HttpInspect::get_buf(unsigned, snort::Packet*, snort::InspectionBuffer&) { return true; }
+HttpCommon::SectionType HttpInspect::get_type_expected(snort::Flow*, HttpCommon::SourceId) const
+{ return SEC_DISCARD; }
+void HttpInspect::finish_hx_body(snort::Flow*, HttpCommon::SourceId, HttpCommon::HXBodyState,
+    bool) const { }
+void HttpInspect::set_hx_body_state(snort::Flow*, HttpCommon::SourceId,
+    HttpCommon::HXBodyState) const { }
+bool HttpInspect::get_fp_buf(snort::InspectionBuffer::Type, snort::Packet*,
+    snort::InspectionBuffer&) { return false; }
+void HttpInspect::eval(snort::Packet*) { }
+void HttpInspect::eval(snort::Packet*, HttpCommon::SourceId, const uint8_t*, uint16_t) { }
+void HttpInspect::clear(snort::Packet*) { }
+bool HttpInspect::get_buf(snort::InspectionBuffer::Type, snort::Packet*,
+    snort::InspectionBuffer&) { return false; }
+const uint8_t* HttpInspect::adjust_log_packet(snort::Packet*, uint16_t&) { return nullptr; }
+StreamSplitter::Status HttpStreamSplitter::scan(snort::Packet*, const uint8_t*, uint32_t,
+    uint32_t, uint32_t*) { return StreamSplitter::FLUSH; }
+StreamSplitter::Status HttpStreamSplitter::scan(snort::Flow*, const uint8_t*, uint32_t,
+    uint32_t*, snort::Packet*) { return StreamSplitter::FLUSH; }
+const snort::StreamBuffer HttpStreamSplitter::reassemble(snort::Flow*, unsigned, unsigned,
+    const uint8_t*, unsigned, uint32_t, unsigned&)
+{
+    StreamBuffer buf { nullptr, 0 };
+    return buf;
+}
+
+bool HttpStreamSplitter::finish(snort::Flow*) { return false; }
+void HttpStreamSplitter::prep_partial_flush(snort::Flow*, uint32_t, uint32_t, uint32_t) { }
+void HttpMsgSection::clear_tmp_buffers() { }
+
+HttpTransaction::~HttpTransaction() = default;
+void HttpTransaction::delete_transaction(HttpTransaction*, HttpFlowData*) { }
+HttpInfractions* HttpTransaction::get_infractions(HttpCommon::SourceId) { return nullptr; }
+
+THREAD_LOCAL PegCount HttpModule::peg_counts[PEG_COUNT_MAX] = { };
+const Field Field::FIELD_NULL { STAT_NO_SOURCE };
+
+struct HttpDecompressionFixture : public Utest
+{
+    Flow* const flow = new Flow;
+    HttpParaList params;
+    // cppcheck-suppress constVariablePointer
+    HttpFlowData* session_data = nullptr;
+
+    void setup() override
+    {
+        flow->gadget = new HttpInspect(&params);
+        // cppcheck-suppress unreadVariable
+        session_data = new HttpFlowData(flow, &params);
+    }
+
+    void teardown() override
+    {
+        delete session_data;
+        session_data = nullptr;
+        delete flow->gadget;
+        flow->gadget = nullptr;
+        delete flow;
+    }
+};
+
+TEST_GROUP_BASE(cl_cutter_decompression_test, HttpDecompressionFixture)
+{
+};
+
+TEST(cl_cutter_decompression_test, long_body_deflate_overrun)
+{
+    auto* compress_stream = new HttpCompressStream;
+    compress_stream->setup(CMP_DEFLATE);
+    HttpUnitTestSetup::set_compress_stream(session_data, SRC_SERVER, compress_stream);
+
+    const uint32_t pre_filled_len = MAX_OCTETS - 5;
+    auto* pre_filled_buf = new uint8_t[MAX_OCTETS];
+    HttpUnitTestSetup::set_partial_buffer(session_data, SRC_SERVER, pre_filled_buf, pre_filled_len);
+
+    const uint32_t flow_target = deflate_compressed_size;
+    const uint32_t remaining = scan_buf_size;
+
+    uint8_t scan_buf[scan_buf_size];
+    memcpy(scan_buf, deflate_compressed, deflate_compressed_size);
+
+    HttpBodyClCutter cutter(static_cast<int64_t>(remaining), false, nullptr, session_data, SRC_SERVER);
+
+    auto* infractions = HttpUnitTestSetup::get_infractions(session_data, SRC_SERVER);
+    HttpEventGen events;
+
+    const auto result = cutter.cut(scan_buf, scan_buf_size, infractions, &events,
+        flow_target, true, HX_BODY_NOT_COMPLETE);
+    // num_flush is reduced because decompress ran out of space
+    CHECK_EQUAL(SCAN_FOUND_PIECE, result);
+    CHECK_EQUAL(0, cutter.get_octets_seen());
+    CHECK_EQUAL(7, cutter.get_num_flush());
+    // cppcheck-suppress syntaxError
+    CHECK_COMPARE(flow_target, >, cutter.get_num_flush());
+}
+
+TEST(cl_cutter_decompression_test, long_body_gzip_overrun)
+{
+    auto* compress_stream = new HttpCompressStream;
+    compress_stream->setup(CMP_GZIP);
+    HttpUnitTestSetup::set_compress_stream(session_data, SRC_SERVER, compress_stream);
+
+    const uint32_t pre_filled_size = MAX_OCTETS - 5;
+    auto* pre_filled_buf = new uint8_t[MAX_OCTETS];
+    HttpUnitTestSetup::set_partial_buffer(session_data, SRC_SERVER, pre_filled_buf, pre_filled_size);
+
+    const uint32_t flow_target = gzip_compressed_size;
+    const uint32_t remaining = scan_buf_size;
+
+    uint8_t scan_buf[scan_buf_size];
+    memcpy(scan_buf, gzip_compressed, gzip_compressed_size);
+
+    HttpBodyClCutter cutter(static_cast<int64_t>(remaining), false, nullptr, session_data, SRC_SERVER);
+
+    auto* infractions = HttpUnitTestSetup::get_infractions(session_data, SRC_SERVER);
+    HttpEventGen events;
+
+    const auto result = cutter.cut(scan_buf, scan_buf_size, infractions, &events,
+        flow_target, true, HX_BODY_NOT_COMPLETE);
+    // num_flush is reduced because decompress ran out of space
+    CHECK_EQUAL(SCAN_FOUND_PIECE, result);
+    CHECK_EQUAL(0, cutter.get_octets_seen());
+    CHECK_EQUAL(15, cutter.get_num_flush());
+    CHECK_COMPARE(flow_target, >, cutter.get_num_flush());
+}
+
+TEST(cl_cutter_decompression_test, octets_seen_long_section_overrun)
+{
+    auto* compress_stream = new HttpCompressStream;
+    compress_stream->setup(CMP_DEFLATE);
+    HttpUnitTestSetup::set_compress_stream(session_data, SRC_SERVER, compress_stream);
+
+    const uint32_t pre_filled_len = MAX_OCTETS - 5;
+    auto* pre_filled_buf = new uint8_t[MAX_OCTETS];
+    HttpUnitTestSetup::set_partial_buffer(session_data, SRC_SERVER, pre_filled_buf, pre_filled_len);
+
+    const uint32_t flow_target = 10;
+
+    uint8_t scan_buf[scan_buf_size];
+    memcpy(scan_buf, deflate_compressed, deflate_compressed_size);
+
+    HttpBodyClCutter cutter(10, false, nullptr, session_data, SRC_SERVER);
+
+    auto* infractions = HttpUnitTestSetup::get_infractions(session_data, SRC_SERVER);
+    HttpEventGen events;
+
+    auto result = cutter.cut(scan_buf, 1, infractions, &events, flow_target, true, HX_BODY_NOT_COMPLETE);
+    CHECK_EQUAL(SCAN_NOT_FOUND, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(0, cutter.get_num_flush());
+    CHECK_COMPARE(flow_target, >, cutter.get_num_flush());
+
+    result = cutter.cut(scan_buf + 1, 7, infractions, &events, flow_target, true, HX_BODY_NOT_COMPLETE);
+    // num_flush is reduced because decompress ran out of space
+    CHECK_EQUAL(SCAN_FOUND_PIECE, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(6, cutter.get_num_flush());
+    CHECK_COMPARE(flow_target, >, cutter.get_num_flush());
+
+    // remaining after the two cuts above is 2 bytes, so when we send the next 3 bytes, only 2 are
+    // flushed.
+    result = cutter.cut(scan_buf + 8, 3, infractions, &events, flow_target, false, HX_BODY_NOT_COMPLETE);
+    CHECK_EQUAL(SCAN_FOUND, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(2, cutter.get_num_flush());
+    CHECK_COMPARE(flow_target, >, cutter.get_num_flush());
+}
+
+TEST(cl_cutter_decompression_test, octets_seen_no_stretch_overrun)
+{
+    auto* compress_stream = new HttpCompressStream;
+    compress_stream->setup(CMP_GZIP);
+    HttpUnitTestSetup::set_compress_stream(session_data, SRC_SERVER, compress_stream);
+
+    const uint32_t pre_filled_size = MAX_OCTETS - 5;
+    auto* pre_filled_buf = new uint8_t[MAX_OCTETS];
+    HttpUnitTestSetup::set_partial_buffer(session_data, SRC_SERVER, pre_filled_buf, pre_filled_size);
+
+    const uint32_t flow_target = gzip_compressed_size;
+    const uint32_t remaining = scan_buf_size;
+
+    uint8_t scan_buf[scan_buf_size];
+    memcpy(scan_buf, gzip_compressed, gzip_compressed_size);
+
+    HttpBodyClCutter cutter(static_cast<int64_t>(remaining), false, nullptr, session_data, SRC_SERVER);
+
+    auto* infractions = HttpUnitTestSetup::get_infractions(session_data, SRC_SERVER);
+    HttpEventGen events;
+
+    auto result = cutter.cut(scan_buf, 1, infractions, &events, flow_target, true, HX_BODY_NOT_COMPLETE);
+    CHECK_EQUAL(SCAN_NOT_FOUND, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(0, cutter.get_num_flush());
+
+    result = cutter.cut(scan_buf + 1, scan_buf_size - 1, infractions, &events,
+        flow_target, true, HX_BODY_NOT_COMPLETE);
+    // num_flush is reduced because decompress ran out of space
+    CHECK_EQUAL(SCAN_FOUND_PIECE, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(14, cutter.get_num_flush());
+    CHECK_COMPARE(flow_target, >, cutter.get_num_flush());
+
+    result = cutter.cut(scan_buf, scan_buf_size, infractions, &events, 2000, false, HX_BODY_NOT_COMPLETE);
+    // If the cutter computed remaining correctly in the previous cut() call, it will return 1984
+    // bytes to be flushed.
+    // Otherwise, it will return 1985 bytes because we haven't accounted for octets_seen in the
+    // previous cut() call where remaining was computed.
+    CHECK_EQUAL(SCAN_FOUND, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(1984, cutter.get_num_flush());
+    CHECK_COMPARE(2000, >, cutter.get_num_flush());
+}
+
+TEST(cl_cutter_decompression_test, octets_seen_mid_segment_stretch_overrun)
+{
+    auto* compress_stream = new HttpCompressStream;
+    compress_stream->setup(CMP_DEFLATE);
+    HttpUnitTestSetup::set_compress_stream(session_data, SRC_SERVER, compress_stream);
+
+    const uint32_t pre_filled_len = MAX_OCTETS - 5;
+    auto* pre_filled_buf = new uint8_t[MAX_OCTETS];
+    HttpUnitTestSetup::set_partial_buffer(session_data, SRC_SERVER, pre_filled_buf, pre_filled_len);
+
+    const uint32_t flow_target = 240;
+
+    uint8_t scan_buf[scan_buf_size] = { };
+    memcpy(scan_buf, deflate_compressed, deflate_compressed_size);
+
+    HttpBodyClCutter cutter(1800, false, nullptr, session_data, SRC_SERVER);
+
+    auto* infractions = HttpUnitTestSetup::get_infractions(session_data, SRC_SERVER);
+    HttpEventGen events;
+
+    auto result = cutter.cut(scan_buf, 1, infractions, &events, flow_target, true, HX_BODY_NOT_COMPLETE);
+    CHECK_EQUAL(SCAN_NOT_FOUND, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(0, cutter.get_num_flush());
+
+    result = cutter.cut(scan_buf + 1, 1700, infractions, &events, flow_target, true, HX_BODY_NOT_COMPLETE);
+    // num_flush is reduced because decompress ran out of space
+    CHECK_EQUAL(SCAN_FOUND_PIECE, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(6, cutter.get_num_flush());
+    CHECK_COMPARE(flow_target - cutter.get_octets_seen(), >, cutter.get_num_flush());
+
+    result = cutter.cut(scan_buf, scan_buf_size, infractions, &events, 2000, false, HX_BODY_NOT_COMPLETE);
+    // If the cutter computed remaining correctly in the previous cut() call, it will return 1792
+    // bytes to be flushed.
+    // Otherwise, it will return 1793 bytes because we haven't accounted for octets_seen in the
+    // previous cut() call where remaining was computed.
+    CHECK_EQUAL(SCAN_FOUND, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(1792, cutter.get_num_flush());
+    CHECK_COMPARE(2000, >, cutter.get_num_flush());
+}
+
+TEST(cl_cutter_decompression_test, octets_seen_last_segment_overrun)
+{
+    auto* compress_stream = new HttpCompressStream;
+    compress_stream->setup(CMP_DEFLATE);
+    HttpUnitTestSetup::set_compress_stream(session_data, SRC_SERVER, compress_stream);
+
+    const uint32_t pre_filled_len = MAX_OCTETS - 5;
+    auto* pre_filled_buf = new uint8_t[MAX_OCTETS];
+    HttpUnitTestSetup::set_partial_buffer(session_data, SRC_SERVER, pre_filled_buf, pre_filled_len);
+
+    const uint32_t flow_target = 240;
+
+    uint8_t scan_buf[scan_buf_size] = { };
+    memcpy(scan_buf, deflate_compressed, deflate_compressed_size);
+
+    HttpBodyClCutter cutter(10, false, nullptr, session_data, SRC_SERVER);
+
+    auto* infractions = HttpUnitTestSetup::get_infractions(session_data, SRC_SERVER);
+    HttpEventGen events;
+
+    auto result = cutter.cut(scan_buf, 1, infractions, &events, flow_target, true, HX_BODY_NOT_COMPLETE);
+    CHECK_EQUAL(SCAN_NOT_FOUND, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(0, cutter.get_num_flush());
+
+    result = cutter.cut(scan_buf + 1, 9, infractions, &events, flow_target, false, HX_BODY_NOT_COMPLETE);
+    // num_flush is reduced because decompress ran out of space
+    CHECK_EQUAL(SCAN_FOUND_PIECE, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(6, cutter.get_num_flush());
+    CHECK_COMPARE((flow_target - cutter.get_octets_seen()), >, cutter.get_num_flush());
+
+    result = cutter.cut(scan_buf + 10, 10, infractions, &events, flow_target, false, HX_BODY_NOT_COMPLETE);
+    // Here we verify that remaining is computed correctly when stretching is not allowed.
+    // If remaining is computed correctly, we will receive SCAN_FOUND. Otherwise, SCAN_NOT_FOUND.
+    CHECK_EQUAL(SCAN_FOUND, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(2, cutter.get_num_flush());
+    CHECK_COMPARE((flow_target - cutter.get_octets_seen()), >, cutter.get_num_flush());
+}
+
+TEST_GROUP_BASE(old_cutter_decompression_test, HttpDecompressionFixture)
+{
+};
+
+TEST(old_cutter_decompression_test, no_stretch_overrun)
+{
+    auto* compress_stream = new HttpCompressStream;
+    compress_stream->setup(CMP_DEFLATE);
+    HttpUnitTestSetup::set_compress_stream(session_data, SRC_SERVER, compress_stream);
+
+    const uint32_t pre_filled_len = MAX_OCTETS - 5;
+    auto* pre_filled_buf = new uint8_t[MAX_OCTETS];
+    HttpUnitTestSetup::set_partial_buffer(session_data, SRC_SERVER, pre_filled_buf, pre_filled_len);
+
+    const uint32_t flow_target = 8;
+
+    uint8_t scan_buf[scan_buf_size];
+    memcpy(scan_buf, deflate_compressed, deflate_compressed_size);
+
+    HttpBodyOldCutter cutter(false, nullptr, session_data, SRC_SERVER);
+
+    auto* infractions = HttpUnitTestSetup::get_infractions(session_data, SRC_SERVER);
+    HttpEventGen events;
+
+    auto result = cutter.cut(scan_buf, 1, infractions, &events, flow_target, true, HX_BODY_NOT_COMPLETE);
+    CHECK_EQUAL(SCAN_NOT_FOUND, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(0, cutter.get_num_flush());
+
+    result = cutter.cut(scan_buf + 1, 7, infractions, &events, flow_target, false, HX_BODY_NOT_COMPLETE);
+    // num_flush is reduced because decompress ran out of space
+    CHECK_EQUAL(SCAN_FOUND_PIECE, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(6, cutter.get_num_flush());
+}
+
+TEST(old_cutter_decompression_test, large_body_deflate_overrun)
+{
+    auto* compress_stream = new HttpCompressStream;
+    compress_stream->setup(CMP_DEFLATE);
+    HttpUnitTestSetup::set_compress_stream(session_data, SRC_SERVER, compress_stream);
+
+    const uint32_t pre_filled_size = MAX_OCTETS - 5;
+    auto* pre_filled_buf = new uint8_t[MAX_OCTETS]();
+    HttpUnitTestSetup::set_partial_buffer(session_data, SRC_SERVER, pre_filled_buf, pre_filled_size);
+
+    // flow_target = compressed size; send a larger buffer so length >= flow_target
+    const uint32_t flow_target = deflate_compressed_size;
+
+    uint8_t scan_buf[scan_buf_size] = { };
+    memcpy(scan_buf, deflate_compressed, deflate_compressed_size);
+
+    HttpBodyOldCutter cutter(false, nullptr, session_data, SRC_SERVER);
+
+    auto* infractions = HttpUnitTestSetup::get_infractions(session_data, SRC_SERVER);
+    HttpEventGen events;
+
+    const auto result = cutter.cut(scan_buf, scan_buf_size, infractions, &events,
+        flow_target, false, HX_BODY_NOT_COMPLETE);
+    // num_flush is reduced because decompress ran out of space
+    CHECK_EQUAL(SCAN_FOUND_PIECE, result);
+    CHECK_EQUAL(0, cutter.get_octets_seen());
+    CHECK_EQUAL(7, cutter.get_num_flush());
+    CHECK_COMPARE(flow_target, >, cutter.get_num_flush());
+}
+
+TEST(old_cutter_decompression_test, large_body_gzip_overrun)
+{
+    auto* compress_stream = new HttpCompressStream;
+    compress_stream->setup(CMP_GZIP);
+    HttpUnitTestSetup::set_compress_stream(session_data, SRC_SERVER, compress_stream);
+
+    const uint32_t pre_filled_size = MAX_OCTETS - 5;
+    auto* pre_filled_buf = new uint8_t[MAX_OCTETS]();
+    HttpUnitTestSetup::set_partial_buffer(session_data, SRC_SERVER, pre_filled_buf, pre_filled_size);
+
+    const uint32_t flow_target = gzip_compressed_size;
+
+    uint8_t scan_buf[scan_buf_size] = { };
+    memcpy(scan_buf, gzip_compressed, gzip_compressed_size);
+
+    HttpBodyOldCutter cutter(false, nullptr, session_data, SRC_SERVER);
+
+    auto* infractions = HttpUnitTestSetup::get_infractions(session_data, SRC_SERVER);
+    HttpEventGen events;
+
+    const auto result = cutter.cut(scan_buf, scan_buf_size, infractions, &events,
+        flow_target, false, HX_BODY_NOT_COMPLETE);
+    // num_flush is reduced because decompress ran out of space
+    CHECK_EQUAL(SCAN_FOUND_PIECE, result);
+    CHECK_EQUAL(0, cutter.get_octets_seen());
+    CHECK_EQUAL(15, cutter.get_num_flush());
+    CHECK_COMPARE(flow_target, >, cutter.get_num_flush());
+}
+
+TEST_GROUP_BASE(hx_cutter_decompression_test, HttpDecompressionFixture)
+{
+};
+
+TEST(hx_cutter_decompression_test, octets_seen_section_not_complete_overrun)
+{
+    auto* compress_stream = new HttpCompressStream;
+    compress_stream->setup(CMP_DEFLATE);
+    HttpUnitTestSetup::set_compress_stream(session_data, SRC_SERVER, compress_stream);
+
+    const uint32_t pre_filled_len = MAX_OCTETS - 5;
+    auto* pre_filled_buf = new uint8_t[MAX_OCTETS];
+    HttpUnitTestSetup::set_partial_buffer(session_data, SRC_SERVER, pre_filled_buf, pre_filled_len);
+
+    const uint32_t flow_target = 8;
+
+    uint8_t scan_buf[scan_buf_size];
+    memcpy(scan_buf, deflate_compressed, deflate_compressed_size);
+
+    HttpBodyHXCutter cutter(20, false, nullptr, session_data, SRC_SERVER);
+
+    auto* infractions = HttpUnitTestSetup::get_infractions(session_data, SRC_SERVER);
+    HttpEventGen events;
+
+    auto result = cutter.cut(scan_buf, 1, infractions, &events, flow_target, true, HX_BODY_NOT_COMPLETE);
+    CHECK_EQUAL(SCAN_NOT_FOUND, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(0, cutter.get_num_flush());
+
+    result = cutter.cut(scan_buf + 1, 7, infractions, &events, flow_target, false, HX_BODY_NOT_COMPLETE);
+    // num_flush is reduced because decompress ran out of space
+    CHECK_EQUAL(SCAN_FOUND_PIECE, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(6, cutter.get_num_flush());
+}
+
+TEST(hx_cutter_decompression_test, octets_seen_last_segment_overrun)
+{
+    auto* compress_stream = new HttpCompressStream;
+    compress_stream->setup(CMP_DEFLATE);
+    HttpUnitTestSetup::set_compress_stream(session_data, SRC_SERVER, compress_stream);
+
+    const uint32_t pre_filled_len = MAX_OCTETS - 5;
+    auto* pre_filled_buf = new uint8_t[MAX_OCTETS];
+    HttpUnitTestSetup::set_partial_buffer(session_data, SRC_SERVER, pre_filled_buf, pre_filled_len);
+
+    const uint32_t flow_target = 8;
+
+    uint8_t scan_buf[scan_buf_size];
+    memcpy(scan_buf, deflate_compressed, deflate_compressed_size);
+
+    HttpBodyHXCutter cutter(20, false, nullptr, session_data, SRC_SERVER);
+
+    auto* infractions = HttpUnitTestSetup::get_infractions(session_data, SRC_SERVER);
+    HttpEventGen events;
+
+    auto result = cutter.cut(scan_buf, 1, infractions, &events, flow_target, true, HX_BODY_NOT_COMPLETE);
+    CHECK_EQUAL(SCAN_NOT_FOUND, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(0, cutter.get_num_flush());
+
+    result = cutter.cut(scan_buf + 1, 8, infractions, &events, flow_target, false, HX_BODY_LAST_SEG);
+    // num_flush is reduced because decompress ran out of space
+    CHECK_EQUAL(SCAN_FOUND_PIECE, result);
+    CHECK_EQUAL(1, cutter.get_octets_seen());
+    CHECK_EQUAL(6, cutter.get_num_flush());
+}
+
+int main(int argc, char** argv)
+{
+    return CommandLineTestRunner::RunAllTests(argc, argv);
+}
+
index 959706d78b024edb0574dd72471699c1a81aface..8e9f29228e4d943f79527c5dcc94ab3fca228e98 100644 (file)
@@ -74,8 +74,13 @@ const StreamBuffer StreamSplitter::reassemble(snort::Flow*, unsigned int, unsign
     return buf;
 }
 unsigned StreamSplitter::max(snort::Flow*) { return 0; }
+uint8_t TraceApi::get_constraints_generation() { return 0; }
+void TraceApi::filter(const Packet&) { }
+void trace_vprintf(const char*, TraceLevel, const char*, const snort::Packet*, const char*, va_list) { }
 }
 
+THREAD_LOCAL const snort::Trace* http_trace = nullptr;
+
 HttpParaList::UriParam::UriParam() {}
 HttpParaList::JsNormParam::~JsNormParam() {}
 HttpParaList::~HttpParaList() {}
index 7463405a0cbbc9790125459f0d0fbbf567597ffd..aea8ce0e93452a3c1bb07d8ed47d3d90c6555927 100644 (file)
@@ -22,6 +22,8 @@
 #define HTTP_UNIT_TEST_HELPERS_H
 
 #include "service_inspectors/http_inspect/http_common.h"
+#include "service_inspectors/http_inspect/http_compress_stream.h"
+#include "service_inspectors/http_inspect/http_event.h"
 #include "service_inspectors/http_inspect/http_flow_data.h"
 
 class HttpUnitTestSetup
@@ -33,6 +35,18 @@ public:
         { assert(flow_data!=nullptr); return flow_data->type_expected; }
     static void half_reset(HttpFlowData* flow_data, HttpCommon::SourceId source_id)
         { assert(flow_data!=nullptr); flow_data->half_reset(source_id); }
+    static void set_compress_stream(HttpFlowData* flow_data, HttpCommon::SourceId source_id,
+        HttpCompressStream* stream)
+        { assert(flow_data!=nullptr); flow_data->compress[source_id] = stream; }
+    static void set_partial_buffer(HttpFlowData* flow_data, HttpCommon::SourceId source_id,
+        uint8_t* buf, uint32_t length)
+    {
+        assert(flow_data!=nullptr);
+        flow_data->partial_buffer[source_id] = buf;
+        flow_data->partial_buffer_length[source_id] = length;
+    }
+    static HttpInfractions* get_infractions(HttpFlowData* flow_data, HttpCommon::SourceId source_id)
+        { assert(flow_data!=nullptr); return flow_data->infractions[source_id]; }
 };
 
 #endif