]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #2407 in SNORT/snort3 from ~MDAGON/snort3:translate2 to master
authorMike Stepanek (mstepane) <mstepane@cisco.com>
Thu, 27 Aug 2020 19:31:52 +0000 (19:31 +0000)
committerMike Stepanek (mstepane) <mstepane@cisco.com>
Thu, 27 Aug 2020 19:31:52 +0000 (19:31 +0000)
Squashed commit of the following:

commit 169cd2c9214765cf3756a7ba82e5f15161dd13fa
Author: mdagon <mdagon@cisco.com>
Date:   Wed Jul 29 10:37:03 2020 -0400

    payload_injector: support http2 injection

src/payload_injector/dev_notes.txt
src/payload_injector/payload_injector_module.cc
src/payload_injector/payload_injector_module.h
src/payload_injector/payload_injector_translate_page.cc
src/payload_injector/test/payload_injector_test.cc
src/payload_injector/test/payload_injector_translate_test.cc
src/service_inspectors/http2_inspect/http2_data_cutter.cc
src/service_inspectors/http2_inspect/http2_flow_data.h
src/service_inspectors/http2_inspect/http2_stream_splitter_impl.cc

index 0c99cf1d19a52421c64e4ca58a39024808dccc50..99500cff78ecc7493f22e221b08a30669260c28d 100644 (file)
@@ -4,7 +4,10 @@ what to inject. Payload injector is responsible for flow-control concerns such
 as transmitting RST flags to endpoints as well as blocking flows within Snort.
 It coordinates with the Active component of Snort to perform these functions.
 
-Currently it is being used for HTTP/1 injections. HTTP/2 support is in development.
+Currently it is being used for HTTP/1 and HTTP/2 injections. For HTTP/2
+injection triggered while server-to-client flow of traffic is in a middle of a
+frame is not supported. The traffic will be blocked, but the page will not be
+injected/displayed.
 
 get_http2_payload supports translation of HTTP block/redirect page to HTTP2.
 Current implementation is limited, the constraints are specified in
index 8a1d33a645bce40d55bcf247c8854f24a6d3d966..dab8cb89c8664a4f76b1fcd8d549a26503eb5603 100644 (file)
@@ -27,6 +27,8 @@
 #include "detection/detection_engine.h"
 #include "packet_io/active.h"
 #include "protocols/packet.h"
+#include "service_inspectors/http2_inspect/http2_flow_data.h"
+#include "utils/util.h"
 
 #define s_name "payload_injector"
 #define s_help \
@@ -40,9 +42,28 @@ const PegInfo payload_injector_pegs[] =
 {
     { CountType::SUM, "http_injects", "total number of http injections" },
     { CountType::SUM, "http2_injects", "total number of http2 injections" },
+    { CountType::SUM, "http2_translate_err", "total number of http2 page translation errors" },
+    { CountType::SUM, "http2_mid_frame", "total number of attempts to inject mid-frame" },
     { CountType::END, nullptr, nullptr }
 };
 
+// Should have an entry for each error in InjectionReturnStatus
+static const std::map <InjectionReturnStatus, const char*> InjectionErrorToString =
+{
+    { ERR_INJECTOR_NOT_CONFIGURED, "Payload injector is not configured" },
+    { ERR_STREAM_NOT_ESTABLISHED, "TCP stream not established" },
+    { ERR_UNIDENTIFIED_PROTOCOL, "Unidentified protocol" },
+    { ERR_HTTP2_STREAM_ID_0, "HTTP/2 - injection to stream 0" },
+    { ERR_PAGE_TRANSLATION, "Error in translating HTTP block page to HTTP/2. "
+      "Unsupported or bad format." },
+    { ERR_HTTP2_MID_FRAME, "HTTP/2 - attempt to inject mid frame. Currently not supported." },
+    { ERR_HTTP2_HDR_FIELD_VAL_LEN, "HTTP/2 header field value length > 127. "
+      " Currently not supported." },
+    { ERR_TRANSLATED_HDRS_SIZE,
+      "HTTP/2 translated header size is bigger than expected. Update max size." },
+    { ERR_HTTP2_BODY_SIZE, "HTTP/2 body is > 16k. Currently not supported." }
+};
+
 bool PayloadInjectorModule::configured = false;
 
 PayloadInjectorModule::PayloadInjectorModule() :
@@ -61,6 +82,46 @@ bool PayloadInjectorModule::end(const char*, int, SnortConfig*)
     return true;
 }
 
+InjectionReturnStatus PayloadInjectorModule::inject_http2_payload(Packet* p,
+    const InjectionControl& control, EncodeFlags df)
+{
+    InjectionReturnStatus status;
+
+    if (control.stream_id == 0)
+        status = ERR_HTTP2_STREAM_ID_0;
+    else
+    {
+        // Check if mid frame
+        Http2FlowData* const session_data =
+            (Http2FlowData*)p->flow->get_flow_data(Http2FlowData::inspector_id);
+        if (!session_data || session_data->is_mid_frame())
+        {
+            payload_injector_stats.http2_mid_frame++;
+            // FIXIT-E mid-frame injection not supported
+            status = ERR_HTTP2_MID_FRAME;
+        }
+        else
+        {
+            uint8_t* http2_payload;
+            uint32_t payload_len;
+            status = get_http2_payload(control, http2_payload, payload_len);
+            if (status == INJECTION_SUCCESS)
+            {
+                p->active->send_data(p, df, http2_payload, payload_len);
+                snort_free(http2_payload);
+                payload_injector_stats.http2_injects++;
+                return INJECTION_SUCCESS;
+            }
+            else
+                payload_injector_stats.http2_translate_err++;
+        }
+    }
+
+    // If we got here, shouldn't inject the page
+    p->active->send_data(p, df, nullptr, 0);
+    return status;
+}
+
 InjectionReturnStatus PayloadInjectorModule::inject_http_payload(Packet* p,
     const InjectionControl& control)
 {
@@ -83,18 +144,9 @@ InjectionReturnStatus PayloadInjectorModule::inject_http_payload(Packet* p,
                 p->active->send_data(p, df, control.http_page, control.http_page_len);
             }
             else if (strcmp(p->flow->gadget->get_name(),"http2_inspect") == 0)
-            {
-                if (control.stream_id != 0)
-                {
-                    payload_injector_stats.http2_injects++;
-                    // FIXIT-E translate page, inject payload
-                    p->active->send_data(p, df, nullptr, 0);
-                }
-                else
-                    status = ERR_HTTP2_STREAM_ID_0;
-            }
-           else
-               status = ERR_UNIDENTIFIED_PROTOCOL;
+                status = inject_http2_payload(p, control, df);
+            else
+                status = ERR_UNIDENTIFIED_PROTOCOL;
         }
         else
             status = ERR_STREAM_NOT_ESTABLISHED;
