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
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.
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
};
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
};
enum PseudoHeaders
{
- HEADER_NONE = 0,
+ HEADER__INVALID = -1,
+ HEADER__NONE = 0,
AUTHORITY = 1,
METHOD = 3,
PATH = 5,
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); }
#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;
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() -
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);
}
#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;
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;
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));
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;
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;
}
// 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(
}
// 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();
}
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;
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)
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)
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
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());
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());
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());
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());
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;
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
#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;
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()
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;
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
--- /dev/null
+//--------------------------------------------------------------------------
+// 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;
+}
--- /dev/null
+//--------------------------------------------------------------------------
+// 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
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 }
};