http2_frame.h
http2_headers_frame.cc
http2_headers_frame.h
+ http2_headers_frame_header.cc
+ http2_headers_frame_header.h
+ http2_headers_frame_trailer.cc
+ http2_headers_frame_trailer.h
http2_hpack.cc
http2_hpack.h
http2_hpack_dynamic_table.cc
-The HTTP/2 inspector (H2I) will convert HTTP/2 frames into HTTP/1.1 message sections and feed them
+The HTTP/2 inspector (H2I) converts HTTP/2 frames into HTTP/1.1 message sections and feeds them
to the new HTTP inspector (NHI) for further processing.
-The current implementation is the very first step. It splits an HTTP/2 stream into frame sections
-and forwards them for inspection. It does not interface with NHI and does not address the
-multiplexed nature of HTTP/2.
-
The Http2StreamSplitter strips the frame headers from the frame data and stores them in separate
buffers. As in NHI, long data frames are split into 16kb chunks for inspection. If a header frame
is followed by continuation frames, all the header frames are flushed together for inspection. The
the frame headers, the frame data is stored as a single block, consisting of the HPACK encoded
HTTP/2 headers.
-HPACK decoding is under ongoing development. In the current implementation, reassemble() makes a
-first copy of the encoded headers, which is stored in the frame_data buffer. The frame_data buffer
-is passed to the function decode_headers(), which is the main loop driving HPACK decoding. The
-function allocates a second buffer, raw_decoded_header, to which the decoding routine will
-progressively write. As part of decoding the pseudo-headers (described below), data that must not be
-sent to NHI may be written to the start of the buffer. In order to avoid making any extra copies,
-the decoding routine sets a pointer to the start of the regular headers inside raw_decoded_header
-that will be processed by NHI, called http2_decoded_header.
+HTTP/2 headers frames can come at the start of a stream and contain the header block, or at the end
+of a stream and contain trailers. H2I contains headers frame subclasses Http2HeadersFrameHeader and
+Http2HeadersFrameTrailer to support these two types of headers frames. Headers frames containing the
+header block will contain pseudo-headers that must be converted into an HTTP/1.1 start line in
+addition to regular HTTP/1.1 headers. The two Http2StartLine subclasses, Http2RequestLine and
+Http2ResponseLine perform this translation and generate the start-line, which is stored in a new
+buffer inside the Http2StartLine object. Trailers may only contain regular headers.
+
+Both headers and trailers must undergo HPACK decoding before being sent to NHI for processing. To
+perform decoding, reassemble() makes a first copy of the encoded headers, which is stored in the
+frame_data buffer. The frame_data buffer is passed to the function decode_headers(), which is the
+main loop driving HPACK decoding. Each decoded header line is progressively written to a second
+decoded_headers buffer that will ultimately be sent to NHI.
The main loop in decode_headers() finds the cut point for a single header line. The line is is
passed to decode_header_line(), which parses the line and calls the appropriate decoding function
the dynamic table. The static table is 61-elements defined in the HPACK RFC. The dynamic table,
which starts at index 62, is specific to each direction of each flow. For the second type, literal
to be indexed, the header name may be indexed or a string literal, while the value is always a
-literal. The resulting header line will then be added to the dynamic table (not yet implemented).
-The third representation type is literal not to be indexed, which is the same as literal to be
-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 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.
+literal. The resulting header line is then added to the dynamic table. The third representation type
+is literal not to be indexed, which is the same as literal to be indexed, except the header line is
+not added to the dynamic table.
H2I supports the NHI test tool. See ../http_inspect/dev_notes.txt for usage instructions.
{
Http2Stream* const stream = session_data->find_stream(
session_data->current_stream[source_id]);
- stream->finish_msg_body(source_id);
+ bool clear_partial_buffers = false;
+ if (!bytes_sent_http)
+ clear_partial_buffers = true;
+ stream->finish_msg_body(source_id, false, clear_partial_buffers);
// Since there may be multiple frame headers or zero frame headers in the flushed
// data section, remember END_STREAM flag in the stream object
EVENT_DYNAMIC_TABLE_OVERFLOW = 14,
EVENT_INVALID_STARTLINE = 15,
EVENT_PADDING_LEN = 16,
+ EVENT_PSEUDO_HEADER_AFTER_REGULAR_HEADER = 17,
+ EVENT_PSEUDO_HEADER_IN_TRAILERS = 18,
+ EVENT_INVALID_PSEUDO_HEADER = 19,
EVENT__MAX_VALUE
};
INF_INVALID_HEADER = 26,
INF_PADDING_LEN = 27,
INF_TRAILERS_AFTER_END_STREAM = 28,
+ INF_PSEUDO_HEADER_IN_TRAILERS = 29,
INF__MAX_VALUE
};
NO_HEADER = 0x80, //No valid flags use this bit
};
-enum PseudoHeaders
-{
- HEADER__INVALID = -1,
- HEADER__NONE = 0,
- AUTHORITY = 1,
- METHOD = 3,
- PATH = 5,
- SCHEME = 7,
- STATUS = 14,
-};
-
enum SettingsFrameIds
{
HEADER_TABLE_SIZE = 1,
friend class Http2DataFrame;
friend class Http2DataCutter;
friend class Http2HeadersFrame;
+ friend class Http2HeadersFrameHeader;
+ friend class Http2HeadersFrameTrailer;
friend class Http2Hpack;
friend class Http2Inspect;
friend class Http2RequestLine;
#include "http2_data_frame.h"
#include "http2_enum.h"
#include "http2_flow_data.h"
-#include "http2_headers_frame.h"
+#include "http2_headers_frame_header.h"
+#include "http2_headers_frame_trailer.h"
#include "http2_settings_frame.h"
#include "http2_stream.h"
#include "service_inspectors/http_inspect/http_field.h"
switch(session_data->frame_type[source_id])
{
case FT_HEADERS:
- return new Http2HeadersFrame(header, header_len, data, data_len, session_data,
- source_id, stream);
+ if (stream->get_state(source_id) == STATE_IDLE)
+ return new Http2HeadersFrameHeader(header, header_len, data, data_len, session_data,
+ source_id, stream);
+ else
+ return new Http2HeadersFrameTrailer(header, header_len, data, data_len,
+ session_data, source_id, stream);
case FT_SETTINGS:
return new Http2SettingsFrame(header, header_len, data, data_len, session_data,
source_id, stream);
Http2Frame(header_buffer, header_len, data_buffer, data_len, session_data_, source_id_, stream_)
{
// FIXIT-E check frame validity before creating frame
- if (!check_frame_validity())
- return;
-
- // If the stream state is not IDLE, this frame contains trailers.
- if (stream->get_state(source_id) >= STATE_OPEN)
+ if (!check_frame_validity() or data.length() <= 0)
{
- HttpFlowData* http_flow = session_data->get_current_stream(source_id)->get_hi_flow_data();
- if (http_flow->get_type_expected(source_id) != HttpEnums::SEC_TRAILER)
- {
- // If there was no unflushed data on this stream when the trailers arrived, http_inspect
- // will not yet be expecting trailers. Flush empty buffer through scan, reassemble, and
- // eval to prepare http_inspect for trailers.
- assert(http_flow->get_type_expected(source_id) == HttpEnums::SEC_BODY_H2);
- stream->finish_msg_body(source_id, true); // calls http_inspect scan()
-
- unsigned copied;
- StreamBuffer stream_buf;
- stream_buf = session_data->hi_ss[source_id]->reassemble(session_data->flow,
- 0, 0, nullptr, 0, PKT_PDU_TAIL, copied);
- assert(stream_buf.data != nullptr);
- assert(copied == 0);
-
- Http2DummyPacket dummy_pkt;
- dummy_pkt.flow = session_data->flow;
- dummy_pkt.packet_flags = (source_id == SRC_CLIENT) ? PKT_FROM_CLIENT : PKT_FROM_SERVER;
- dummy_pkt.dsize = stream_buf.length;
- dummy_pkt.data = stream_buf.data;
- session_data->hi->eval(&dummy_pkt);
- assert (http_flow->get_type_expected(source_id) == HttpEnums::SEC_TRAILER);
- if (http_flow->get_type_expected(source_id) == HttpEnums::SEC_ABORT)
- {
- hi_abort = true;
- return;
- }
- session_data->hi->clear(&dummy_pkt);
- }
- // FIXIT-E Trailers are not yet being processed
- trailer = true;
+ process_frame = false;
return;
}
- // No need to process an empty headers frame
- if (data.length() <= 0)
- return;
-
- uint8_t hpack_headers_offset = 0;
-
// Remove stream dependency if present
if (get_flags() & PRIORITY)
hpack_headers_offset = 5;
- // Set up the decoding context
- Http2HpackDecoder& hpack_decoder = session_data->hpack_decoder[source_id];
-
- // Allocate stuff
+ // Set up HPACK decoding
+ hpack_decoder = &session_data->hpack_decoder[source_id];
decoded_headers = new uint8_t[MAX_OCTETS];
+}
- 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() -
- hpack_headers_offset, decoded_headers, start_line_generator))
- {
- session_data->frame_type[source_id] = FT__ABORT;
- error_during_decode = true;
- }
- start_line = hpack_decoder.get_start_line();
- http1_header = hpack_decoder.get_decoded_headers(decoded_headers);
+Http2HeadersFrame::~Http2HeadersFrame()
+{
+ delete http1_header;
+ delete[] decoded_headers;
+}
- if (error_during_decode)
+void Http2HeadersFrame::clear()
+{
+ if (error_during_decode || hi_abort)
return;
+ Packet dummy_pkt(false);
+ dummy_pkt.flow = session_data->flow;
+ session_data->hi->clear(&dummy_pkt);
+}
- // http_inspect scan() of start line
- {
- uint32_t flush_offset;
- Http2DummyPacket dummy_pkt;
- dummy_pkt.flow = session_data->flow;
- const uint32_t unused = 0;
- const StreamSplitter::Status start_scan_result =
- session_data->hi_ss[source_id]->scan(&dummy_pkt, start_line->start(),
- start_line->length(), unused, &flush_offset);
- assert(start_scan_result == StreamSplitter::FLUSH);
- UNUSED(start_scan_result);
- assert((int64_t)flush_offset == start_line->length());
- }
- StreamBuffer stream_buf;
- // http_inspect reassemble() of start line
- {
- unsigned copied;
- stream_buf = session_data->hi_ss[source_id]->reassemble(session_data->flow,
- start_line->length(), 0, start_line->start(), start_line->length(), PKT_PDU_TAIL,
- copied);
- assert(stream_buf.data != nullptr);
- assert(copied == (unsigned)start_line->length());
- }
- HttpFlowData* http_flow = session_data->get_current_stream(source_id)->get_hi_flow_data();
- // http_inspect eval() and clear() of start line
- {
- Http2DummyPacket dummy_pkt;
- dummy_pkt.flow = session_data->flow;
- dummy_pkt.packet_flags = (source_id == SRC_CLIENT) ? PKT_FROM_CLIENT : PKT_FROM_SERVER;
- dummy_pkt.dsize = stream_buf.length;
- dummy_pkt.data = stream_buf.data;
- session_data->hi->eval(&dummy_pkt);
- if (http_flow->get_type_expected(source_id) != HttpEnums::SEC_HEADER)
- {
- *session_data->infractions[source_id] += INF_INVALID_STARTLINE;
- session_data->events[source_id]->create_event(EVENT_INVALID_STARTLINE);
- hi_abort = true;
- return;
- }
- session_data->hi->clear(&dummy_pkt);
- }
+void Http2HeadersFrame::process_decoded_headers(HttpFlowData* http_flow)
+{
+ if (error_during_decode)
+ return;
+
+ http1_header = hpack_decoder->get_decoded_headers(decoded_headers);
+ StreamBuffer stream_buf;
// http_inspect scan() of headers
{
}
}
-Http2HeadersFrame::~Http2HeadersFrame()
-{
- delete start_line;
- delete start_line_generator;
- delete http1_header;
- delete[] decoded_headers;
-}
-
-void Http2HeadersFrame::clear()
-{
- if (error_during_decode || hi_abort)
- return;
- Packet dummy_pkt(false);
- dummy_pkt.flow = session_data->flow;
- session_data->hi->clear(&dummy_pkt);
-}
-
const Field& Http2HeadersFrame::get_buf(unsigned id)
{
switch (id)
#ifdef REG_TEST
void Http2HeadersFrame::print_frame(FILE* output)
{
- if (!trailer)
- fprintf(output, "Headers frame\n");
- else
- fprintf(output, "Trailing Headers frame\n");
if (error_during_decode)
fprintf(output, "Error decoding headers.\n");
- if (start_line)
- start_line->print(output, "Decoded start-line");
if (http1_header)
http1_header->print(output, "Decoded header");
Http2Frame::print_frame(output);
class Http2StartLine;
class Http2Frame;
class Http2Stream;
+class HttpFlowData;
class Http2HeadersFrame : public Http2Frame
{
bool is_detection_required() const override { return detection_required; }
void update_stream_state() override;
- friend Http2Frame* Http2Frame::new_frame(const uint8_t*, const int32_t, const uint8_t*,
- const int32_t, Http2FlowData*, HttpCommon::SourceId, Http2Stream* stream);
-
#ifdef REG_TEST
void print_frame(FILE* output) override;
#endif
-private:
+protected:
Http2HeadersFrame(const uint8_t* header_buffer, const int32_t header_len,
const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* ssn_data,
HttpCommon::SourceId src_id, Http2Stream* stream);
bool check_frame_validity();
+ void process_decoded_headers(HttpFlowData* http_flow);
- Http2StartLine* start_line_generator = nullptr;
uint8_t* decoded_headers = nullptr; // working buffer to store decoded headers
uint32_t decoded_headers_size = 0;
const Field* http1_header = nullptr; // finalized headers to be passed to NHI
- const Field* start_line = nullptr;
bool error_during_decode = false;
bool hi_abort = false;
uint32_t xtradata_mask = 0;
bool detection_required = false;
-
- // FIXIT-E Process trailers
- bool trailer = false;
+ bool process_frame = true;
+ Http2HpackDecoder* hpack_decoder;
+ uint8_t hpack_headers_offset = 0;
};
#endif
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 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_headers_frame_header.cc author Katura Harvey <katharve@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "http2_headers_frame_header.h"
+
+#include "protocols/packet.h"
+#include "service_inspectors/http_inspect/http_enum.h"
+#include "service_inspectors/http_inspect/http_flow_data.h"
+#include "service_inspectors/http_inspect/http_inspect.h"
+#include "service_inspectors/http_inspect/http_stream_splitter.h"
+
+#include "http2_dummy_packet.h"
+#include "http2_enum.h"
+#include "http2_flow_data.h"
+#include "http2_hpack.h"
+#include "http2_start_line.h"
+#include "http2_stream.h"
+
+using namespace snort;
+using namespace HttpCommon;
+using namespace Http2Enums;
+
+Http2HeadersFrameHeader::Http2HeadersFrameHeader(const uint8_t* header_buffer, const int32_t header_len,
+ const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* session_data_,
+ HttpCommon::SourceId source_id_, Http2Stream* stream_) :
+ Http2HeadersFrame(header_buffer, header_len, data_buffer, data_len, session_data_, source_id_,
+ stream_)
+{
+ if (!process_frame)
+ return;
+
+ 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() -
+ hpack_headers_offset, decoded_headers, start_line_generator, false))
+ {
+ session_data->frame_type[source_id] = FT__ABORT;
+ error_during_decode = true;
+ }
+
+ // process start line
+ if (!start_line_generator->generate_start_line(start_line))
+ {
+ // FIXIT-E set stream state to error
+ return;
+ }
+
+ StreamBuffer stream_buf;
+ HttpFlowData* http_flow;
+
+ // http_inspect scan() of start line
+ {
+ uint32_t flush_offset;
+ Http2DummyPacket dummy_pkt;
+ dummy_pkt.flow = session_data->flow;
+ const uint32_t unused = 0;
+ const StreamSplitter::Status start_scan_result =
+ session_data->hi_ss[source_id]->scan(&dummy_pkt, start_line->start(),
+ start_line->length(), unused, &flush_offset);
+ assert(start_scan_result == StreamSplitter::FLUSH);
+ UNUSED(start_scan_result);
+ assert((int64_t)flush_offset == start_line->length());
+ }
+
+ // http_inspect reassemble() of start line
+ {
+ unsigned copied;
+ stream_buf = session_data->hi_ss[source_id]->reassemble(session_data->flow,
+ start_line->length(), 0, start_line->start(), start_line->length(), PKT_PDU_TAIL,
+ copied);
+ assert(stream_buf.data != nullptr);
+ assert(copied == (unsigned)start_line->length());
+ }
+
+ http_flow = session_data->get_current_stream(source_id)->get_hi_flow_data();
+ // http_inspect eval() and clear() of start line
+ {
+ Http2DummyPacket dummy_pkt;
+ dummy_pkt.flow = session_data->flow;
+ dummy_pkt.packet_flags = (source_id == SRC_CLIENT) ? PKT_FROM_CLIENT : PKT_FROM_SERVER;
+ dummy_pkt.dsize = stream_buf.length;
+ dummy_pkt.data = stream_buf.data;
+ session_data->hi->eval(&dummy_pkt);
+ if (http_flow->get_type_expected(source_id) != HttpEnums::SEC_HEADER)
+ {
+ *session_data->infractions[source_id] += INF_INVALID_STARTLINE;
+ session_data->events[source_id]->create_event(EVENT_INVALID_STARTLINE);
+ hi_abort = true;
+ return;
+ }
+ session_data->hi->clear(&dummy_pkt);
+ }
+
+ process_decoded_headers(http_flow);
+}
+
+Http2HeadersFrameHeader::~Http2HeadersFrameHeader()
+{
+ delete start_line;
+ delete start_line_generator;
+}
+
+#ifdef REG_TEST
+void Http2HeadersFrameHeader::print_frame(FILE* output)
+{
+ fprintf(output, "Headers frame\n");
+ if (start_line)
+ start_line->print(output, "Decoded start-line");
+ else
+ fprintf(output, "Error generating start line.\n");
+ Http2HeadersFrame::print_frame(output);
+}
+#endif
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 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_headers_frame_header.h author Katura Harvey <katharve@cisco.com>
+
+#ifndef HTTP2_HEADERS_FRAME_HEADER_H
+#define HTTP2_HEADERS_FRAME_HEADER_H
+
+#include "http2_frame.h"
+#include "http2_headers_frame.h"
+
+class Http2StartLine;
+
+class Http2HeadersFrameHeader : public Http2HeadersFrame
+{
+public:
+ ~Http2HeadersFrameHeader();
+
+ friend Http2Frame* Http2Frame::new_frame(const uint8_t*, const int32_t, const uint8_t*,
+ const int32_t, Http2FlowData*, HttpCommon::SourceId, Http2Stream* stream);
+
+#ifdef REG_TEST
+ void print_frame(FILE* output) override;
+#endif
+
+private:
+ Http2HeadersFrameHeader(const uint8_t* header_buffer, const int32_t header_len,
+ const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* ssn_data,
+ HttpCommon::SourceId src_id, Http2Stream* stream);
+
+ Http2StartLine* start_line_generator = nullptr;
+ const Field* start_line = nullptr;
+};
+#endif
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 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_headers_frame_trailer.cc author Katura Harvey <katharve@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "http2_headers_frame_trailer.h"
+
+#include "protocols/packet.h"
+#include "service_inspectors/http_inspect/http_enum.h"
+#include "service_inspectors/http_inspect/http_flow_data.h"
+#include "service_inspectors/http_inspect/http_inspect.h"
+#include "service_inspectors/http_inspect/http_stream_splitter.h"
+
+#include "http2_dummy_packet.h"
+#include "http2_enum.h"
+#include "http2_flow_data.h"
+#include "http2_hpack.h"
+#include "http2_stream.h"
+
+using namespace snort;
+using namespace HttpCommon;
+using namespace Http2Enums;
+
+Http2HeadersFrameTrailer::Http2HeadersFrameTrailer(const uint8_t* header_buffer, const int32_t header_len,
+ const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* session_data_,
+ HttpCommon::SourceId source_id_, Http2Stream* stream_) :
+ Http2HeadersFrame(header_buffer, header_len, data_buffer, data_len, session_data_, source_id_,
+ stream_)
+{
+ if (!process_frame)
+ return;
+
+ StreamBuffer stream_buf;
+ HttpFlowData* http_flow;
+
+ http_flow = session_data->get_current_stream(source_id)->get_hi_flow_data();
+ assert(http_flow);
+ if (http_flow->get_type_expected(source_id) != HttpEnums::SEC_TRAILER)
+ {
+ // If there was no unflushed data on this stream when the trailers arrived, http_inspect
+ // will not yet be expecting trailers. Flush empty buffer through scan, reassemble, and
+ // eval to prepare http_inspect for trailers.
+ assert(http_flow->get_type_expected(source_id) == HttpEnums::SEC_BODY_H2);
+ stream->finish_msg_body(source_id, true, true); // calls http_inspect scan()
+
+ unsigned copied;
+ stream_buf = session_data->hi_ss[source_id]->reassemble(session_data->flow,
+ 0, 0, nullptr, 0, PKT_PDU_TAIL, copied);
+ assert(stream_buf.data != nullptr);
+ assert(copied == 0);
+
+ Http2DummyPacket dummy_pkt;
+ dummy_pkt.flow = session_data->flow;
+ dummy_pkt.packet_flags = (source_id == SRC_CLIENT) ? PKT_FROM_CLIENT : PKT_FROM_SERVER;
+ dummy_pkt.dsize = stream_buf.length;
+ dummy_pkt.data = stream_buf.data;
+ session_data->hi->eval(&dummy_pkt);
+ assert (http_flow->get_type_expected(source_id) == HttpEnums::SEC_TRAILER);
+ if (http_flow->get_type_expected(source_id) == HttpEnums::SEC_ABORT)
+ {
+ hi_abort = true;
+ return;
+ }
+ session_data->hi->clear(&dummy_pkt);
+ }
+
+ // Decode headers
+ if (!hpack_decoder->decode_headers((data.start() + hpack_headers_offset), data.length() -
+ hpack_headers_offset, decoded_headers, nullptr, true))
+ {
+ session_data->frame_type[source_id] = FT__ABORT;
+ error_during_decode = true;
+ }
+
+ process_decoded_headers(http_flow);
+}
+
+#ifdef REG_TEST
+void Http2HeadersFrameTrailer::print_frame(FILE* output)
+{
+ fprintf(output, "Trailers frame\n");
+ Http2HeadersFrame::print_frame(output);
+
+}
+#endif
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 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_headers_frame_trailer.h author Katura Harvey <katharve@cisco.com>
+
+#ifndef HTTP2_HEADERS_FRAME_TRAILER_H
+#define HTTP2_HEADERS_FRAME_TRAILER_H
+
+#include "http2_frame.h"
+#include "http2_headers_frame.h"
+
+class Http2HeadersFrameTrailer : public Http2HeadersFrame
+{
+public:
+ friend Http2Frame* Http2Frame::new_frame(const uint8_t*, const int32_t, const uint8_t*,
+ const int32_t, Http2FlowData*, HttpCommon::SourceId, Http2Stream* stream);
+
+#ifdef REG_TEST
+ void print_frame(FILE* output) override;
+#endif
+
+private:
+ Http2HeadersFrameTrailer(const uint8_t* header_buffer, const int32_t header_len,
+ const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* ssn_data,
+ HttpCommon::SourceId src_id, Http2Stream* stream);
+};
+#endif
return true;
}
-bool Http2HpackDecoder::decode_indexed_name(const uint8_t* encoded_header_buffer,
- const uint32_t encoded_header_length, const Http2HpackIntDecode& decode_int,
- uint32_t& bytes_consumed, uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
- uint32_t& bytes_written, Field& name)
+const HpackTableEntry* Http2HpackDecoder::get_hpack_table_entry(
+ const uint8_t* encoded_header_buffer, const uint32_t encoded_header_length,
+ const Http2HpackIntDecode& decode_int, uint32_t& bytes_consumed)
{
uint64_t index;
- bytes_written = 0;
bytes_consumed = 0;
if (!decode_int.translate(encoded_header_buffer, encoded_header_length, bytes_consumed,
- index, events, infractions))
- return false;
-
- const HpackTableEntry* const entry = decode_table.lookup(index);
+ index, events, infractions))
+ {
+ return nullptr;
+ }
+ const HpackTableEntry* entry = decode_table.lookup(index);
if (!entry)
{
*infractions += INF_HPACK_INDEX_OUT_OF_BOUNDS;
events->create_event(EVENT_MISFORMATTED_HTTP2);
- return false;
}
+ return entry;
+}
+
+bool Http2HpackDecoder::decode_indexed_name(const uint8_t* encoded_header_buffer,
+ const uint32_t encoded_header_length, const Http2HpackIntDecode& decode_int,
+ uint32_t& bytes_consumed, uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
+ uint32_t& bytes_written, Field& name)
+{
+ bytes_written = bytes_consumed = 0;
- // If not a pseudo-header, write to the decoded_header buffer
- if (index > HpackIndexTable::PSEUDO_HEADER_MAX_STATIC_INDEX)
+ const HpackTableEntry* entry = get_hpack_table_entry(encoded_header_buffer,
+ encoded_header_length, decode_int, bytes_consumed);
+ if (!entry)
+ return false;
+
+ if (!write_decoded_headers(entry->name.start(), entry->name.length(), decoded_header_buffer,
+ decoded_header_length, bytes_written))
{
- if (!write_decoded_headers(entry->name.start(), entry->name.length(), decoded_header_buffer,
- decoded_header_length, bytes_written))
- return false;
+ return false;
}
name.set(entry->name);
bool Http2HpackDecoder::decode_literal_header_line(const uint8_t* encoded_header_buffer,
const uint32_t encoded_header_length, const uint8_t name_index_mask,
const Http2HpackIntDecode& decode_int, bool with_indexing, uint32_t& bytes_consumed,
- uint8_t* decoded_header_buffer, const uint32_t decoded_header_length, uint32_t& bytes_written)
+ uint8_t* decoded_header_buffer, const uint32_t decoded_header_length, uint32_t& bytes_written,
+ Field& name, Field& value)
{
- bytes_written = 0;
- bytes_consumed = 0;
- uint32_t partial_bytes_consumed;
- uint32_t partial_bytes_written;
- Field name, value;
-
- table_size_update_allowed = false;
+ bytes_written = bytes_consumed = 0;
+ uint32_t partial_bytes_consumed, partial_bytes_written;
// Indexed field name
if (encoded_header_buffer[0] & name_index_mask)
bytes_consumed += partial_bytes_consumed;
bytes_written += partial_bytes_written;
- // If this was a pseudo-header value, give it to the start-line.
- if (start_line->is_pseudo_name(name.start()))
- {
- start_line->process_pseudo_header_name(name.start(), name.length());
- // Pseudo_header after regular header not allowed
- if (start_line->is_finalized())
- bytes_written -= partial_bytes_written;
- }
- else
- {
- if (!start_line->is_finalized())
- {
- if (!finalize_start_line())
- return false;
- }
-
- if (!write_decoded_headers((const uint8_t*)": ", 2, decoded_header_buffer + bytes_written,
- decoded_header_length - bytes_written, partial_bytes_written))
- return false;
- bytes_written += partial_bytes_written;
- }
+ if (!write_decoded_headers((const uint8_t*)": ", 2, decoded_header_buffer + bytes_written,
+ decoded_header_length - bytes_written, partial_bytes_written))
+ return false;
+ bytes_written += partial_bytes_written;
// Value is always a string literal
if (!decode_string_literal(encoded_header_buffer + bytes_consumed, encoded_header_length -
bytes_written += partial_bytes_written;
bytes_consumed += partial_bytes_consumed;
- // If this was a pseudo-header, give it to the start-line
- if (start_line->is_pseudo_value())
- {
- start_line->process_pseudo_header_value(value.start(), value.length());
- // Pseudo_header after regular header not allowed
- if (start_line->is_finalized())
- bytes_written -= partial_bytes_written;
- }
- else
- {
- if (!write_decoded_headers((const uint8_t*)"\r\n", 2, decoded_header_buffer + bytes_written,
- decoded_header_length - bytes_written, partial_bytes_written))
- return false;
- bytes_written += partial_bytes_written;
- }
+ if (!write_decoded_headers((const uint8_t*)"\r\n", 2, decoded_header_buffer + bytes_written,
+ decoded_header_length - bytes_written, partial_bytes_written))
+ return false;
+ bytes_written += partial_bytes_written;
if (with_indexing)
{
bool Http2HpackDecoder::decode_indexed_header(const uint8_t* encoded_header_buffer,
const uint32_t encoded_header_length, const Http2HpackIntDecode& decode_int,
uint32_t &bytes_consumed, uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
- uint32_t& bytes_written)
+ uint32_t& bytes_written, Field& name, Field& value)
{
- uint64_t index;
- bytes_written = 0;
- bytes_consumed = 0;
-
- table_size_update_allowed = false;
-
- if (!decode_int.translate(encoded_header_buffer, encoded_header_length, bytes_consumed,
- index, events, infractions))
- return false;
-
- const HpackTableEntry* const entry = decode_table.lookup(index);
+ uint32_t partial_bytes_written = 0;
+ bytes_written = bytes_consumed = 0;
+ const HpackTableEntry* const entry = get_hpack_table_entry(encoded_header_buffer,
+ encoded_header_length, decode_int, bytes_consumed);
if (!entry)
- {
- *infractions += INF_HPACK_INDEX_OUT_OF_BOUNDS;
- events->create_event(EVENT_MISFORMATTED_HTTP2);
return false;
- }
+ name.set(entry->name);
+ value.set(entry->value);
- if (entry->value.length() <= 0)
+ if (value.length() <= 0)
{
*infractions += INF_LOOKUP_EMPTY_VALUE;
events->create_event(EVENT_MISFORMATTED_HTTP2);
}
- // If this was a pseudo-header value, give it to the start-line.
- if (start_line->is_pseudo_name(entry->name.start()))
- {
- start_line->process_pseudo_header_name(entry->name.start(), entry->name.length());
- start_line->process_pseudo_header_value(entry->value.start(), entry->value.length());
- }
- else
- {
- uint32_t local_bytes_written = 0;
- if (!start_line->is_finalized())
- if (!finalize_start_line())
- return false;
- if (!write_decoded_headers(entry->name.start(), entry->name.length(), decoded_header_buffer,
- decoded_header_length, local_bytes_written))
- return false;
- bytes_written += local_bytes_written;
- if (!write_decoded_headers((const uint8_t*) ": ", 2, decoded_header_buffer + bytes_written,
- decoded_header_length - bytes_written, local_bytes_written))
- return false;
- bytes_written += local_bytes_written;
- if (!write_decoded_headers(entry->value.start(), entry->value.length(),
- decoded_header_buffer + bytes_written, decoded_header_length - bytes_written,
- local_bytes_written))
- return false;
- bytes_written += local_bytes_written;
- if (!write_decoded_headers((const uint8_t*) "\r\n", 2, decoded_header_buffer +
- bytes_written, decoded_header_length - bytes_written, local_bytes_written))
- return false;
- bytes_written += local_bytes_written;
- }
+ if (!write_header_part(name, (const uint8_t*)": ", 2, decoded_header_buffer,
+ decoded_header_length, partial_bytes_written))
+ return false;
+ bytes_written += partial_bytes_written;
+ if (!write_header_part(value, (const uint8_t*)"\r\n", 2, decoded_header_buffer + bytes_written,
+ decoded_header_length - bytes_written, partial_bytes_written))
+ return false;
+ bytes_written += partial_bytes_written;
+ return true;
+}
+
+bool Http2HpackDecoder::write_header_part(Field& header, const uint8_t* suffix,
+ uint32_t suffix_length, uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
+ uint32_t& bytes_written)
+{
+ bytes_written = 0;
+ uint32_t partial_bytes_written;
+
+ if (!write_decoded_headers(header.start(), header.length(), decoded_header_buffer,
+ decoded_header_length, partial_bytes_written))
+ return false;
+ bytes_written += partial_bytes_written;
+ if (!write_decoded_headers(suffix, suffix_length, decoded_header_buffer + bytes_written,
+ decoded_header_length - bytes_written, partial_bytes_written))
+ return false;
+ bytes_written += partial_bytes_written;
return true;
}
bool Http2HpackDecoder::handle_dynamic_size_update(const uint8_t* encoded_header_buffer,
- const uint32_t encoded_header_length, const Http2HpackIntDecode &decode_int,
- uint32_t &bytes_consumed, uint32_t &bytes_written)
+ const uint32_t encoded_header_length, uint32_t &bytes_consumed)
{
uint64_t decoded_int;
uint32_t encoded_bytes_consumed;
bytes_consumed = 0;
- bytes_written = 0;
- if (!decode_int.translate(encoded_header_buffer, encoded_header_length,
+ if (!decode_int5.translate(encoded_header_buffer, encoded_header_length,
encoded_bytes_consumed, decoded_int, events, infractions))
{
return false;
const uint32_t encoded_header_length, uint32_t& bytes_consumed, uint8_t* decoded_header_buffer,
const uint32_t decoded_header_length, uint32_t& bytes_written)
{
+ const static uint8_t DYN_TABLE_SIZE_UPDATE_MASK = 0xe0;
+ const static uint8_t DYN_TABLE_SIZE_UPDATE_PATTERN = 0x20;
const static uint8_t INDEX_MASK = 0x80;
const static uint8_t LITERAL_INDEX_MASK = 0x40;
const static uint8_t LITERAL_INDEX_NAME_INDEX_MASK = 0x3f;
- const static uint8_t LITERAL_NO_INDEX_MASK = 0xf0;
- const static uint8_t LITERAL_NEVER_INDEX_PATTERN = 0x10;
const static uint8_t LITERAL_NO_INDEX_NAME_INDEX_MASK = 0x0f;
+ Field name, value;
+ bytes_consumed = bytes_written = 0;
+ bool ret;
+
+ if ((encoded_header_buffer[0] & DYN_TABLE_SIZE_UPDATE_MASK) == DYN_TABLE_SIZE_UPDATE_PATTERN)
+ return handle_dynamic_size_update(encoded_header_buffer,
+ encoded_header_length, bytes_consumed);
+
+ table_size_update_allowed = false;
+
// Indexed header representation
if (encoded_header_buffer[0] & INDEX_MASK)
- return decode_indexed_header(encoded_header_buffer,
+ ret = decode_indexed_header(encoded_header_buffer,
encoded_header_length, decode_int7, bytes_consumed,
- decoded_header_buffer, decoded_header_length, bytes_written);
+ decoded_header_buffer, decoded_header_length, bytes_written, name, value);
// Literal header representation to be added to dynamic table
else if (encoded_header_buffer[0] & LITERAL_INDEX_MASK)
- return decode_literal_header_line(encoded_header_buffer, encoded_header_length,
+ ret = decode_literal_header_line(encoded_header_buffer, encoded_header_length,
LITERAL_INDEX_NAME_INDEX_MASK, decode_int6, true, bytes_consumed, decoded_header_buffer,
- decoded_header_length, bytes_written);
+ decoded_header_length, bytes_written, name, value);
// Literal header field representation not to be added to dynamic table
// Note that this includes two representation types from the RFC - literal without index and
// literal never index. From a decoding standpoint these are identical.
- else if ((encoded_header_buffer[0] & LITERAL_NO_INDEX_MASK) == 0 or
- (encoded_header_buffer[0] & LITERAL_NO_INDEX_MASK) == LITERAL_NEVER_INDEX_PATTERN)
- return decode_literal_header_line(encoded_header_buffer, encoded_header_length,
- LITERAL_NO_INDEX_NAME_INDEX_MASK, decode_int4, false, bytes_consumed,
- decoded_header_buffer, decoded_header_length, bytes_written);
else
- return handle_dynamic_size_update(encoded_header_buffer,
- encoded_header_length, decode_int5, bytes_consumed, bytes_written);
+ ret = decode_literal_header_line(encoded_header_buffer, encoded_header_length,
+ LITERAL_NO_INDEX_NAME_INDEX_MASK, decode_int4, false, bytes_consumed,
+ decoded_header_buffer, decoded_header_length, bytes_written, name, value);
+
+ // Handle pseudoheaders
+ if (ret and bytes_written > 0)
+ {
+ if (decoded_header_buffer[0] == ':')
+ {
+ if (pseudo_headers_allowed)
+ start_line->process_pseudo_header(name, value);
+ else
+ {
+ if (is_trailers)
+ {
+ *infractions += INF_PSEUDO_HEADER_IN_TRAILERS;
+ events->create_event(EVENT_PSEUDO_HEADER_IN_TRAILERS);
+ }
+ else
+ {
+ *infractions += INF_PSEUDO_HEADER_AFTER_REGULAR_HEADER;
+ events->create_event(EVENT_PSEUDO_HEADER_AFTER_REGULAR_HEADER);
+ }
+ }
+ bytes_written = 0;
+ }
+ else if (pseudo_headers_allowed)
+ pseudo_headers_allowed = false;
+ }
+ return ret;
}
// Entry point to decode an HPACK-encoded header block. This function returns true on successful
// decode and false on an unrecoverable decode error. Note that alerts may still be generated for
// recoverable errors while the function returns true. This function performs all decoding, but does
// not output the start line or decoded headers - this function must be followed by calls to
-// get_start_line() and get_decoded_headers() to generate and obtain these fields.
+// generate_start_line() and get_decoded_headers() to generate and obtain these fields.
bool Http2HpackDecoder::decode_headers(const uint8_t* encoded_headers,
const uint32_t encoded_headers_length, uint8_t* decoded_headers,
- Http2StartLine *start_line_generator)
+ Http2StartLine *start_line_generator, bool trailers)
{
uint32_t total_bytes_consumed = 0;
uint32_t line_bytes_consumed = 0;
bool success = true;
start_line = start_line_generator;
decoded_headers_size = 0;
- pseudo_headers_fragment_size = 0;
decode_error = false;
+ is_trailers = trailers;
+ pseudo_headers_allowed = !is_trailers;
// A maximum of two table size updates are allowed, and must be at the start of the header block
table_size_update_allowed = true;
decoded_headers_size += line_bytes_written;
}
- // If there were only pseudo-headers, finalize never got called, so create the start-line
- if (!start_line->is_finalized())
- success &= finalize_start_line();
-
- /* Write the last CRLF to end the header
-
- Adding artificial chunked header to end of HTTP/1.1 decoded header block for H2I to communicate
- frame boundaries to http_inspect and http_inspect can expect chunked data during inspection */
+ // Write the last CRLF to end the header
if (success)
{
success = write_decoded_headers((const uint8_t*)"\r\n", 2, decoded_headers +
return success;
}
-bool Http2HpackDecoder::finalize_start_line()
-{
- // Save the current position in the decoded buffer so we can set the pointer to the start
- // of the regular headers
- pseudo_headers_fragment_size = decoded_headers_size;
-
- return start_line->finalize();
-}
-
-const Field* Http2HpackDecoder::get_start_line()
-{
- return start_line->get_start_line();
-}
-
const Field* Http2HpackDecoder::get_decoded_headers(const uint8_t* const decoded_headers)
{
if (decode_error)
return new Field(STAT_NO_SOURCE);
else
- return new Field(decoded_headers_size - pseudo_headers_fragment_size, decoded_headers +
- pseudo_headers_fragment_size, false);
+ return new Field(decoded_headers_size, decoded_headers, false);
}
Http2EventGen* const _events, Http2Infractions* const _infractions) :
events(_events), infractions(_infractions), decode_table(flow_data, src_id) { }
bool decode_headers(const uint8_t* encoded_headers, const uint32_t encoded_headers_length,
- uint8_t* decoded_headers, Http2StartLine* start_line);
+ uint8_t* decoded_headers, Http2StartLine* start_line, bool trailers);
bool write_decoded_headers(const uint8_t* in_buffer, const uint32_t in_length,
uint8_t* decoded_header_buffer, uint32_t decoded_header_length, uint32_t& bytes_written);
bool decode_header_line(const uint8_t* encoded_header_buffer,
const uint32_t encoded_header_length, uint32_t& bytes_consumed,
uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
uint32_t& bytes_written);
- bool decode_literal_header_line(const uint8_t* encoded_header_buffer,
- const uint32_t encoded_header_length, const uint8_t name_index_mask,
- const Http2HpackIntDecode& decode_int, bool with_indexing, uint32_t& bytes_consumed,
+ bool handle_dynamic_size_update(const uint8_t* encoded_header_buffer,
+ const uint32_t encoded_header_length, uint32_t& bytes_consumed);
+ const HpackTableEntry* get_hpack_table_entry(const uint8_t* encoded_header_buffer,
+ const uint32_t encoded_header_length, const Http2HpackIntDecode& decode_int,
+ uint32_t& bytes_consumed);
+ bool write_header_part(Field& header, const uint8_t* suffix, uint32_t suffix_length,
uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
uint32_t& bytes_written);
- bool decode_string_literal(const uint8_t* encoded_header_buffer,
- const uint32_t encoded_header_length, uint32_t &bytes_consumed,
- uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
- uint32_t &bytes_written, Field& field);
bool decode_indexed_name(const uint8_t* encoded_header_buffer,
const uint32_t encoded_header_length, const Http2HpackIntDecode& decode_int,
- uint32_t& bytes_consumed, uint8_t* decoded_header_buffer,
- const uint32_t decoded_header_length, uint32_t& bytes_written, Field& name);
+ uint32_t& bytes_consumed, uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
+ uint32_t& bytes_written, Field& name);
+ bool decode_literal_header_line(const uint8_t* encoded_header_buffer,
+ const uint32_t encoded_header_length, const uint8_t name_index_mask,
+ const Http2HpackIntDecode& decode_int, bool with_indexing, uint32_t& bytes_consumed,
+ uint8_t* decoded_header_buffer, const uint32_t decoded_header_length, uint32_t& bytes_written,
+ Field& name, Field& value);
bool decode_indexed_header(const uint8_t* encoded_header_buffer,
const uint32_t encoded_header_length, const Http2HpackIntDecode& decode_int,
- uint32_t& bytes_consumed, uint8_t* decoded_header_buffer,
- const uint32_t decoded_header_length, uint32_t& bytes_written);
- bool handle_dynamic_size_update(const uint8_t* encoded_header_buffer,
- const uint32_t encoded_header_length, const Http2HpackIntDecode &decode_int,
- uint32_t& bytes_consumed, uint32_t& bytes_written);
+ uint32_t &bytes_consumed, uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
+ uint32_t& bytes_written, Field& name, Field& value);
+ bool decode_string_literal(const uint8_t* encoded_header_buffer,
+ const uint32_t encoded_header_length, uint32_t& bytes_consumed,
+ uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
+ uint32_t& bytes_written, Field& field);
+
bool finalize_start_line();
const Field* get_start_line();
const Field* get_decoded_headers(const uint8_t* const decoded_headers);
private:
Http2StartLine* start_line;
+ bool pseudo_headers_allowed;
uint32_t decoded_headers_size;
- uint32_t pseudo_headers_fragment_size;
bool decode_error;
Http2EventGen* const events;
Http2Infractions* const infractions;
HpackIndexTable decode_table;
bool table_size_update_allowed = true;
uint8_t num_table_size_updates = 0;
+ bool is_trailers = false;
};
#endif
{
public:
// FIXIT-P This array can be optimized to start smaller and grow on demand
- HpackDynamicTable() : circular_buf(4096, nullptr) {}
+ HpackDynamicTable() : circular_buf(ARRAY_CAPACITY, nullptr) {}
~HpackDynamicTable();
const HpackTableEntry* get_entry(uint32_t index) const;
bool add_entry(const Field& name, const Field& value);
const char* Http2RequestLine::OPTIONS = "OPTIONS";
const char* Http2RequestLine::CONNECT = "CONNECT";
-void Http2RequestLine::process_pseudo_header_name(const uint8_t* const& name, uint32_t length)
+void Http2RequestLine::process_pseudo_header(const Field& name, const Field& value)
{
- process_pseudo_header_precheck();
-
- if (length == AUTHORITY_NAME_LENGTH and memcmp(name, AUTHORITY_NAME, length) == 0 and
- authority.length() <= 0)
- value_coming = AUTHORITY;
- else if (length == METHOD_NAME_LENGTH and memcmp(name, METHOD_NAME, length) == 0 and
- method.length() <= 0)
- value_coming = METHOD;
- else if (length == PATH_NAME_LENGTH and memcmp(name, PATH_NAME, length) == 0 and
- path.length() <= 0)
- value_coming = PATH;
- else if (length == SCHEME_NAME_LENGTH and memcmp(name, SCHEME_NAME, length) == 0 and
- scheme.length() <= 0)
- value_coming = SCHEME;
- else
+ Field *field;
+ if ((name.length() == AUTHORITY_NAME_LENGTH) and
+ (memcmp(name.start(), AUTHORITY_NAME, name.length()) == 0) and (authority.length() <= 0))
{
- *infractions += INF_INVALID_PSEUDO_HEADER;
- events->create_event(EVENT_INVALID_HEADER);
- value_coming = HEADER__INVALID;
+ field = &authority;
}
-}
-
-void Http2RequestLine::process_pseudo_header_value(const uint8_t* const& value, const uint32_t length)
-{
- switch (value_coming)
+ else if ((name.length() == METHOD_NAME_LENGTH) and
+ (memcmp(name.start(), METHOD_NAME, name.length()) == 0) and (method.length() <= 0))
+ {
+ field = &method;
+ }
+ else if ((name.length() == PATH_NAME_LENGTH) and
+ (memcmp(name.start(), PATH_NAME, name.length()) == 0) and (path.length() <= 0))
{
- case AUTHORITY:
- authority.set(length, value);
- break;
- case METHOD:
- method.set(length, value);
- break;
- case PATH:
- path.set(length, value);
- break;
- case SCHEME:
- scheme.set(length, value);
- break;
- default:
- // ignore invalid pseudo-header value - alert generated in process_pseudo_header_name
- break;
+ field = &path;
}
- value_coming = HEADER__NONE;
+ else if ((name.length() == SCHEME_NAME_LENGTH) and
+ (memcmp(name.start(), SCHEME_NAME, name.length()) == 0) and (scheme.length() <= 0))
+ {
+ field = &scheme;
+ }
+ else
+ {
+ *infractions += INF_INVALID_PSEUDO_HEADER;
+ events->create_event(EVENT_INVALID_PSEUDO_HEADER);
+ return;
+ }
+ uint8_t* value_str = new uint8_t[value.length()];
+ memcpy(value_str, value.start(), value.length());
+ field->set(value.length(), value_str, true);
}
// This is called on the first non-pseudo-header. Select the appropriate URI form based on the
// provided pseudo-headers and generate the start line
-bool Http2RequestLine::generate_start_line()
+bool Http2RequestLine::generate_start_line(const Field*& start_line)
{
uint32_t bytes_written = 0;
bytes_written += 2;
assert(bytes_written == start_line_length);
+ start_line = new Field(start_line_length, start_line_buffer, false);
+
return true;
}
class Http2RequestLine : public Http2StartLine
{
public:
- 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;
+ void process_pseudo_header(const Field& name, const Field& value) override;
+ bool generate_start_line(const Field*& start_line) override;
friend Http2StartLine* Http2StartLine::new_start_line_generator(HttpCommon::SourceId source_id,
Http2EventGen* const events, Http2Infractions* const infractions);
return new Http2StatusLine(events, infractions);
}
-void Http2StartLine::process_pseudo_header_precheck()
-{
- if (finalized)
- {
- *infractions += INF_PSEUDO_HEADER_AFTER_REGULAR_HEADER;
- events->create_event(EVENT_INVALID_HEADER);
- }
-}
-
-bool Http2StartLine::finalize()
-{
- finalized = true;
- return generate_start_line();
-}
-
-const Field* Http2StartLine::get_start_line()
-{
- return new Field(start_line_length, start_line_buffer, false);
-}
friend class Http2Hpack;
- const Field* get_start_line();
- 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_name(const uint8_t* const& name) { return name[0] == ':'; }
+ virtual bool generate_start_line(const Field*& start_line) = 0;
+ virtual void process_pseudo_header(const Field& name, const Field& value) = 0;
protected:
Http2StartLine(Http2EventGen* const events, Http2Infractions* const infractions) :
events(events), infractions(infractions) { }
- void process_pseudo_header_precheck();
- virtual bool generate_start_line() = 0;
-
Http2EventGen* const events;
Http2Infractions* const infractions;
- bool finalized = false;
uint32_t start_line_length = 0;
uint8_t *start_line_buffer = nullptr;
- 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;
const char* Http2StatusLine::STATUS_NAME = ":status";
-void Http2StatusLine::process_pseudo_header_name(const uint8_t* const& name, uint32_t length)
+void Http2StatusLine::process_pseudo_header(const Field& name, const Field& value)
{
- process_pseudo_header_precheck();
-
- if (length == STATUS_NAME_LENGTH and memcmp(name, STATUS_NAME, length) == 0 and
- status.length() <= 0)
- value_coming = STATUS;
+ if ((name.length() == STATUS_NAME_LENGTH) and
+ (memcmp(name.start(), STATUS_NAME, name.length()) == 0) and (status.length() <= 0))
+ {
+ uint8_t* value_str = new uint8_t[value.length()];
+ memcpy(value_str, value.start(), value.length());
+ status.set(value.length(), value_str, true);
+ }
else
{
*infractions += INF_INVALID_PSEUDO_HEADER;
- events->create_event(EVENT_INVALID_HEADER);
- value_coming = HEADER__INVALID;
+ events->create_event(EVENT_INVALID_PSEUDO_HEADER);
}
}
-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()
+bool Http2StatusLine::generate_start_line(const Field*& start_line)
{
uint32_t bytes_written = 0;
bytes_written += 2;
assert(bytes_written == start_line_length);
+ start_line = new Field(start_line_length, start_line_buffer, false);
+
return true;
}
class Http2StatusLine : public Http2StartLine
{
public:
- 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;
+ void process_pseudo_header(const Field& name, const Field& value) override;
+ bool generate_start_line(const Field*& start_line) override;
friend Http2StartLine* Http2StartLine::new_start_line_generator(HttpCommon::SourceId source_id,
Http2EventGen* const events, Http2Infractions* const infractions);
return (state[source_id] == STATE_OPEN) || (state[source_id] == STATE_OPEN_DATA);
}
-void Http2Stream::finish_msg_body(HttpCommon::SourceId source_id, bool expect_trailers)
+void Http2Stream::finish_msg_body(HttpCommon::SourceId source_id, bool expect_trailers,
+ bool clear_partial_buffer)
{
uint32_t http_flush_offset = 0;
Http2DummyPacket dummy_pkt;
uint32_t unused = 0;
const H2BodyState body_state = expect_trailers ?
H2_BODY_COMPLETE_EXPECT_TRAILERS : H2_BODY_COMPLETE;
- get_hi_flow_data()->finish_h2_body(source_id, body_state);
+ get_hi_flow_data()->finish_h2_body(source_id, body_state, clear_partial_buffer);
const snort::StreamSplitter::Status scan_result = session_data->hi_ss[source_id]->scan(
&dummy_pkt, nullptr, 0, unused, &http_flush_offset);
assert(scan_result == snort::StreamSplitter::FLUSH);
{ end_stream_on_data_flush[source_id] = true; }
bool is_end_stream_on_data_flush(HttpCommon::SourceId source_id)
{ return end_stream_on_data_flush[source_id]; }
- void finish_msg_body(HttpCommon::SourceId source_id, bool expect_trailers = false);
+ void finish_msg_body(HttpCommon::SourceId source_id, bool expect_trailers,
+ bool clear_partial_buffer);
#ifdef REG_TEST
void print_frame(FILE* output);
if ((type == FT_HEADERS) and
(session_data->current_stream[source_id]) == next_stream)
{
- stream->finish_msg_body(source_id, true);
+ stream->finish_msg_body(source_id, true, false);
}
session_data->stream_in_hi = NO_STREAM_ID;
return StreamSplitter::FLUSH;
{ EVENT_DYNAMIC_TABLE_OVERFLOW, "HTTP/2 dynamic table size limit exceeded" },
{ EVENT_INVALID_STARTLINE, "invalid HTTP/2 start line" },
{ EVENT_PADDING_LEN, "HTTP/2 padding length is bigger than frame data size" },
+ { EVENT_PSEUDO_HEADER_AFTER_REGULAR_HEADER, "HTTP/2 pseudo-header after regular header" },
+ { EVENT_PSEUDO_HEADER_IN_TRAILERS, "HTTP/2 pseudo-header in trailers" },
+ { EVENT_INVALID_PSEUDO_HEADER, "invalid HTTP/2 pseudo-header" },
{ 0, nullptr }
};
return memory_usage_estimate;
}
-void HttpFlowData::finish_h2_body(HttpCommon::SourceId source_id, HttpEnums::H2BodyState state)
+void HttpFlowData::finish_h2_body(HttpCommon::SourceId source_id, HttpEnums::H2BodyState state,
+ bool clear_partial_buffer)
{
assert(h2_body_state[source_id] == H2_BODY_NOT_COMPLETE);
h2_body_state[source_id] = state;
partial_flush[source_id] = false;
+ if (clear_partial_buffer)
+ {
+ // We've already sent all data through detection so no need to reinspect. Just need to
+ // prep for trailers
+ partial_buffer_length[source_id] = 0;
+ delete[] partial_buffer[source_id];
+ partial_buffer[source_id] = nullptr;
+ partial_inspected_octets[source_id] = 0;
+ partial_detect_length[source_id] = 0;
+ delete[] partial_detect_buffer[source_id];
+ partial_detect_buffer[source_id] = nullptr;
+ }
}
#ifdef REG_TEST
HttpEnums::SectionType get_type_expected(HttpCommon::SourceId source_id) const
{ return type_expected[source_id]; }
- void finish_h2_body(HttpCommon::SourceId source_id, HttpEnums::H2BodyState state);
+ void finish_h2_body(HttpCommon::SourceId source_id, HttpEnums::H2BodyState state,
+ bool clear_partial_buffer);
void reset_partial_flush(HttpCommon::SourceId source_id)
{ partial_flush[source_id] = false; }