@@ -111,3 +163,11 @@ InjectionReturnStatus PayloadInjectorModule::inject_http_payload(Packet* p,
 
     return status;
 }
+
+const char* PayloadInjectorModule::get_err_string(InjectionReturnStatus status)
+{
+    auto iter = InjectionErrorToString.find(status);
+    assert (iter != InjectionErrorToString.end());
+    return iter->second;
+}
+
index 7bd6eb241b33d955778fec3b71f9edc7f1041fed..582ade686354c4234f39586d4e94019f3acce710 100644 (file)
@@ -21,6 +21,7 @@
 #ifndef PAYLOAD_INJECTOR_MODULE_H
 #define PAYLOAD_INJECTOR_MODULE_H
 
+#include "framework/codec.h"
 #include "framework/module.h"
 
 namespace snort
@@ -32,6 +33,8 @@ struct PayloadInjectorCounts
 {
     PegCount http_injects;
     PegCount http2_injects;
+    PegCount http2_translate_err;
+    PegCount http2_mid_frame;
 };
 
 extern THREAD_LOCAL PayloadInjectorCounts payload_injection_stats;
@@ -41,9 +44,14 @@ enum InjectionReturnStatus : int8_t
     INJECTION_SUCCESS = 1,
     ERR_INJECTOR_NOT_CONFIGURED = -1,
     ERR_STREAM_NOT_ESTABLISHED = -2,
-    ERR_HTTP2_STREAM_ID_0 = -3,
-    ERR_UNIDENTIFIED_PROTOCOL = -4,
-    ERR_PAGE_TRANSLATION = -4,
+    ERR_UNIDENTIFIED_PROTOCOL = -3,
+    ERR_HTTP2_STREAM_ID_0 = -4,
+    ERR_PAGE_TRANSLATION = -5,
+    ERR_HTTP2_MID_FRAME = -6,
+    ERR_HTTP2_HDR_FIELD_VAL_LEN = -7,
+    ERR_TRANSLATED_HDRS_SIZE = -8,
+    ERR_HTTP2_BODY_SIZE = -9,
+    // Update InjectionErrorToString when adding/removing error codes
 };
 
 struct InjectionControl
@@ -65,8 +73,11 @@ public:
 
     bool end(const char*, int, snort::SnortConfig*) override;
 
-    static InjectionReturnStatus inject_http_payload(snort::Packet* p, const InjectionControl& control);
+    static InjectionReturnStatus inject_http_payload(snort::Packet* p, const
+        InjectionControl& control);
 
+    static const char* get_err_string(InjectionReturnStatus status);
+    
 #ifdef UNIT_TEST
     void set_configured(bool val) { configured = val; }
 #endif
@@ -74,10 +85,14 @@ public:
 private:
     static bool configured;
 
+    static InjectionReturnStatus inject_http2_payload(snort::Packet* p, const
+         InjectionControl& control, snort::EncodeFlags df);
+
 #ifdef UNIT_TEST
+
 public:
 #endif
-    static InjectionReturnStatus get_http2_payload(InjectionControl control, uint8_t *& http2_payload, uint32_t & payload_len);
+    static InjectionReturnStatus get_http2_payload(InjectionControl control, uint8_t*& http2_payload, uint32_t& payload_len);
 };
 
 #endif
index c0179af26b386cad66c82473cd021c3661da0ef6..31da9ea23a7bea393216baa36ce4d2ee162d745b 100644 (file)
 // payload_injector_translate_page.cc author Maya Dagon <mdagon@cisco.com>
 
 // Translates HTTP 1.1 block/redirect page to HTTP2.
-// 1. Headers are separated by /r/n
-// 2. Headers end with /r/n
+// 1. Headers are separated by \r\n or \n
+// 2. Headers end with \r\n or \n
 // 3. Must have headers and body
 // 4. Translated header length <= 2000
-// 5. Supported: HTTP/1.1 403, HTTP/1.1 307, Connection: close,
+// 5. Supported: HTTP/1.1 200, HTTP/1.1 403, HTTP/1.1 307, Connection: close,
 //     Content-Length: , Content-Type: , Set-Cookie: , Location:
 
 #ifdef HAVE_CONFIG_H
 #include "service_inspectors/http2_inspect/http2_enum.h"
 #include "utils/util.h"
 
+using namespace Http2Enums;
+
 static const char status_403[] = "HTTP/1.1 403";
 static uint8_t status_403_h2[] = { 0, 7, ':', 's', 't', 'a', 't', 'u', 's', 3, '4', '0', '3' };
 static const char status_307[] = "HTTP/1.1 307";
 static uint8_t status_307_h2[] = { 0, 7, ':', 's', 't', 'a', 't', 'u', 's', 3, '3', '0', '7' };
+static const char status_200[] = "HTTP/1.1 200";
+static uint8_t status_200_h2[] =  { 0x88 };
 static const char connection[] = "Connection: close";
 static const uint8_t connection_h2[] = { 0, 10, 'c','o','n','n','e','c','t','i','o','n',
                                          5, 'c', 'l', 'o', 's', 'e' };
