]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #1349 in SNORT/snort3 from nhttp113 to master
authorTom Peters (thopeter) <thopeter@cisco.com>
Fri, 7 Sep 2018 15:19:04 +0000 (11:19 -0400)
committerTom Peters (thopeter) <thopeter@cisco.com>
Fri, 7 Sep 2018 15:19:04 +0000 (11:19 -0400)
Squashed commit of the following:

commit ff828f6ea9547c4377e8b1162c920839d4b78acb
Author: Tom Peters <thopeter@cisco.com>
Date:   Fri Jul 27 11:25:05 2018 -0400

    http_inspect: split and inspect immediately upon reaching depth

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_msg_body_chunk.cc
src/service_inspectors/http_inspect/http_msg_section.cc
src/service_inspectors/http_inspect/http_stream_splitter_finish.cc
src/service_inspectors/http_inspect/http_stream_splitter_scan.cc
src/service_inspectors/http_inspect/http_tables.cc

index d6ff25b861cf6108ccae923393717539210454ca..454bc30262b458c3e54f128d75035d5f552fa263 100644 (file)
@@ -26,7 +26,7 @@
 using namespace HttpEnums;
 
 ScanResult HttpStartCutter::cut(const uint8_t* buffer, uint32_t length,
-    HttpInfractions* infractions, HttpEventGen* events, uint32_t, uint32_t)
+    HttpInfractions* infractions, HttpEventGen* events, uint32_t, bool)
 {
     for (uint32_t k = 0; k < length; k++)
     {
@@ -154,7 +154,7 @@ HttpStartCutter::ValidationResult HttpStatusCutter::validate(uint8_t octet,
 }
 
 ScanResult HttpHeaderCutter::cut(const uint8_t* buffer, uint32_t length,
-    HttpInfractions* infractions, HttpEventGen* events, uint32_t, uint32_t)
+    HttpInfractions* infractions, HttpEventGen* events, uint32_t, bool)
 {
     // Header separators: leading \r\n, leading \n, nonleading \r\n\r\n, nonleading \n\r\n,
     // nonleading \r\n\n, and nonleading \n\n. The separator itself becomes num_excess which is
@@ -251,9 +251,9 @@ ScanResult HttpHeaderCutter::cut(const uint8_t* buffer, uint32_t length,
 }
 
 ScanResult HttpBodyClCutter::cut(const uint8_t*, uint32_t length, HttpInfractions*,
-    HttpEventGen*, uint32_t flow_target, uint32_t flow_max)
+    HttpEventGen*, uint32_t flow_target, bool stretch)
 {
-    assert(remaining > 0);
+    assert(remaining > octets_seen);
 
     // Are we skipping to the next message?
     if (flow_target == 0)
@@ -272,24 +272,62 @@ ScanResult HttpBodyClCutter::cut(const uint8_t*, uint32_t length, HttpInfraction
         }
     }
 
-    // The normal body section size is flow_target. But if there are only flow_max or less
-    // remaining we take the whole thing rather than leave a small final section.
-    if (remaining <= flow_max)
+    // A target that is bigger than the entire rest of the message body makes no sense
+    if (remaining <= flow_target)
     {
-        num_flush = remaining;
-        remaining = 0;
-        return SCAN_FOUND;
+        flow_target = remaining;
+        stretch = false;
     }
-    else
+
+    if (!stretch)
     {
         num_flush = flow_target;
-        remaining -= num_flush;
+        if (num_flush < remaining)
+        {
+            remaining -= num_flush;
+            return SCAN_FOUND_PIECE;
+        }
+        else
+        {
+            remaining = 0;
+            return SCAN_FOUND;
+        }
+    }
+
+    if (octets_seen + length < flow_target)
+    {
+        octets_seen += length;
+        return SCAN_NOT_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
+        if (octets_seen + length <= flow_target + MAX_SECTION_STRETCH)
+            num_flush = length;
+        else
+            num_flush = flow_target - octets_seen;
+        remaining -= octets_seen + 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;
+    }
+
+    // Cannot stretch to the end of the message body. Cut at the original target.
+    num_flush = flow_target - octets_seen;
+    remaining -= flow_target;
+    return SCAN_FOUND_PIECE;
 }
 
 ScanResult HttpBodyOldCutter::cut(const uint8_t*, uint32_t length, HttpInfractions*, HttpEventGen*,
-    uint32_t flow_target, uint32_t)
+    uint32_t flow_target, bool stretch)
 {
     if (flow_target == 0)
     {
@@ -302,16 +340,35 @@ ScanResult HttpBodyOldCutter::cut(const uint8_t*, uint32_t length, HttpInfractio
         return SCAN_DISCARD_PIECE;
     }
 
-    num_flush = flow_target;
-    return SCAN_FOUND_PIECE;
+    if (octets_seen + length < flow_target)
+    {
+        // Not enough data yet to create a message section
+        octets_seen += length;
+        return 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;
+        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;
+        return SCAN_FOUND_PIECE;
+    }
 }
 
 ScanResult HttpBodyChunkCutter::cut(const uint8_t* buffer, uint32_t length,
-    HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, uint32_t)
+    HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch)
 {
     // Are we skipping through the rest of this chunked body to the trailers and the next message?
     const bool discard_mode = (flow_target == 0);
 
+    const uint32_t adjusted_target = stretch ? MAX_SECTION_STRETCH + flow_target : flow_target;
+
     for (int32_t k=0; k < static_cast<int32_t>(length); k++)
     {
         switch (curr_state)
@@ -485,16 +542,16 @@ ScanResult HttpBodyChunkCutter::cut(const uint8_t* buffer, uint32_t length,
             // Moving through the chunk data
           {
             uint32_t skip_amount = (length-k <= expected) ? length-k : expected;
-            if (!discard_mode && (skip_amount > flow_target-data_seen))
-            { // Do not exceed requested section size
-                skip_amount = flow_target-data_seen;
+            if (!discard_mode && (skip_amount > adjusted_target-data_seen))
+            { // Do not exceed requested section size (including stretching)
+                skip_amount = adjusted_target-data_seen;
             }
             k += skip_amount - 1;
             if ((expected -= skip_amount) == 0)
             {
                 curr_state = CHUNK_DCRLF1;
             }
-            if ((data_seen += skip_amount) == flow_target)
+            if ((data_seen += skip_amount) == adjusted_target)
             {
                 data_seen = 0;
                 num_flush = k+1;
@@ -556,13 +613,13 @@ ScanResult HttpBodyChunkCutter::cut(const uint8_t* buffer, uint32_t length,
             // that there were chunk header bytes between the last good chunk and the point where
             // the failure occurred. These will not have been counted in data_seen because we
             // 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 flow_target making the
+            // 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 <= flow_target-data_seen) ? skip_amount :
-                flow_target-data_seen;
+            skip_amount = (skip_amount <= adjusted_target-data_seen) ? skip_amount :
+                adjusted_target-data_seen;
             k += skip_amount - 1;
-            if ((data_seen += skip_amount) == flow_target)
+            if ((data_seen += skip_amount) == adjusted_target)
             {
                 data_seen = 0;
                 num_flush = k+1;
@@ -577,6 +634,14 @@ ScanResult HttpBodyChunkCutter::cut(const uint8_t* buffer, uint32_t length,
         return SCAN_DISCARD_PIECE;
     }
 
+    if (data_seen >= flow_target)
+    {
+        // We passed the flow_target and stretched to the end of the segment
+        data_seen = 0;
+        num_flush = length;
+        return SCAN_FOUND_PIECE;
+    }
+
     octets_seen += length;
     return SCAN_NOT_FOUND;
 }
index 593a86a279ac0ba773ad241c75da194e74ac1eee..114630d1659c4bdbb77c44977a567b5f9dbaa1d1 100644 (file)
@@ -35,8 +35,8 @@ class HttpCutter
 public:
     virtual ~HttpCutter() = default;
     virtual HttpEnums::ScanResult cut(const uint8_t* buffer, uint32_t length,
-        HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target,
-        uint32_t flow_max) = 0;
+        HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch)
+        = 0;
     uint32_t get_num_flush() const { return num_flush; }
     uint32_t get_octets_seen() const { return octets_seen; }
     uint32_t get_num_excess() const { return num_crlf; }
@@ -56,7 +56,7 @@ class HttpStartCutter : public HttpCutter
 {
 public:
     HttpEnums::ScanResult cut(const uint8_t* buffer, uint32_t length,
-        HttpInfractions* infractions, HttpEventGen* events, uint32_t, uint32_t) override;
+        HttpInfractions* infractions, HttpEventGen* events, uint32_t, bool) override;
 
 protected:
     enum ValidationResult { V_GOOD, V_BAD, V_TBD };
@@ -85,7 +85,7 @@ class HttpHeaderCutter : public HttpCutter
 {
 public:
     HttpEnums::ScanResult cut(const uint8_t* buffer, uint32_t length,
-        HttpInfractions* infractions, HttpEventGen* events, uint32_t, uint32_t) override;
+        HttpInfractions* infractions, HttpEventGen* events, uint32_t, bool) override;
     uint32_t get_num_head_lines() const override { return num_head_lines; }
 
 private:
@@ -100,7 +100,8 @@ public:
     explicit HttpBodyClCutter(int64_t expected_length) : remaining(expected_length)
         { assert(remaining > 0); }
     HttpEnums::ScanResult cut(const uint8_t*, uint32_t length, HttpInfractions*, HttpEventGen*,
-        uint32_t flow_target, uint32_t flow_max) override;
+        uint32_t flow_target, bool stretch) override;
+    void soft_reset() override { octets_seen = 0; }
 
 private:
     int64_t remaining;
@@ -110,14 +111,15 @@ class HttpBodyOldCutter : public HttpCutter
 {
 public:
     HttpEnums::ScanResult cut(const uint8_t*, uint32_t, HttpInfractions*, HttpEventGen*,
-        uint32_t flow_target, uint32_t) override;
+        uint32_t flow_target, bool stretch) override;
+    void soft_reset() override { octets_seen = 0; }
 };
 
 class HttpBodyChunkCutter : public HttpCutter
 {
 public:
     HttpEnums::ScanResult cut(const uint8_t* buffer, uint32_t length,
-        HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, uint32_t)
+        HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch)
         override;
     bool get_is_broken_chunk() const override { return curr_state == HttpEnums::CHUNK_BAD; }
     uint32_t get_num_good_chunks() const override { return num_good_chunks; }
index a2a96bc36b9953f7ead8c326d9788b2d5029cf1f..e721a3f701f18901dcad65f68795aee34c5519c4 100644 (file)
@@ -26,8 +26,8 @@ namespace HttpEnums
 {
 static const int MAX_OCTETS = 63780;
 static const int GZIP_BLOCK_SIZE = 2048;
-static const int FINAL_GZIP_BLOCK_SIZE = 2304; // compromise value, too big causes gzip overruns
-                                               // too small leaves too many little end sections
+static const int MAX_SECTION_STRETCH = 1460;
+
 static const uint32_t HTTP_GID = 119;
 static const int GZIP_WINDOW_BITS = 31;
 static const int DEFLATE_WINDOW_BITS = 15;
@@ -284,7 +284,7 @@ enum EventSid
     EVENT_UNESCAPED_SPACE_URI,
     EVENT_PIPELINE_MAX,
 
-    EVENT_ANOM_SERVER = 101,
+    EVENT_OBSOLETE_ANOM_SERVER = 101,      // Previously used, do not reuse this number
     EVENT_INVALID_STATCODE,
     EVENT_UNUSED_1,
     EVENT_UTF_NORM_FAIL,
index 00e2160401c05909318bec2b74917385b425142d..610ea5e187bc3f1f274638271b9bbd94ecde54d1 100644 (file)
@@ -101,7 +101,7 @@ void HttpFlowData::half_reset(SourceId source_id)
     data_length[source_id] = STAT_NOT_PRESENT;
     body_octets[source_id] = STAT_NOT_PRESENT;
     section_size_target[source_id] = 0;
-    section_size_max[source_id] = 0;
+    stretch_section_to_packet[source_id] = false;
     file_depth_remaining[source_id] = STAT_NOT_PRESENT;
     detect_depth_remaining[source_id] = STAT_NOT_PRESENT;
     detection_status[source_id] = DET_REACTIVATING;
index 6e0124231931c4c6a2af36884e2f870ebaa172a9..11011f229410c9dbfe42b1936875bd84ac10afeb 100644 (file)
@@ -114,9 +114,9 @@ private:
     uint64_t zero_nine_expected = 0;
     int64_t data_length[2] = { HttpEnums::STAT_NOT_PRESENT, HttpEnums::STAT_NOT_PRESENT };
     uint32_t section_size_target[2] = { 0, 0 };
-    uint32_t section_size_max[2] = { 0, 0 };
     HttpEnums::CompressId compression[2] = { HttpEnums::CMP_NONE, HttpEnums::CMP_NONE };
     HttpEnums::DetectionStatus detection_status[2] = { HttpEnums::DET_ON, HttpEnums::DET_ON };
+    bool stretch_section_to_packet[2] = { false, false };
 
     // *** Inspector's internal data about the current message
     struct FdCallbackContext
index 80669069dfa072b706830bdf59fa0eaecf28586e..7be55ff77c2ddeb4a07feb5421b3e49a732aca0b 100644 (file)
@@ -27,10 +27,11 @@ using namespace HttpEnums;
 
 void HttpMsgBodyChunk::update_flow()
 {
-    // Cutter was deleted by splitter when zero-length chunk received
+    session_data->body_octets[source_id] = body_octets;
+
+    // Cutter was deleted by splitter when zero-length chunk received or at TCP close
     if (session_data->cutter[source_id] == nullptr)
     {
-        session_data->body_octets[source_id] = body_octets;
         session_data->trailer_prep(source_id);
         if (session_data->mime_state[source_id] != nullptr)
         {
@@ -46,7 +47,6 @@ void HttpMsgBodyChunk::update_flow()
     }
     else
     {
-        session_data->body_octets[source_id] = body_octets;
         update_depth();
     }
     session_data->section_type[source_id] = SEC__NOT_COMPUTE;
index 2dcc4a145541cd240bcd5a5010b3bcacd66a23c6..e6ee696068e3d2ce7f07f1149a1939db7b2cac46 100644 (file)
@@ -70,40 +70,48 @@ void HttpMsgSection::create_event(int sid)
 
 void HttpMsgSection::update_depth() const
 {
-    if ((session_data->detect_depth_remaining[source_id] <= 0) &&
+    int64_t& file_depth_remaining = session_data->file_depth_remaining[source_id];
+    int64_t& detect_depth_remaining = session_data->detect_depth_remaining[source_id];
+
+    if ((detect_depth_remaining <= 0) &&
         (session_data->detection_status[source_id] == DET_ON))
     {
         session_data->detection_status[source_id] = DET_DEACTIVATING;
     }
 
-    if ((session_data->file_depth_remaining[source_id] <= 0) &&
-        (session_data->detect_depth_remaining[source_id] <= 0))
+    if (detect_depth_remaining <= 0)
     {
-        // Don't need any more of the body
-        session_data->section_size_target[source_id] = 0;
-        session_data->section_size_max[source_id] = 0;
+        if (file_depth_remaining <= 0)
+        {
+            // Don't need any more of the body
+            session_data->section_size_target[source_id] = 0;
+        }
+        else
+        {
+            // Just for file processing
+            session_data->section_size_target[source_id] = snort::SnortConfig::get_conf()->max_pdu;
+            session_data->stretch_section_to_packet[source_id] = true;
+        }
         return;
     }
 
-    const int random_increment = FlushBucket::get_size() - 192;
-    assert((random_increment >= -64) && (random_increment <= 63));
+    const unsigned target_size = (session_data->compression[source_id] == CMP_NONE) ?
+        snort::SnortConfig::get_conf()->max_pdu : GZIP_BLOCK_SIZE;
 
-    switch (session_data->compression[source_id])
+    if (detect_depth_remaining <= target_size)
     {
-    case CMP_NONE:
-      {
-        unsigned max_pdu = snort::SnortConfig::get_conf()->max_pdu;
-        session_data->section_size_target[source_id] = max_pdu + random_increment;
-        session_data->section_size_max[source_id] = max_pdu + (max_pdu >> 1);
-        break;
-      }
-    case CMP_GZIP:
-    case CMP_DEFLATE:
-        session_data->section_size_target[source_id] = GZIP_BLOCK_SIZE + random_increment;
-        session_data->section_size_max[source_id] = FINAL_GZIP_BLOCK_SIZE;
-        break;
-    default:
-        assert(false);
+        // Go to detection as soon as detect depth is reached
+        session_data->section_size_target[source_id] = detect_depth_remaining;
+        session_data->stretch_section_to_packet[source_id] = true;
+    }
+    else
+    {
+        // Randomize the split point a little bit to make it harder to evade detection.
+        // FlushBucket provides pseudo random numbers in the range 128 to 255.
+        const int random_increment = FlushBucket::get_size() - 192;
+        assert((random_increment >= -64) && (random_increment <= 63));
+        session_data->section_size_target[source_id] = target_size + random_increment;
+        session_data->stretch_section_to_packet[source_id] = false;
     }
 }
 
index 72b8725386159744284c023a12476b913b8e8e5b..4c8f82abf6d54dfcbd1084511893449546284292 100644 (file)
@@ -90,6 +90,9 @@ bool HttpStreamSplitter::finish(snort::Flow* flow)
             session_data->cutter[source_id]->get_num_good_chunks(),
             session_data->cutter[source_id]->get_octets_seen(),
             true);
+        delete session_data->cutter[source_id];
+        session_data->cutter[source_id] = nullptr;
+
         return true;
     }
 
index 3f1af00f0ba335051444f20708c40b77e56f706f..97fc984e972d967cb0e51e42595434a579e917cd 100644 (file)
@@ -167,7 +167,8 @@ StreamSplitter::Status HttpStreamSplitter::scan(Flow* flow, const uint8_t* data,
     const uint32_t max_length = MAX_OCTETS - cutter->get_octets_seen();
     const ScanResult cut_result = cutter->cut(data, (length <= max_length) ? length :
         max_length, session_data->get_infractions(source_id), session_data->get_events(source_id),
-        session_data->section_size_target[source_id], session_data->section_size_max[source_id]);
+        session_data->section_size_target[source_id],
+        session_data->stretch_section_to_packet[source_id]);
     switch (cut_result)
     {
     case SCAN_NOT_FOUND:
index 3fcf95fb13a0a88bad5191c1502599ba275ee330..cf2cd3505a9c6173f73bc0e34a8ec9067a1ff5bf 100644 (file)
@@ -322,7 +322,7 @@ const snort::RuleMap HttpModule::http_events[] =
     { EVENT_SIMPLE_REQUEST,             "simple request" },
     { EVENT_UNESCAPED_SPACE_URI,        "unescaped space in HTTP URI" },
     { EVENT_PIPELINE_MAX,               "too many pipelined requests" },
-    { EVENT_ANOM_SERVER,                "anomalous http server on undefined HTTP port" },
+    { EVENT_OBSOLETE_ANOM_SERVER,       "obsolete event--deleted" },
     { EVENT_INVALID_STATCODE,           "invalid status code in HTTP response" },
     { EVENT_UNUSED_1,                   "unused event number--should not appear" },
     { EVENT_UTF_NORM_FAIL,              "HTTP response has UTF charset that failed to normalize" },