]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #1830 in SNORT/snort3 from ~KATHARVE/snort3:h2i_response_start_lin...
authorMike Stepanek (mstepane) <mstepane@cisco.com>
Mon, 25 Nov 2019 14:28:51 +0000 (14:28 +0000)
committerMike Stepanek (mstepane) <mstepane@cisco.com>
Mon, 25 Nov 2019 14:28:51 +0000 (14:28 +0000)
Squashed commit of the following:

commit 0a5f41439490f3dad02b91ae6358c448af539553
Author: Katura Harvey <katharve@cisco.com>
Date:   Wed Oct 30 17:09:13 2019 -0400

    http2_inspect: generate status lines for responses and be more lenient on RFC violations

13 files changed:
src/service_inspectors/http2_inspect/CMakeLists.txt
src/service_inspectors/http2_inspect/dev_notes.txt
src/service_inspectors/http2_inspect/http2_enum.h
src/service_inspectors/http2_inspect/http2_flow_data.h
src/service_inspectors/http2_inspect/http2_headers_frame.cc
src/service_inspectors/http2_inspect/http2_hpack.cc
src/service_inspectors/http2_inspect/http2_request_line.cc
src/service_inspectors/http2_inspect/http2_request_line.h
src/service_inspectors/http2_inspect/http2_start_line.cc
src/service_inspectors/http2_inspect/http2_start_line.h
src/service_inspectors/http2_inspect/http2_status_line.cc [new file with mode: 0644]
src/service_inspectors/http2_inspect/http2_status_line.h [new file with mode: 0644]
src/service_inspectors/http2_inspect/http2_tables.cc

index 53ff980a883a50480278d202e4e032a07ed8b308..9f8357e387bf18d2dc2c5f74127226a583682da1 100644 (file)
@@ -28,6 +28,8 @@ set (FILE_LIST
     http2_request_line.h
     http2_start_line.cc
     http2_start_line.h
+    http2_status_line.cc
+    http2_status_line.h
     http2_stream_splitter.cc
     http2_stream_splitter_impl.cc
     http2_stream_splitter.h
index 60b474047dc77ad63dcf3c7701a4d90e1af3ce44..f3e76a10de1be4ea1520e0a34681581bddd62dfc 100644 (file)
@@ -35,8 +35,10 @@ indexed, except the header line is not added to the dynamic table.
 HTTP/2 uses pseudo-headers to convey the information included in an HTTP/1.1 start-line.
 Pseudo-headers are not valid HTTP/1.1 headers, so must be translated into a start-line before the
 decoded headers can be passed to NHI. The two Http2StartLine subclasses, Http2RequestLine and
-Http2ResponseLine (not yet implemented) perform this translation and generate the start-line, which
-is stored in a new buffer inside the Http2StartLine object. The start-line buffer must be passed to
-NHI before the http2_decoded_header buffer, which contains the regular HTTP/1.1 headers.
+Http2ResponseLine perform this translation and generate the start-line, which is stored in a new
+buffer inside the Http2StartLine object. The start-line buffer must be passed to NHI before the
+http2_decoded_header buffer, which contains the regular HTTP/1.1 headers. Note that HTTP/2 does not
+use status reason phrases, so the status line passed to NHI will not include one. It will include
+only the HTTP version and the status code. 
 
 H2I supports the NHI test tool. See ../http_inspect/dev_notes.txt for usage instructions.
index 76a86ce3ebceaaf15cf272fbeb8d913d44544341..9a0306fdf98deab62e481e9ba494af4fdda4e029 100644 (file)
@@ -55,6 +55,9 @@ enum EventSid
     EVENT_UNEXPECTED_CONTINUATION = 5,
     EVENT_MISFORMATTED_HTTP2 = 6,
     EVENT_PREFACE_MATCH_FAILURE = 7,
+    EVENT_REQUEST_WITHOUT_REQUIRED_FIELD = 9,
+    EVENT_RESPONSE_WITHOUT_STATUS = 10,
+    EVENT_INVALID_HEADER = 11,
     EVENT__MAX_VALUE
 };
 
@@ -78,6 +81,7 @@ enum Infraction
     INF_INVALID_PSEUDO_HEADER = 13,
     INF_PSEUDO_HEADER_AFTER_REGULAR_HEADER = 14,
     INF_PSEUDO_HEADER_URI_FORM_MISMATCH = 15,