@@ -50,10 +54,10 @@ static const char location[] = "Location: ";
 static const uint32_t max_hdr_size = 2000;
 
 // Write literal header field
-static InjectionReturnStatus write_indexed(char* hdr, uint32_t len, uint8_t*& out,
+static InjectionReturnStatus write_indexed(const uint8_t* hdr, uint32_t len, uint8_t*& out,
     uint32_t& out_free_space, const uint8_t* ind, uint8_t ind_size)
 {
-    const char* sep = (char*)memchr(hdr,':',len);
+    const uint8_t* sep = (const uint8_t*)memchr(hdr,':',len);
     assert(sep != nullptr);
     const uint32_t skip_len = strlen(": ");
     assert((sep - hdr) >= skip_len);
@@ -61,7 +65,10 @@ static InjectionReturnStatus write_indexed(char* hdr, uint32_t len, uint8_t*& ou
     const uint8_t max_val_len = (1<<7) - 1; // FIXIT-E bigger than this will have to be 7 bit
                                             // prefix
                                             // encoded - currently not supported
-    if ((val_len == 0) || (val_len > max_val_len))
+    if (val_len > max_val_len)
+        return ERR_HTTP2_HDR_FIELD_VAL_LEN;
+
+    if (val_len == 0)
         return ERR_PAGE_TRANSLATION;
 
     if (out_free_space < (val_len + 1 + ind_size))
@@ -69,7 +76,7 @@ static InjectionReturnStatus write_indexed(char* hdr, uint32_t len, uint8_t*& ou
 #ifndef UNIT_TEST
         assert(false);  // increase max_hdr_size
 #endif
-        return ERR_PAGE_TRANSLATION;
+        return ERR_TRANSLATED_HDRS_SIZE;
     }
 
     memcpy(out, ind, ind_size);
@@ -91,7 +98,7 @@ static InjectionReturnStatus write_translation(uint8_t*& out, uint32_t& out_free
 #ifndef UNIT_TEST
         assert(false);  // increase max_hdr_size
 #endif
-        return ERR_PAGE_TRANSLATION;
+        return ERR_TRANSLATED_HDRS_SIZE;
     }
 
     memcpy(out, translation, size);
@@ -101,7 +108,7 @@ static InjectionReturnStatus write_translation(uint8_t*& out, uint32_t& out_free
     return INJECTION_SUCCESS;
 }
 
-static InjectionReturnStatus translate_hdr_field(char* hdr, uint32_t len, uint8_t*& out,
+static InjectionReturnStatus translate_hdr_field(const uint8_t* hdr, uint32_t len, uint8_t*& out,
     uint32_t& out_free_space)
 {
     if (len > strlen(status_403) && memcmp(hdr, status_403, strlen(status_403)) == 0)
@@ -110,7 +117,11 @@ static InjectionReturnStatus translate_hdr_field(char* hdr, uint32_t len, uint8_
     }
     else if (len > strlen(status_307) && memcmp(hdr, status_307, strlen(status_307)) == 0)
     {
-        return write_translation(out, out_free_space,status_307_h2, sizeof(status_307_h2));
+        return write_translation(out, out_free_space, status_307_h2, sizeof(status_307_h2));
+    }
+    else if (len > strlen(status_200) && memcmp(hdr, status_200, strlen(status_200)) == 0)
+    {
+        return write_translation(out, out_free_space, status_200_h2, sizeof(status_200_h2));
     }
     else if (len == strlen(connection) && memcmp(hdr, connection, strlen(connection))==0)
     {
@@ -143,37 +154,46 @@ static InjectionReturnStatus translate_hdr_field(char* hdr, uint32_t len, uint8_
         return ERR_PAGE_TRANSLATION;
 }
 
-static InjectionReturnStatus get_http2_hdr(char* http_page, uint32_t len,
+static InjectionReturnStatus get_http2_hdr(const uint8_t* http_page, uint32_t len,
     uint8_t* http2_hdr, uint32_t& hdr_len, uint32_t& body_offset)
 {
     InjectionReturnStatus status = ERR_PAGE_TRANSLATION;
     body_offset = 0;
 
     uint32_t hdr_free_space = max_hdr_size;
-    char* page_cur = http_page;
+    const uint8_t* page_cur = http_page;
     uint8_t* hdr_cur = http2_hdr;
     while ((page_cur - http_page) < len)
     {
-        char* cr_newline = strstr(page_cur, "\r\n");
-        if (cr_newline != nullptr)
+        const uint8_t* newline = (const uint8_t*)memchr(page_cur, '\n', len - (page_cur - http_page));
+        if (newline != nullptr)
         {
-            if (cr_newline == page_cur)
+            // FIXIT-E only \r\n should be supported
+            if (newline == page_cur || (newline == page_cur + 1 && *page_cur == '\r'))
             {
                 // reached end of headers
-                if ((page_cur - http_page + 2) < len)
-                    body_offset = page_cur - http_page + 2;
+                if ((newline + 1 - http_page) < len)
+                    body_offset = newline + 1 - http_page;
                 break;
             }
-            status = translate_hdr_field(page_cur, cr_newline-page_cur, hdr_cur, hdr_free_space);
+            if (*(newline - 1) == '\r')
+                status = translate_hdr_field(page_cur, newline - page_cur - 1, hdr_cur,
+                    hdr_free_space);
+            else
+                status = translate_hdr_field(page_cur, newline - page_cur, hdr_cur,
+                    hdr_free_space);
             if (status != INJECTION_SUCCESS)
                 break;
-            page_cur = cr_newline + 2;
+            page_cur = newline + 1;
         }
         else
             break;
     }
 
-    if (status == ERR_PAGE_TRANSLATION || body_offset == 0)
+    if (status != INJECTION_SUCCESS)
+        return status;
+
+    if (body_offset == 0)
         return ERR_PAGE_TRANSLATION;
 
     hdr_len = hdr_cur - http2_hdr;
@@ -211,35 +231,27 @@ InjectionReturnStatus PayloadInjectorModule::get_http2_payload(InjectionControl
     if (control.http_page == nullptr || control.http_page_len == 0)
         return ERR_PAGE_TRANSLATION;
 
-    // create a string version to run with strstr
-    char* page_string = (char*)snort_alloc(control.http_page_len + 1);
-    memcpy(page_string, control.http_page, control.http_page_len);
-    page_string[control.http_page_len] = '\0';
-
     uint8_t http2_hdr[max_hdr_size];
     uint32_t hdr_len, body_offset;
-    InjectionReturnStatus status = get_http2_hdr(page_string, control.http_page_len, http2_hdr,
-        hdr_len, body_offset);
+    InjectionReturnStatus status = get_http2_hdr(control.http_page, control.http_page_len,
+        http2_hdr, hdr_len, body_offset);
 
-    snort_free(page_string);
-
-    if (status == ERR_PAGE_TRANSLATION)
+    if (status != INJECTION_SUCCESS)
         return status;
 
     const uint32_t body_len = control.http_page_len - body_offset;
     // FIXIT-E support larger body size
     if (body_len > 1<<14)
-        return ERR_PAGE_TRANSLATION;
+        return ERR_HTTP2_BODY_SIZE;
 
-    payload_len = 2*Http2Enums::FRAME_HEADER_LENGTH + hdr_len + body_len;
+    payload_len = 2*FRAME_HEADER_LENGTH + hdr_len + body_len;
     http2_payload = (uint8_t*)snort_alloc(payload_len);
 
     uint8_t* http2_payload_cur = http2_payload;
-    // FIXIT-E update flags
-    write_frame_hdr(http2_payload_cur, hdr_len, Http2Enums::FT_HEADERS, 0, control.stream_id);
+    write_frame_hdr(http2_payload_cur, hdr_len, FT_HEADERS, END_HEADERS, control.stream_id);
     memcpy(http2_payload_cur, http2_hdr, hdr_len);
     http2_payload_cur += hdr_len;
-    write_frame_hdr(http2_payload_cur, body_len, Http2Enums::FT_DATA, 0, control.stream_id);
+    write_frame_hdr(http2_payload_cur, body_len, FT_DATA, END_STREAM, control.stream_id);
     memcpy(http2_payload_cur, control.http_page + body_offset, body_len);
 
     return INJECTION_SUCCESS;
index d13946e8da6df99cb0606db9c0771125a19d47a5..30c4fb8ffa864220c520b619c8590c9dbfebddf8 100644 (file)
 #include "flow/flow.h"
 #include "packet_io/active.h"
 #include "protocols/packet.h"
+#include "utils/util.h"
+#include "service_inspectors/http_inspect/http_enum.h"
+#include "service_inspectors/http2_inspect/http2_flow_data.h"
 
 #include <CppUTest/CommandLineTestRunner.h>
 #include <CppUTest/TestHarness.h>
 
 using namespace snort;
+using namespace HttpCommon;
 
 //--------------------------------------------------------------------------
 // mocks
@@ -44,38 +48,78 @@ uint32_t Active::send_data(snort::Packet*, EncodeFlags, unsigned char const*, un
 {
     return 1;
 }
+
 void Active::block_session(snort::Packet*, bool) { }
 void DetectionEngine::disable_all(snort::Packet*) { }
 Flow::Flow() { memset(this, 0, sizeof(*this)); }
 Flow::~Flow() { }
 Packet::Packet(bool) { packet_flags = 0; flow = nullptr; }
 Packet::~Packet() { }
+int DetectionEngine::queue_event(unsigned int, unsigned int, snort::Actions::Type) { return 0; }
+FlowData::~FlowData() { }
+FlowData::FlowData(unsigned int, snort::Inspector*) { }
 
+// Inspector mocks, used by MockInspector class
 InspectApi mock_api;
 Inspector::Inspector()
 {
     set_api(&mock_api);
 }
+
 Inspector::~Inspector() = default;
 bool Inspector::likes(Packet*) { return true; }
 bool Inspector::get_buf(const char*, Packet*, InspectionBuffer&) { return true; }
 class StreamSplitter* Inspector::get_splitter(bool) { return nullptr; }
-
 }
 
 void show_stats(PegCount*, const PegInfo*, unsigned, const char*) { }
 void show_stats(PegCount*, const PegInfo*, const IndexVec&, const char*, FILE*) { }
 
+// MockInspector class
+
 class MockInspector : public snort::Inspector
 {
 public:
 
-  MockInspector() {}
-  ~MockInspector() override {}
-  void eval(snort::Packet*) override {}
-  bool configure(snort::SnortConfig*) override { return true;}
+    MockInspector() { }
+    ~MockInspector() override { }
+    void eval(snort::Packet*) override { }
+    bool configure(snort::SnortConfig*) override { return true; }
 };
 
+// Mocks for PayloadInjectorModule::get_http2_payload
+
+static InjectionReturnStatus translation_status = INJECTION_SUCCESS;
+InjectionReturnStatus PayloadInjectorModule::get_http2_payload(InjectionControl,
+    uint8_t*& http2_payload, uint32_t& payload_len)
+{
+    if (translation_status == INJECTION_SUCCESS)
+    {
+        http2_payload = (uint8_t*)snort_alloc(1);
+        payload_len = 1;
+    }
+
+    return translation_status;
+}
+
+// Mocks for snort::Flow::get_flow_data
+
+unsigned Http2FlowData::inspector_id = 0;
+Http2Stream::~Http2Stream() { }
+HpackDynamicTable::~HpackDynamicTable() { }
+Http2FlowData::Http2FlowData(snort::Flow*) :
+    FlowData(inspector_id),
+    flow(nullptr),
+    hi(nullptr),
+    hpack_decoder
+    {
+        Http2HpackDecoder(this, SRC_CLIENT, events[SRC_CLIENT], infractions[SRC_CLIENT]),
+       Http2HpackDecoder(this, SRC_SERVER, events[SRC_SERVER], infractions[SRC_SERVER])
+    }
+    { }
+Http2FlowData::~Http2FlowData() { }
+Http2FlowData http2_flow_data(nullptr);
+FlowData* snort::Flow::get_flow_data(unsigned int) const { return &http2_flow_data; }
 
 TEST_GROUP(payload_injector_test)
 {
@@ -88,9 +132,14 @@ TEST_GROUP(payload_injector_test)
     {
         counts->http_injects = 0;
         counts->http2_injects = 0;
+        counts->http2_translate_err = 0;
+        counts->http2_mid_frame = 0;
         control.http_page = (const uint8_t*)"test";
         control.http_page_len = 4;
         flow.set_state(Flow::FlowState::INSPECT);
+        translation_status = INJECTION_SUCCESS;
+        http2_flow_data.set_continuation_expected(SRC_SERVER, false);
+        http2_flow_data.set_reading_frame(SRC_SERVER, false);
     }
 };
 
@@ -103,6 +152,8 @@ TEST(payload_injector_test, not_configured_stream_not_established)
     CHECK(counts->http_injects == 0);
     CHECK(status == ERR_INJECTOR_NOT_CONFIGURED);
     CHECK(flow.flow_state == Flow::FlowState::BLOCK);
+    const char* err_string = mod.get_err_string(status);
+    CHECK(strcmp(err_string, "Payload injector is not configured") == 0);
 }
 
 TEST(payload_injector_test, not_configured_stream_established)
@@ -125,6 +176,8 @@ TEST(payload_injector_test, configured_stream_not_established)
     InjectionReturnStatus status = mod.inject_http_payload(&p, control);
     CHECK(counts->http_injects == 0);
     CHECK(status == ERR_STREAM_NOT_ESTABLISHED);
+    const char* err_string = mod.get_err_string(status);
+    CHECK(strcmp(err_string, "TCP stream not established") == 0);
     CHECK(flow.flow_state == Flow::FlowState::BLOCK);
 }
 
@@ -155,6 +208,8 @@ TEST(payload_injector_test, http2_stream0)
     CHECK(counts->http2_injects == 0);
     CHECK(status == ERR_HTTP2_STREAM_ID_0);
     CHECK(flow.flow_state == Flow::FlowState::BLOCK);
+    const char* err_string = mod.get_err_string(status);
+    CHECK(strcmp(err_string, "HTTP/2 - injection to stream 0") == 0);
     delete flow.gadget;
 }
 
@@ -183,6 +238,8 @@ TEST(payload_injector_test, unidentified_gadget_is_null)
     InjectionReturnStatus status = mod.inject_http_payload(&p, control);
     CHECK(status == ERR_UNIDENTIFIED_PROTOCOL);
     CHECK(flow.flow_state == Flow::FlowState::BLOCK);
+    const char* err_string = mod.get_err_string(status);
+    CHECK(strcmp(err_string, "Unidentified protocol") == 0);
 }
 
 TEST(payload_injector_test, unidentified_gadget_name)
@@ -199,6 +256,124 @@ TEST(payload_injector_test, unidentified_gadget_name)
     delete flow.gadget;
 }
 
+TEST(payload_injector_test, http2_mid_frame)
+{
+    mod.set_configured(true);
+    Packet p(false);
+    p.packet_flags = PKT_STREAM_EST;
+    mock_api.base.name = "http2_inspect";
+    flow.gadget = new MockInspector();
+    p.flow = &flow;
+    control.stream_id = 1;
+    http2_flow_data.set_reading_frame(SRC_SERVER, true);
+    InjectionReturnStatus status = mod.inject_http_payload(&p, control);
+    CHECK(counts->http2_mid_frame == 1);
+    CHECK(status == ERR_HTTP2_MID_FRAME);
+    CHECK(flow.flow_state == Flow::FlowState::BLOCK);
+    const char* err_string = mod.get_err_string(status);
+    CHECK(strcmp(err_string, "HTTP/2 - attempt to inject mid frame. Currently not supported.")
+        == 0);
+    delete flow.gadget;
+}
+
+TEST(payload_injector_test, http2_continuation_expected)
+{
+    mod.set_configured(true);
+    Packet p(false);
+    p.packet_flags = PKT_STREAM_EST;
+    mock_api.base.name = "http2_inspect";
+    flow.gadget = new MockInspector();
+    p.flow = &flow;
+    control.stream_id = 1;
+    http2_flow_data.set_continuation_expected(SRC_SERVER, true);
+    InjectionReturnStatus status = mod.inject_http_payload(&p, control);
+    CHECK(counts->http2_mid_frame == 1);
+    CHECK(status == ERR_HTTP2_MID_FRAME);
+    CHECK(flow.flow_state == Flow::FlowState::BLOCK);
+    delete flow.gadget;
+}
+
+TEST_GROUP(payload_injector_translate_err_test)
+{
+    PayloadInjectorModule mod;
+    InjectionControl control;
+    PayloadInjectorCounts* counts = (PayloadInjectorCounts*)mod.get_counts();
+    Flow flow;
+    InjectionReturnStatus status = INJECTION_SUCCESS;
+
+    void setup() override
+    {
+        counts->http_injects = 0;
+        counts->http2_injects = 0;
+        counts->http2_translate_err = 0;
+        counts->http2_mid_frame = 0;
+        control.http_page = (const uint8_t*)"test";
+        control.http_page_len = 4;
+        flow.set_state(Flow::FlowState::INSPECT);
+        http2_flow_data.set_continuation_expected(SRC_SERVER, false);
+        http2_flow_data.set_reading_frame(SRC_SERVER, false);
+        mod.set_configured(true);
+        mock_api.base.name = "http2_inspect";
+        flow.gadget = new MockInspector();
+        control.stream_id = 1;
+    }
+
+    void teardown() override
+    {
+        CHECK(counts->http2_translate_err == 1);
+        CHECK(status == translation_status);
+        CHECK(flow.flow_state == Flow::FlowState::BLOCK);
+        delete flow.gadget;
+    }
+};
+
+TEST(payload_injector_translate_err_test, http2_page_translation_err)
+{
+    Packet p(false);
+    p.packet_flags = PKT_STREAM_EST;
+    p.flow = &flow;
+    translation_status = ERR_PAGE_TRANSLATION;
+    status = mod.inject_http_payload(&p, control);
+    const char* err_string = mod.get_err_string(status);
+    CHECK(strcmp(err_string, "Error in translating HTTP block page to HTTP/2. "
+        "Unsupported or bad format.") == 0);
+}
+
+TEST(payload_injector_translate_err_test, http2_field_len_err)
+{
+    Packet p(false);
+    p.packet_flags = PKT_STREAM_EST;
+    p.flow = &flow;
+    translation_status = ERR_HTTP2_HDR_FIELD_VAL_LEN;
+    status = mod.inject_http_payload(&p, control);
+    const char* err_string = mod.get_err_string(status);
+    CHECK(strcmp(err_string, "HTTP/2 header field value length > 127. "
+        " Currently not supported.") == 0);
+}
+
+TEST(payload_injector_translate_err_test, http2_hdrs_size)
+{
+    Packet p(false);
+    p.packet_flags = PKT_STREAM_EST;
+    p.flow = &flow;
+    translation_status = ERR_TRANSLATED_HDRS_SIZE;
+    status = mod.inject_http_payload(&p, control);
+    const char* err_string = mod.get_err_string(status);
+    CHECK(strcmp(err_string,
+        "HTTP/2 translated header size is bigger than expected. Update max size.") == 0);
+}
+
+TEST(payload_injector_translate_err_test, http2_body_size)
+{
+    Packet p(false);
+    p.packet_flags = PKT_STREAM_EST;
+    p.flow = &flow;
+    translation_status = ERR_HTTP2_BODY_SIZE;
+    status = mod.inject_http_payload(&p, control);
+    const char* err_string = mod.get_err_string(status);
+    CHECK(strcmp(err_string, "HTTP/2 body is > 16k. Currently not supported.") == 0);
+}
+
 int main(int argc, char** argv)
 {
     return CommandLineTestRunner::RunAllTests(argc, argv);
index f60ec1f454fbf989625e3263df31ffb8ab05973f..27c964accaa6962b7480c52292710d75c0b3a4fc 100644 (file)
@@ -49,30 +49,22 @@ TEST(payload_injector_translate_test, basic_hdr_translation)
     status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
     CHECK(status == INJECTION_SUCCESS);
 
-    uint8_t out[] = { 0x0, 0x0, 0x40, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x7, 0x3a, 0x73, 0x74,
-                      0x61, 0x74, 0x75, 0x73, 0x3, 0x34, 0x30, 0x33, 0x0, 0xa, 0x63, 0x6f, 0x6e,
-                      0x6e, 0x65,
-                      0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0xf, 0xd,
-                      0x3, 0x35,
-                      0x30, 0x34, 0xf, 0x10, 0x18, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d,
-                      0x6c,
-                      0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x55, 0x54, 0x46,
-                      0x2d,
-                      0x38, 0x0, 0x0, 0x62, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3c, 0x21, 0x44, 0x4f,
-                      0x43, 0x54,
-                      0x59, 0x50, 0x45, 0x20, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0xa, 0x3c, 0x68, 0x74,
-                      0x6d,
-                      0x6c, 0x3e, 0x3c, 0x68, 0x65, 0x61, 0x64, 0x3e, 0xa, 0x3c, 0x6d, 0x65, 0x74,
-                      0x61,
-                      0x68, 0x74, 0x74, 0x70, 0x2d, 0x65, 0x71, 0x75, 0x69, 0x76, 0x3d, 0x22, 0x63,
-                      0x6f,
-                      0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x22, 0x20, 0x63,
-                      0x6f,
-                      0x6e, 0x74, 0x65, 0x6e, 0x74, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x68,
-                      0x74,
-                      0x6d, 0x6c, 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x55,
-                      0x54,
-                      0x46, 0x2d, 0x38, 0x22, 0x20, 0x2f, 0x3e, 0xa };
+    uint8_t out[] =
+    {
+        0x0, 0x0, 0x40, 0x1, 0x4, 0x0, 0x0, 0x0, 0x1, 0x0, 0x7, 0x3a, 0x73, 0x74,
+        0x61, 0x74, 0x75, 0x73, 0x3, 0x34, 0x30, 0x33, 0x0, 0xa, 0x63, 0x6f, 0x6e,
+        0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0xf, 0xd,
+        0x3, 0x35, 0x30, 0x34, 0xf, 0x10, 0x18, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d,
+        0x6c, 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x55, 0x54, 0x46,
+        0x2d, 0x38, 0x0, 0x0, 0x62, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x3c, 0x21, 0x44, 0x4f,
+        0x43, 0x54, 0x59, 0x50, 0x45, 0x20, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0xa, 0x3c, 0x68, 0x74,
+        0x6d, 0x6c, 0x3e, 0x3c, 0x68, 0x65, 0x61, 0x64, 0x3e, 0xa, 0x3c, 0x6d, 0x65, 0x74,
+        0x61, 0x68, 0x74, 0x74, 0x70, 0x2d, 0x65, 0x71, 0x75, 0x69, 0x76, 0x3d, 0x22, 0x63,
+        0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x22, 0x20, 0x63,
+        0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x68,
+        0x74, 0x6d, 0x6c, 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x55,
+        0x54, 0x46, 0x2d, 0x38, 0x22, 0x20, 0x2f, 0x3e, 0xa
+    };
     CHECK(payload_len == sizeof(out));
     CHECK(memcmp(http2_payload, out, payload_len) == 0);
 
@@ -92,16 +84,75 @@ TEST(payload_injector_translate_test, basic_hdr_translation2)
 
     CHECK(status == INJECTION_SUCCESS);
 
-    uint8_t out[] = { 0x0, 0x0, 0x36, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x7, 0x3a, 0x73, 0x74,
-                      0x61, 0x74, 0x75, 0x73, 0x3, 0x33, 0x30, 0x37, 0xf, 0x1f, 0x8, 0x68, 0x74,
-                      0x74, 0x70,
-                      0x73, 0x3a, 0x2f, 0x2f, 0xf, 0x28, 0x1b, 0x30, 0x34, 0x66, 0x32, 0x3b, 0x20,
-                      0x4d,
-                      0x61, 0x78, 0x2d, 0x41, 0x67, 0x65, 0x3a, 0x20, 0x36, 0x30, 0x30, 0x3b, 0x20,
-                      0x70,
-                      0x61, 0x74, 0x68, 0x3d, 0x2f, 0x3b, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0,
-                      0x1, 0x42,
-                      0x6f, 0x64, 0x79, 0xa };
+    uint8_t out[] =
+    {
+        0x0, 0x0, 0x36, 0x1, 0x4, 0x0, 0x0, 0x0, 0x1, 0x0, 0x7, 0x3a, 0x73, 0x74,
+        0x61, 0x74, 0x75, 0x73, 0x3, 0x33, 0x30, 0x37, 0xf, 0x1f, 0x8, 0x68, 0x74,
+        0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0xf, 0x28, 0x1b, 0x30, 0x34, 0x66, 0x32, 0x3b, 0x20,
+        0x4d, 0x61, 0x78, 0x2d, 0x41, 0x67, 0x65, 0x3a, 0x20, 0x36, 0x30, 0x30, 0x3b, 0x20,
+        0x70, 0x61, 0x74, 0x68, 0x3d, 0x2f, 0x3b, 0x0, 0x0, 0x5, 0x0, 0x1, 0x0, 0x0, 0x0,
+        0x1, 0x42, 0x6f, 0x64, 0x79, 0xa
+    };
+    CHECK(payload_len == sizeof(out));
+    CHECK(memcmp(http2_payload, out, payload_len) == 0);
+
+    snort_free(http2_payload);
+}
+
+// Same like 2 , using \n instead of \r\n
+TEST(payload_injector_translate_test, basic_hdr_translation3)
+{
+    char http_page[] =
+        "HTTP/1.1 307 Proxy Redirect\nLocation: https://\nSet-Cookie: 04f2; Max-Age: 600; path=/;\n\nBody\n";
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = (uint8_t*)http_page;
+    control.http_page_len = strlen(http_page);
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == INJECTION_SUCCESS);
+
+    uint8_t out[] =
+    {
+        0x0, 0x0, 0x36, 0x1, 0x4, 0x0, 0x0, 0x0, 0x1, 0x0, 0x7, 0x3a, 0x73, 0x74,
+        0x61, 0x74, 0x75, 0x73, 0x3, 0x33, 0x30, 0x37, 0xf, 0x1f, 0x8, 0x68, 0x74,
+        0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0xf, 0x28, 0x1b, 0x30, 0x34, 0x66, 0x32, 0x3b, 0x20,
+        0x4d, 0x61, 0x78, 0x2d, 0x41, 0x67, 0x65, 0x3a, 0x20, 0x36, 0x30, 0x30, 0x3b, 0x20,
+        0x70, 0x61, 0x74, 0x68, 0x3d, 0x2f, 0x3b, 0x0, 0x0, 0x5, 0x0, 0x1, 0x0, 0x0, 0x0,
+        0x1, 0x42, 0x6f, 0x64, 0x79, 0xa
+    };
+    CHECK(payload_len == sizeof(out));
+    CHECK(memcmp(http2_payload, out, payload_len) == 0);
+
+    snort_free(http2_payload);
+}
+
+// Mix of \r\n and \n, body length == 1
+TEST(payload_injector_translate_test, mix_n_and_rn)
+{
+    char http_page[] =
+        "HTTP/1.1 200 OK\nConnection: close\nSet-Cookie: 04f2; Max-Age: 600; path=/;\r\nContent-Length: 956\r\nContent-Type: text/html; charset=UTF-8\n\nb";
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = (uint8_t*)http_page;
+    control.http_page_len = strlen(http_page);
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == INJECTION_SUCCESS);
+
+    uint8_t out[] =
+    {
+        0x0, 0x0, 0x52, 0x1, 0x4, 0x0, 0x0, 0x0, 0x1, 0x88, 0x0, 0xa, 0x63, 0x6f,
+        0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5, 0x63, 0x6c, 0x6f, 0x73,
+        0x65, 0xf, 0x28, 0x1b, 0x30, 0x34, 0x66, 0x32, 0x3b, 0x20, 0x4d, 0x61, 0x78, 0x2d, 0x41,
+        0x67, 0x65, 0x3a, 0x20, 0x36, 0x30, 0x30, 0x3b, 0x20, 0x70, 0x61, 0x74, 0x68, 0x3d,
+        0x2f, 0x3b, 0xf, 0xd, 0x3, 0x39, 0x35, 0x36, 0xf, 0x10, 0x18, 0x74, 0x65, 0x78,
+        0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74,
+        0x3d, 0x55, 0x54, 0x46, 0x2d, 0x38, 0x0, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1,
+        0x62
+    };
     CHECK(payload_len == sizeof(out));
     CHECK(memcmp(http2_payload, out, payload_len) == 0);
 
@@ -177,10 +228,10 @@ TEST(payload_injector_translate_test, extra_space_before_colon)
     CHECK(status == ERR_PAGE_TRANSLATION);
 }
 
-TEST(payload_injector_translate_test, unsuporrted_status)
+TEST(payload_injector_translate_test, unsupported_status)
 {
     char http_page[] =
-        "HTTP/1.1 200 OK\r\nLocation: https://\r\nSet-Cookie: 04f2; Max-Age: 600; path=/;\r\n\r\nBody\n";
+        "HTTP/1.1 201 Created\r\nLocation: https://\r\nSet-Cookie: 04f2; Max-Age: 600; path=/;\r\n\r\nBody\n";
 
     InjectionControl control;
     control.stream_id = 1;
@@ -246,7 +297,7 @@ TEST(payload_injector_translate_test, val_len_too_big)
     control.http_page_len = size;
     status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
 
-    CHECK(status == ERR_PAGE_TRANSLATION);
+    CHECK(status == ERR_HTTP2_HDR_FIELD_VAL_LEN);
 }
 
 // Header value has maximum supported length 127
@@ -295,9 +346,9 @@ TEST(payload_injector_translate_test, http2_hdr_is_max)
 
     CHECK(status == INJECTION_SUCCESS);
     CHECK(payload_len == 2019);
-    uint8_t hdr[] = { 0x0, 0x7, 0xd0, 0x1, 0x0, 0x0, 0x0, 0xf0, 0x0 };
+    uint8_t hdr[] = { 0x0, 0x7, 0xd0, 0x1, 0x4, 0x0, 0x0, 0xf0, 0x0 };
     CHECK(memcmp(http2_payload, hdr, sizeof(hdr))==0);
-    uint8_t body[] = { 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0xf0, 0x0, 'b' };
+    uint8_t body[] = { 0x0, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0xf0, 0x0, 'b' };
     CHECK(memcmp(http2_payload + 2009, body, sizeof(body))==0);
     snort_free(http2_payload);
 }
@@ -325,7 +376,7 @@ TEST(payload_injector_translate_test, http2_hdr_too_big)
     control.http_page_len = size;
     status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
 
-    CHECK(status == ERR_PAGE_TRANSLATION);
+    CHECK(status == ERR_TRANSLATED_HDRS_SIZE);
 }
 
 // Translated header > 2000. Goes through write_translation code path.
@@ -347,7 +398,7 @@ TEST(payload_injector_translate_test, http2_hdr_too_big2)
     control.http_page = http_page;
     control.http_page_len = size;
     status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
-    CHECK(status == ERR_PAGE_TRANSLATION);
+    CHECK(status == ERR_TRANSLATED_HDRS_SIZE);
 }
 
 TEST(payload_injector_translate_test, payload_body_larger_than_max)
@@ -362,7 +413,7 @@ TEST(payload_injector_translate_test, payload_body_larger_than_max)
     control.http_page = http_page;
     control.http_page_len = size;
     status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
-    CHECK(status == ERR_PAGE_TRANSLATION);
+    CHECK(status == ERR_HTTP2_BODY_SIZE);
 }
 
 TEST(payload_injector_translate_test, http_page_is_nullptr)
@@ -376,7 +427,7 @@ TEST(payload_injector_translate_test, http_page_is_nullptr)
 
 TEST(payload_injector_translate_test, http_page_is_0_length)
 {
-    uint8_t http_page[] = {1};
+    uint8_t http_page[] = { 1 };
 
     InjectionControl control;
     control.http_page = http_page;
index e764f9e016dc05395af34b2c84b5b4b805a03a5f..fea2052167a95022312f2785f1175a67536d83bb 100644 (file)
@@ -110,6 +110,9 @@ bool Http2DataCutter::http2_scan(const uint8_t* data, uint32_t length,
         }
     }
 