+    INF_RESPONSE_WITHOUT_STATUS = 16,
     INF__MAX_VALUE
 };
 
@@ -92,7 +96,8 @@ enum HeaderFrameFlags
 
 enum PseudoHeaders
 {
-    HEADER_NONE = 0,
+    HEADER__INVALID = -1,
+    HEADER__NONE = 0,
     AUTHORITY = 1,
     METHOD = 3,
     PATH = 5,
index 6d324c47148d218791ee300b6cd55053cbc32fff..8a0aa94eeb3faae40cab6766940dd8d5b2c7b83c 100644 (file)
@@ -48,20 +48,21 @@ public:
     static unsigned inspector_id;
     static void init() { inspector_id = snort::FlowData::create_flow_data_id(); }
 
-    friend class Http2Inspect;
-    friend class Http2StreamSplitter;
-    friend class Http2Hpack;
-    friend class Http2StartLine;
-    friend class Http2RequestLine;
     friend class Http2Frame;
     friend class Http2HeadersFrame;
+    friend class Http2Hpack;
+    friend class Http2Inspect;
+    friend class Http2RequestLine;
+    friend class Http2StartLine;
+    friend class Http2StatusLine;
+    friend class Http2StreamSplitter;
+    friend void implement_eval(Http2FlowData* session_data, HttpCommon::SourceId source_id);
+    friend bool implement_get_buf(unsigned id, Http2FlowData*, HttpCommon::SourceId,
+        snort::InspectionBuffer&);
     friend const snort::StreamBuffer implement_reassemble(Http2FlowData*, unsigned, unsigned,
         const uint8_t*, unsigned, uint32_t, HttpCommon::SourceId);
     friend snort::StreamSplitter::Status implement_scan(Http2FlowData*, const uint8_t*, uint32_t,
         uint32_t*, HttpCommon::SourceId);
-    friend bool implement_get_buf(unsigned id, Http2FlowData*, HttpCommon::SourceId,
-        snort::InspectionBuffer&);
-    friend void implement_eval(Http2FlowData* session_data, HttpCommon::SourceId source_id);
 
     size_t size_of() override
     { return sizeof(*this); }
index 9b22f4f12ef23c4bc730e58d26c1241fa8202ef3..be115eee6631579a109a9ce53a5b106f83b8e555 100644 (file)
@@ -26,7 +26,6 @@
 #include "http2_enum.h"
 #include "http2_flow_data.h"
 #include "http2_hpack.h"
-#include "http2_request_line.h"
 #include "http2_start_line.h"
 
 using namespace HttpCommon;
@@ -50,10 +49,8 @@ Http2HeadersFrame::Http2HeadersFrame(const uint8_t* header_buffer, const int32_t
     decoded_headers = new uint8_t[MAX_OCTETS];
     decoded_headers_size = 0;
     
-    // FIXIT-H Implement response start line
-    if (source_id == SRC_CLIENT)
-        start_line_generator = new Http2RequestLine(session_data->events[source_id],
-            session_data->infractions[source_id]);
+    start_line_generator = Http2StartLine::new_start_line_generator(source_id,
+        session_data->events[source_id], session_data->infractions[source_id]);
 
     // Decode headers
     if (!hpack_decoder->decode_headers((data.start() + hpack_headers_offset), data.length() -
@@ -63,9 +60,7 @@ Http2HeadersFrame::Http2HeadersFrame(const uint8_t* header_buffer, const int32_t
         session_data->frame_type[source_id] = FT__ABORT;
         error_during_decode = true;
     }
-    //FIXIT-H remove condition once status lines implemented
-    if (source_id == SRC_CLIENT)
-        start_line = hpack_decoder->get_start_line();
+    start_line = hpack_decoder->get_start_line();
     http2_decoded_header = hpack_decoder->get_decoded_headers(decoded_headers);
 }
 
index 004b37a73b78c1dcd0e8737a30e73abd1eb7cd53..bd1ec88586e1823f3128091fb55a8301b12a28b0 100644 (file)
@@ -27,7 +27,7 @@
 #include "service_inspectors/http_inspect/http_test_manager.h"
 
 #include "http2_enum.h"
-#include "http2_request_line.h"
+#include "http2_start_line.h"
 
 using namespace HttpCommon;
 using namespace Http2Enums;
@@ -119,17 +119,15 @@ bool Http2HpackDecoder::decode_static_table_index(const uint64_t index, const bo
     assert(index > 0);
 
     // If this is a pseudo-header, pass it to the start line
-    // Remove second condition after response start line implemented
-    if (index < PSEUDO_HEADER_MAX_INDEX and start_line)
+    if (index < PSEUDO_HEADER_MAX_INDEX)
     {
-        if (start_line and !start_line->process_pseudo_header_name(index))
-            return false;
+        start_line->process_pseudo_header_name(index);
     }
 
     // If this is a regular header, write header name + ': ' to decoded headers
     else
     {
-        if (start_line and !start_line->is_finalized())
+        if (!start_line->is_finalized())
         {
             if (!finalize_start_line())
                 return false;
@@ -157,8 +155,7 @@ bool Http2HpackDecoder::decode_static_table_index(const uint64_t index, const bo
             return false;
         }
 
-        // Remove second condition after response start line implemented
-        if (index < PSEUDO_HEADER_MAX_INDEX and start_line)
+        if (index < PSEUDO_HEADER_MAX_INDEX)
         {
             start_line->process_pseudo_header_value(
                 (const uint8_t*)entry->value, strlen(entry->value));
@@ -189,8 +186,8 @@ bool Http2HpackDecoder::decode_dynamic_table_index(const uint64_t index,
     UNUSED(index);
     UNUSED(decode_full_line);
 
-    //FIXIT-H finalize header_start_line only for regular headers
-    if (start_line and !start_line->is_finalized())
+    //FIXIT-H finalize start_line only for regular headers
+    if (!start_line->is_finalized())
     {
         if (!finalize_start_line())
             return false;
@@ -263,18 +260,17 @@ bool Http2HpackDecoder::decode_literal_header_line(const uint8_t* encoded_header
             return false;
         }
         // If this was a pseudo-header value, give it to the start-line.
-        if (start_line and start_line->is_pseudo_name(
+        if (start_line->is_pseudo_name(
                 (const char*) decoded_header_buffer))
         {
             // don't include the ': ' that was written following the header name
-            if (!start_line->process_pseudo_header_name(
-                    decoded_header_buffer, partial_bytes_written - 2))
-                return false;
+            start_line->process_pseudo_header_name(
+                decoded_header_buffer, partial_bytes_written - 2);
         }
         // If not a pseudo-header value, keep it in the decoded headers
         else
         {
-            if (start_line and !start_line->is_finalized())
+            if (!start_line->is_finalized())
             {
                 if (!finalize_start_line())
                     return false;
@@ -296,7 +292,7 @@ bool Http2HpackDecoder::decode_literal_header_line(const uint8_t* encoded_header
     }
 
     // If this was a pseudo-header value, give it to the start-line.
-    if (start_line and start_line->is_pseudo_value())
+    if (start_line->is_pseudo_value())
     {
         // Subtract 2 from the length to remove the trailing CRLF before passing to the start line
         start_line->process_pseudo_header_value(
@@ -397,7 +393,7 @@ bool Http2HpackDecoder::decode_headers(const uint8_t* encoded_headers,
     }
 
     // If there were only pseudo-headers, finalize never got called, so create the start-line
-    if (start_line and !start_line->is_finalized())
+    if (!start_line->is_finalized())
     {
         success &= finalize_start_line();
     }
index 61473ad0d6460c764f4a519d8ccda4439f6add81..6e602dbb1e30d3004449158a3fcd742d9245ca4f 100644 (file)
@@ -42,10 +42,9 @@ const char* Http2RequestLine::SCHEME_NAME = ":scheme";
 const char* Http2RequestLine::OPTIONS = "OPTIONS";
 const char* Http2RequestLine::CONNECT = "CONNECT";
 
-bool Http2RequestLine::process_pseudo_header_name(const uint64_t index)
+void Http2RequestLine::process_pseudo_header_name(const uint64_t index)
 {
-    if (!process_pseudo_header_precheck())
-        return false;
+    process_pseudo_header_precheck();
 
     if (index <= AUTHORITY and authority.length() <= 0)
         value_coming = AUTHORITY;
@@ -58,16 +57,14 @@ bool Http2RequestLine::process_pseudo_header_name(const uint64_t index)
     else
     {
         infractions += INF_INVALID_PSEUDO_HEADER;
-        events->create_event(EVENT_MISFORMATTED_HTTP2);
-        return false;
+        events->create_event(EVENT_INVALID_HEADER);
+        value_coming = HEADER__INVALID;
     }
-    return true;
 }
 
-bool Http2RequestLine::process_pseudo_header_name(const uint8_t* const& name, uint32_t length)
+void Http2RequestLine::process_pseudo_header_name(const uint8_t* const& name, uint32_t length)
 {
-    if (!process_pseudo_header_precheck())
-        return false;
+    process_pseudo_header_precheck();
 
     if (length == AUTHORITY_NAME_LENGTH and memcmp(name, AUTHORITY_NAME, length) == 0 and
             authority.length() <= 0)
@@ -84,10 +81,9 @@ bool Http2RequestLine::process_pseudo_header_name(const uint8_t* const& name, ui
     else
     {
         infractions += INF_INVALID_PSEUDO_HEADER;
-        events->create_event(EVENT_MISFORMATTED_HTTP2);
-        return false;
+        events->create_event(EVENT_INVALID_HEADER);
+        value_coming = HEADER__INVALID;
     }
-    return true;
 }
 
 void Http2RequestLine::process_pseudo_header_value(const uint8_t* const& value, const uint32_t length)
@@ -107,10 +103,10 @@ void Http2RequestLine::process_pseudo_header_value(const uint8_t* const& value,
             scheme.set(length, value);
             break;
         default:
-            // should never get here
-            assert(false);
+            // ignore invalid pseudo-header value - alert generated in process_pseudo_header_name
+            break;
     }
-    value_coming = HEADER_NONE;
+    value_coming = HEADER__NONE;
 }
 
 // This is called on the first non-pseudo-header. Select the appropriate URI form based on the
@@ -125,11 +121,11 @@ bool Http2RequestLine::generate_start_line()
         if (method.length() <= 0)
         {
             infractions += INF_PSEUDO_HEADER_URI_FORM_MISMATCH;
-            events->create_event(EVENT_MISFORMATTED_HTTP2);
+            events->create_event(EVENT_REQUEST_WITHOUT_REQUIRED_FIELD);
             return false;
         }
         start_line_length = method.length() + path.length() + http_version_length +
-            start_line_extra_chars;
+            NUM_REQUEST_LINE_EXTRA_CHARS;
         start_line_buffer = new uint8_t[start_line_length];
 
         memcpy(start_line_buffer, method.start(), method.length());
@@ -147,16 +143,22 @@ bool Http2RequestLine::generate_start_line()
     else if (method.length() == CONNECT_LENGTH and memcmp(method.start(),
         CONNECT, method.length()) == 0)
     {
-        // Must have an authority and not have a scheme or path
+        // Must have an authority
         // FIXIT-L May want to be more lenient than RFC on generating start line
-        if ( scheme.length() > 0 or path.length() > 0 or authority.length() <= 0)
+        if (authority.length() <= 0)
         {
             infractions += INF_PSEUDO_HEADER_URI_FORM_MISMATCH;
-            events->create_event(EVENT_MISFORMATTED_HTTP2);
+            events->create_event(EVENT_REQUEST_WITHOUT_REQUIRED_FIELD);
             return false;
         }
+        // Should not have a scheme or path
+        if ( scheme.length() > 0 or path.length() > 0)
+        {
+            infractions += INF_PSEUDO_HEADER_URI_FORM_MISMATCH;
+            events->create_event(EVENT_INVALID_HEADER);
+        }
         start_line_length = method.length() + authority.length() + http_version_length +
-            start_line_extra_chars;
+            NUM_REQUEST_LINE_EXTRA_CHARS;
         start_line_buffer = new uint8_t[start_line_length];
 
         memcpy(start_line_buffer, method.start(), method.length());
@@ -177,8 +179,8 @@ bool Http2RequestLine::generate_start_line()
         if (authority.length() > 0)
         {
             start_line_length = method.length() + scheme.length() + authority.length() +
-                path.length() + http_version_length + start_line_extra_chars +
-                absolute_form_extra_chars_num;
+                path.length() + http_version_length + NUM_REQUEST_LINE_EXTRA_CHARS +
+                NUM_ABSOLUTE_FORM_EXTRA_CHARS;
             start_line_buffer = new uint8_t[start_line_length];
 
             memcpy(start_line_buffer, method.start(), method.length());
@@ -202,7 +204,7 @@ bool Http2RequestLine::generate_start_line()
         else
         {
             start_line_length = method.length() + path.length() + http_version_length +
-                start_line_extra_chars;
+                NUM_REQUEST_LINE_EXTRA_CHARS;
             start_line_buffer = new uint8_t[start_line_length];
 
             memcpy(start_line_buffer, method.start(), method.length());
index 3ef8e35d48c3317bc4a547f5504dcd4ab29c7e7d..22693fc94fefd078839b5a7b82ecee8e4b19cae4 100644 (file)
 class Http2RequestLine : public Http2StartLine
 {
 public:
-    Http2RequestLine(Http2EventGen* events, Http2Infractions* infractions) : Http2StartLine(events,
-        infractions) { }
-
-    bool process_pseudo_header_name(const uint64_t index) override;
-    bool process_pseudo_header_name(const uint8_t* const& name, uint32_t length) override;
+    void process_pseudo_header_name(const uint64_t index) override;
+    void process_pseudo_header_name(const uint8_t* const& name, uint32_t length) override;
     void process_pseudo_header_value(const uint8_t* const& value, const uint32_t length) override;
     bool generate_start_line() override;
 
+    friend Http2StartLine* Http2StartLine::new_start_line_generator(HttpCommon::SourceId source_id,
+        Http2EventGen* events, Http2Infractions* infractions);
+
 private:
+    Http2RequestLine(Http2EventGen* events, Http2Infractions* infractions) : Http2StartLine(events,
+        infractions) { }
+
     Field method;
     Field path;
     Field scheme;
@@ -58,8 +61,10 @@ private:
     static const char* CONNECT;
     static const int32_t CONNECT_LENGTH = 7;
 
+    // Account for two spaces, and trailing crlf
+    static const uint8_t NUM_REQUEST_LINE_EXTRA_CHARS = 4;
     // absolute form adds '://' between scheme and authority
-    static const uint32_t absolute_form_extra_chars_num = 3;
+    static const uint32_t NUM_ABSOLUTE_FORM_EXTRA_CHARS = 3;
 };
 
 #endif
index 04c95c9cb13b716da807941481503ba2f2ab6958..1336b732a9b64a964b6c0ff0555cb3bee78f8c27 100644 (file)
@@ -28,6 +28,8 @@
 
 #include "http2_enum.h"
 #include "http2_flow_data.h"
+#include "http2_request_line.h"
+#include "http2_status_line.h"
 
 using namespace HttpCommon;
 using namespace Http2Enums;
@@ -39,15 +41,22 @@ Http2StartLine::~Http2StartLine()
     delete[] start_line_buffer;
 }
 
-bool Http2StartLine::process_pseudo_header_precheck()
+Http2StartLine* Http2StartLine::new_start_line_generator(SourceId source_id,
+        Http2EventGen* events, Http2Infractions* infractions)
+{
+    if (source_id == SRC_CLIENT)
+        return new Http2RequestLine(events, infractions);
+    else
+        return new Http2StatusLine(events, infractions);
+}
+
+void Http2StartLine::process_pseudo_header_precheck()
 {
     if (finalized)
     {
         infractions += INF_PSEUDO_HEADER_AFTER_REGULAR_HEADER;
-        events->create_event(EVENT_MISFORMATTED_HTTP2);
-        return false;
+        events->create_event(EVENT_INVALID_HEADER);
     }
-    return true;
 }
 
 bool Http2StartLine::finalize()
index 08927b66e94fce80fe2beffafd6e5f5adb2a11c4..f48a9ba99edab084fb71553065b5313ccc23e2c1 100644 (file)
@@ -36,24 +36,27 @@ class Http2FlowData;
 class Http2StartLine
 {
 public:
-    Http2StartLine(Http2EventGen* events, Http2Infractions* infractions) : events(events),
-        infractions(infractions) { }
+    static Http2StartLine* new_start_line_generator(HttpCommon::SourceId source_id,
+        Http2EventGen* events, Http2Infractions* infractions);
 
     virtual ~Http2StartLine();
 
     friend class Http2Hpack;
 
     const Field* get_start_line();
-    virtual bool process_pseudo_header_name(const uint64_t index) = 0;
-    virtual bool process_pseudo_header_name(const uint8_t* const& name, uint32_t length) = 0;
+    virtual void process_pseudo_header_name(const uint64_t index) = 0;
+    virtual void process_pseudo_header_name(const uint8_t* const& name, uint32_t length) = 0;
     virtual void process_pseudo_header_value(const uint8_t* const& value, const uint32_t length) = 0;
     bool finalize();
     bool is_finalized() { return finalized; }
-    bool is_pseudo_value() { return value_coming != Http2Enums::HEADER_NONE; }
+    bool is_pseudo_value() { return value_coming != Http2Enums::HEADER__NONE; }
     bool is_pseudo_name(const char* const& name) { return name[0] == ':'; }
 
 protected:
-    bool process_pseudo_header_precheck();
+    Http2StartLine(Http2EventGen* events, Http2Infractions* infractions)   : events(events),
+        infractions(infractions) { }
+
+    void process_pseudo_header_precheck();
     virtual bool generate_start_line() = 0;
 
     Http2EventGen* events;
@@ -61,14 +64,12 @@ protected:
     bool finalized = false;
     uint32_t start_line_length = 0;
     uint8_t *start_line_buffer = nullptr;
-    Http2Enums::PseudoHeaders value_coming = Http2Enums::HEADER_NONE;
+    Http2Enums::PseudoHeaders value_coming = Http2Enums::HEADER__NONE;
     uint32_t pseudo_header_fragment_size = 0;
 
     // Version string is HTTP/1.1
     static const char* http_version_string;
     static const uint8_t http_version_length = 8;
-    // Account for two spaces, and trailing crlf
-    static const uint8_t start_line_extra_chars = 4;
 };
 
 #endif
diff --git a/src/service_inspectors/http2_inspect/http2_status_line.cc b/src/service_inspectors/http2_inspect/http2_status_line.cc
new file mode 100644 (file)
index 0000000..fa69518
--- /dev/null
@@ -0,0 +1,106 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 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.
+//--------------------------------------------------------------------------
+// http2_status_line.cc author Katura Harvey <katharve@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "http2_status_line.h"
+
+#include <cstdlib>
+
+#include "service_inspectors/http_inspect/http_common.h"
+#include "service_inspectors/http_inspect/http_field.h"
+
+#include "http2_enum.h"
+#include "http2_flow_data.h"
+
+using namespace HttpCommon;
+using namespace Http2Enums;
+
+const char* Http2StatusLine::STATUS_NAME = ":status";
+
+void Http2StatusLine::process_pseudo_header_name(const uint64_t index)
+{
+    process_pseudo_header_precheck();
+
+    if (index >= RESPONSE_PSEUDO_MIN_INDEX and index <= STATUS and status.length() <= 0)
+        value_coming = STATUS;
+    else
+    {
+        infractions += INF_INVALID_PSEUDO_HEADER;
+        events->create_event(EVENT_INVALID_HEADER);
+        value_coming = HEADER__INVALID;
+    }
+}
+
+void Http2StatusLine::process_pseudo_header_name(const uint8_t* const& name, uint32_t length)
+{
+    process_pseudo_header_precheck();
+
+    if (length == STATUS_NAME_LENGTH and memcmp(name, STATUS_NAME, length) == 0 and
+            status.length() <= 0)
+        value_coming = STATUS;
+    else
+    {
+        infractions += INF_INVALID_PSEUDO_HEADER;
+        events->create_event(EVENT_INVALID_HEADER);
+        value_coming = HEADER__INVALID;
+    }
+}
+
+void Http2StatusLine::process_pseudo_header_value(const uint8_t* const& value, const uint32_t length)
+{
+    // ignore invalid pseudo-header value - alert generated in process_pseudo_header_name
+    if  (value_coming == STATUS)
+        status.set(length, (const uint8_t*) value);
+
+    value_coming = HEADER__NONE;
+}
+
+// This is called on the first non-pseudo-header.
+bool Http2StatusLine::generate_start_line()
+{
+    uint32_t bytes_written = 0;
+    
+    // Account for one space and trailing crlf
+    static const uint8_t NUM_RESPONSE_LINE_EXTRA_CHARS = 3;
+
+    if (status.length() <= 0)
+    {
+        infractions += INF_RESPONSE_WITHOUT_STATUS;
+        events->create_event(EVENT_RESPONSE_WITHOUT_STATUS);
+        return false;
+    }
+
+    start_line_length = http_version_length + status.length() + NUM_RESPONSE_LINE_EXTRA_CHARS;
+    start_line_buffer = new uint8_t[start_line_length];
+
+    memcpy(start_line_buffer + bytes_written, http_version_string, http_version_length);
+    bytes_written += http_version_length;
+    memcpy(start_line_buffer + bytes_written, " ", 1);
+    bytes_written += 1;
+    memcpy(start_line_buffer + bytes_written, status.start(), status.length());
+    bytes_written += status.length();
+    memcpy(start_line_buffer + bytes_written, "\r\n", 2);
+    bytes_written += 2;
+    assert(bytes_written == start_line_length);
+
+    return true;
+}
diff --git a/src/service_inspectors/http2_inspect/http2_status_line.h b/src/service_inspectors/http2_inspect/http2_status_line.h
new file mode 100644 (file)
index 0000000..e0a22ad
--- /dev/null
@@ -0,0 +1,51 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 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.
+//--------------------------------------------------------------------------
+// http2_status_line.h author Katura Harvey <katharve@cisco.com>
+
+#ifndef HTTP2_STATUS_LINE_H
+#define HTTP2_STATUS_LINE_H
+
+#include "service_inspectors/http_inspect/http_common.h"
+#include "service_inspectors/http_inspect/http_field.h"
+
+#include "http2_start_line.h"
+
+class Http2StatusLine : public Http2StartLine
+{
+public:
+    void process_pseudo_header_name(const uint64_t index) override;
+    void process_pseudo_header_name(const uint8_t* const& name, uint32_t length) override;
+    void process_pseudo_header_value(const uint8_t* const& value, const uint32_t length) override;
+    bool generate_start_line() override;
+
+    friend Http2StartLine* Http2StartLine::new_start_line_generator(HttpCommon::SourceId source_id,
+        Http2EventGen* events, Http2Infractions* infractions);
+
+private:
+    Http2StatusLine(Http2EventGen* events, Http2Infractions* infractions) : Http2StartLine(events,
+        infractions) { }
+
+    Field status;
+
+    static const char* STATUS_NAME;
+    static const uint32_t STATUS_NAME_LENGTH = 7;
+    static const uint32_t RESPONSE_PSEUDO_MIN_INDEX = 8;
+
+};
+
+#endif
index 04a44eb70d9a7ef71121464e20e4d9fdf8847f0b..8ba1c37c417b6232e319383de0166db7dad4a592 100644 (file)
@@ -32,12 +32,15 @@ using namespace snort;
 const RuleMap Http2Module::http2_events[] =
 {
     { EVENT_INT_DECODE_FAILURE, "error in HPACK integer value" },
-    { EVENT_INT_LEADING_ZEROS, "integer value has leading zeros" },
+    { EVENT_INT_LEADING_ZEROS, "HPACK integer value has leading zeros" },
     { EVENT_STRING_DECODE_FAILURE, "error in HPACK string value" },
-    { EVENT_MISSING_CONTINUATION, "missing continuation frame"},
-    { EVENT_UNEXPECTED_CONTINUATION, "unexpected continuation frame"},
-    { EVENT_MISFORMATTED_HTTP2, "misformatted HTTP/2 traffic"},
-    { EVENT_PREFACE_MATCH_FAILURE, "HTTP/2 connection preface does not match"},
+    { EVENT_MISSING_CONTINUATION, "missing HTTP/2 continuation frame" },
+    { EVENT_UNEXPECTED_CONTINUATION, "unexpected HTTP/2 continuation frame" },
+    { EVENT_MISFORMATTED_HTTP2, "misformatted HTTP/2 traffic" },
+    { EVENT_PREFACE_MATCH_FAILURE, "HTTP/2 connection preface does not match" },
+    { EVENT_REQUEST_WITHOUT_REQUIRED_FIELD, "HTTP/2 request missing required header field" },
+    { EVENT_RESPONSE_WITHOUT_STATUS, "HTTP/2 response has no status code" },
+    { EVENT_INVALID_HEADER, "invalid HTTP/2 header field" },
     { 0, nullptr }
 };