+    if (data_state == FULL_FRAME)
+        session_data->reading_frame[source_id] = false;        
+
     frame_bytes_seen += (cur_pos - leftover_bytes - data_offset - leftover_padding);
     *flush_offset = data_offset = cur_pos;
     session_data->scan_remaining_frame_octets[source_id] = frame_length - frame_bytes_seen;
index 93f20b0262fdafc30355a949ed0b601299123e81..4e10dfb0289a5002c3a0f37339bfaacb0f7a5b17 100644 (file)
@@ -96,6 +96,16 @@ public:
     Http2ConnectionSettings* get_connection_settings(const HttpCommon::SourceId source_id)
     { return &connection_settings[source_id]; }
 
+    bool is_mid_frame(const HttpCommon::SourceId source_id = HttpCommon::SRC_SERVER)
+    { return (continuation_expected[source_id] || reading_frame[source_id]); }
+
+#ifdef UNIT_TEST
+    void set_reading_frame(HttpCommon::SourceId source_id, bool val)
+    { reading_frame[source_id] = val;}
+    void set_continuation_expected(HttpCommon::SourceId source_id, bool val)
+    { continuation_expected[source_id] = val;}
+#endif
+
 protected:
     snort::Flow* flow;
     HttpInspect* const hi;
@@ -156,6 +166,10 @@ protected:
     uint8_t padding_octets_in_frame[2] = { 0, 0 };
     bool get_padding_len[2] = { false, false };
 
+    // used to signal frame wasn't fully read yet,
+    // currently used by payload injector
+    bool reading_frame[2] = { false, false };
+
 #ifdef REG_TEST
     static uint64_t instance_count;
     uint64_t seq_num;
index 1966c216efc51b8047ec934bebc05e9939bebac3..0cf0422d75fec7a280645d75a8a2530579ac5c61 100644 (file)
@@ -142,6 +142,7 @@ StreamSplitter::Status Http2StreamSplitter::non_data_scan(Http2FlowData* session
     }
 
     // Have the full frame
+    session_data->reading_frame[source_id] = false;
     StreamSplitter::Status status = StreamSplitter::FLUSH;
     switch (type)
     {
@@ -215,6 +216,7 @@ bool Http2StreamSplitter::read_frame_hdr(Http2FlowData* session_data, const uint
         {
             // Scanning a new frame
             session_data->num_frame_headers[source_id] += 1;
+            session_data->reading_frame[source_id] = true;
         }
 
         // The first nine bytes are the frame header. But all nine might not all be