to your snort.lua configuration file.
Everything has a beginning and for http2_inspect this is the beginning of
-the beginning. Most of the protocol including HPACK decompression is not
-implemented yet.
+the beginning.
Currently http2_inspect will divide an HTTP/2 connection into individual
-frames and make them available for detection. Two new rule options are
-available for looking at HTTP/2 frames: http2_frame_header provides the
-9-octet frame header and http2_frame_data provides the frame content.
+frames. Two new rule options are available for looking at HTTP/2 frames:
+http2_frame_header provides the 9-octet frame header.
alert tcp any any -> any any (msg:"Frame type"; flow:established,
to_client; http2_frame_header; content:"|06|", offset 3, depth 1;
This will match if the Type byte of the frame header is 6 (PING).
- alert tcp any any -> any any ( msg:"Content of HTTP/2 frame";
- flow:established, to_client; http2_frame_data; content:"peppermint";
- sid:2; rev:1; )
-
-This will look for peppermint in the frame data but not the frame header.
-
-These can be combined:
-
- alert tcp any any -> any any ( msg:"Search in message bodies";
- flow:established, to_client;
- http2_frame_header; content:"|00|", offset 3, depth 1;
- http2_frame_data; content:"MaLwArE"; sid:3; rev:1; )
-
-Frame type 0 is DATA which carries the HTTP message body. This rule will
-search for MaLwArE inside an HTTP message body.
-
To smooth the transition to inspecting HTTP/2, rules that specify
service:http will be treated as if they also specify service:http2.
Thus:
"service http,http2;" if that is the desired behavior. Eventually
support for http implies http2 may be deprecated and removed.
-In the future, http2_inspect will support HPACK header decompression and
-be fully integrated with http_inspect to provide full inspection of the
-individual HTTP/1.1 streams.
+In the future, http2_inspect will be fully integrated with http_inspect to
+provide full inspection of the individual HTTP/1.1 streams.
set (FILE_LIST
http2_api.cc
http2_api.h
+ http2_data_frame.cc
+ http2_data_frame.h
+ http2_data_cutter.cc
+ http2_data_cutter.h
http2_enum.h
http2_flow_data.cc
http2_flow_data.h
};
extern const BaseApi* ips_http2_frame_header;
-extern const BaseApi* ips_http2_frame_data;
extern const BaseApi* ips_http2_decoded_header;
#ifdef BUILDING_SO
{
&Http2Api::http2_api.base,
ips_http2_frame_header,
- ips_http2_frame_data,
ips_http2_decoded_header,
nullptr
};
--- /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_data_cutter.cc author Maya Dagon <mdagon@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "http2_data_cutter.h"
+
+#include "service_inspectors/http_inspect/http_flow_data.h"
+#include "service_inspectors/http_inspect/http_stream_splitter.h"
+
+#include "http2_dummy_packet.h"
+
+using namespace snort;
+using namespace HttpCommon;
+using namespace Http2Enums;
+
+static std::string create_chunk_hdr(uint32_t len)
+{
+ std::stringstream stream;
+ stream<<std::hex<< len;
+ return stream.str() + "\r\n";
+}
+
+Http2DataCutter::Http2DataCutter(Http2FlowData* _session_data, uint32_t len,
+ HttpCommon::SourceId src_id, bool is_padded) :
+ session_data(_session_data), source_id(src_id), frame_length(len), data_len(len)
+{
+ data_state = (is_padded) ? PADDING_LENGTH : DATA;
+}
+
+// Scan data frame, extract information needed for http scan.
+// http scan will need the data only, stripped of padding and header.
+bool Http2DataCutter::http2_scan(const uint8_t* data, uint32_t length,
+ uint32_t* flush_offset)
+{
+ *flush_offset = cur_data_offset = cur_data = cur_padding = 0;
+
+ if (frame_bytes_seen == 0)
+ {
+ frame_bytes_seen = cur_data_offset = FRAME_HEADER_LENGTH;
+ length -= FRAME_HEADER_LENGTH;
+ *flush_offset = FRAME_HEADER_LENGTH;
+ }
+
+ uint32_t cur_pos = 0;
+
+ while ((cur_pos < length) && (data_state != FULL_FRAME))
+ {
+ switch (data_state)
+ {
+ case PADDING_LENGTH:
+ padding_len = *(data + cur_data_offset);
+
+ if (data_len <= padding_len)
+ {
+ *session_data->infractions[source_id] += INF_PADDING_LEN;
+ session_data->events[source_id]->create_event(EVENT_PADDING_LEN);
+ return false;
+ }
+ // FIXIT temporary - till multiple data frames sent to http
+ if (data_len == (padding_len + 1))
+ return false;
+ data_len -= (padding_len + 1);
+ data_state = DATA;
+ cur_pos++;
+ cur_data_offset++;
+ break;
+ case DATA:
+ {
+ const uint32_t missing = data_len - data_bytes_read;
+ cur_data = ((length - cur_pos) >= missing) ?
+ missing : (length - cur_pos);
+ data_bytes_read += cur_data;
+ cur_pos += cur_data;
+ if (data_bytes_read == data_len)
+ data_state = padding_len ? PADDING : FULL_FRAME;
+ break;
+ }
+ case PADDING:
+ {
+ const uint32_t missing = padding_len - padding_read;
+ cur_padding = ((length - cur_pos) >= missing) ?
+ missing : (length - cur_pos);
+ cur_pos += cur_padding;
+ padding_read += cur_padding;
+ if (padding_read == padding_len)
+ data_state = FULL_FRAME;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ frame_bytes_seen += cur_pos;
+ session_data->scan_remaining_frame_octets[source_id] = frame_length - frame_bytes_seen;
+ *flush_offset += cur_pos;
+
+ return true;
+}
+
+// Call http scan. Wrap data with chunk header and end of chunk.
+StreamSplitter::Status Http2DataCutter::http_scan(const uint8_t* data, uint32_t* flush_offset)
+{
+ StreamSplitter::Status scan_result = StreamSplitter::SEARCH;
+ uint32_t http_flush_offset = 0;
+ Http2DummyPacket dummy_pkt;
+ dummy_pkt.flow = session_data->flow;
+ uint32_t unused = 0;
+
+ // first phase supports only flush of full packet
+ switch (http_state)
+ {
+ case NONE_SENT:
+ {
+ if (cur_data)
+ {
+ std::string chunk_hdr = create_chunk_hdr(data_len);
+ scan_result = session_data->hi_ss[source_id]->scan(&dummy_pkt,
+ (const unsigned char*)chunk_hdr.c_str(),
+ chunk_hdr.length(), unused, &http_flush_offset);
+ bytes_sent_http += chunk_hdr.length();
+ http_state = HEADER_SENT;
+ if (scan_result != StreamSplitter::SEARCH)
+ return StreamSplitter::ABORT;
+ }
+ } // fallthrough
+ case HEADER_SENT:
+ {
+ if (cur_data)
+ {
+ scan_result = session_data->hi_ss[source_id]->scan(&dummy_pkt, data + cur_data_offset,
+ cur_data, unused, &http_flush_offset);
+ bytes_sent_http += cur_data;
+
+ if (scan_result != StreamSplitter::SEARCH)
+ return StreamSplitter::ABORT;
+ }
+ if (data_state == FULL_FRAME)
+ {
+ scan_result = session_data->hi_ss[source_id]->scan(&dummy_pkt, (const unsigned
+ char*)"\r\n0\r\n",
+ 5, unused, &http_flush_offset);
+ bytes_sent_http +=5;
+ assert(scan_result == StreamSplitter::FLUSH);
+
+ session_data->scan_octets_seen[source_id] = 0;
+ session_data->scan_remaining_frame_octets[source_id] = 0;
+ }
+ }
+ }
+
+ if (scan_result != StreamSplitter::FLUSH)
+ *flush_offset = 0;
+
+ return scan_result;
+}
+
+StreamSplitter::Status Http2DataCutter::scan(const uint8_t* data, uint32_t length,
+ uint32_t* flush_offset)
+{
+ if (!http2_scan(data, length, flush_offset))
+ return StreamSplitter::ABORT;
+
+ return Http2DataCutter::http_scan(data, flush_offset);
+}
+
+const StreamBuffer Http2DataCutter::reassemble(unsigned total, unsigned offset, const
+ uint8_t* data, unsigned len)
+{
+ StreamBuffer frame_buf { nullptr, 0 };
+
+ if (offset == 0)
+ {
+ padding_read = data_bytes_read = hdr_bytes_read = 0;
+ }
+ cur_data = cur_padding = cur_data_offset = 0;
+
+ unsigned cur_pos = 0;
+ while (cur_pos < len)
+ {
+ switch (reassemble_state)
+ {
+ case SKIP_FRAME_HDR:
+ {
+ if (hdr_bytes_read == 0)
+ {
+ session_data->frame_header[source_id] = new uint8_t[FRAME_HEADER_LENGTH];
+ session_data->frame_header_size[source_id] = FRAME_HEADER_LENGTH;
+ }
+ const uint32_t missing = FRAME_HEADER_LENGTH - hdr_bytes_read;
+ const uint32_t cur_frame = ((len - cur_pos) < missing) ? (len - cur_pos) : missing;
+ memcpy(session_data->frame_header[source_id] + hdr_bytes_read, data + cur_pos,
+ cur_frame);
+ hdr_bytes_read += cur_frame;
+ cur_pos += cur_frame;
+ if (hdr_bytes_read == FRAME_HEADER_LENGTH)
+ {
+ cur_data_offset = cur_pos;
+ reassemble_state = (padding_len) ? SKIP_PADDING_LEN : SEND_CHUNK_HDR;
+ }
+
+ break;
+ }
+ case SKIP_PADDING_LEN:
+ cur_pos++;
+ cur_data_offset++;
+ reassemble_state = SEND_CHUNK_HDR;
+ break;
+ case SEND_CHUNK_HDR:
+ {
+ std::string chunk_hdr = create_chunk_hdr(data_len);
+ unsigned copied;
+ session_data->hi_ss[source_id]->reassemble(session_data->flow,
+ bytes_sent_http, 0, (const uint8_t*)chunk_hdr.c_str(), chunk_hdr.length(), 0,
+ copied);
+ assert(copied == (unsigned)chunk_hdr.length());
+ reassemble_state = SEND_DATA;
+ } // fallthrough
+ case SEND_DATA:
+ {
+ const uint32_t missing = data_len - data_bytes_read;
+ cur_data = ((len - cur_pos) >= missing) ? missing : (len - cur_pos);
+ data_bytes_read += cur_data;
+ cur_pos += cur_data;
+
+ unsigned copied;
+ frame_buf = session_data->hi_ss[source_id]->reassemble(session_data->flow,
+ bytes_sent_http, 0, data + cur_data_offset, cur_data,
+ 0, copied);
+ assert(copied == (unsigned)cur_data);
+
+ if (data_bytes_read == data_len)
+ reassemble_state = (padding_len) ? SKIP_PADDING : SEND_CRLF;
+
+ break;
+ }
+ case SKIP_PADDING:
+ {
+ const uint32_t missing = padding_len - padding_read;
+ cur_padding = ((len - cur_pos) >= missing) ?
+ missing : (len - cur_pos);
+ cur_pos += cur_padding;
+ padding_read += cur_padding;
+ if (padding_read == padding_len)
+ reassemble_state = SEND_CRLF;
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ if (len + offset == total)
+ assert(reassemble_state == SEND_CRLF);
+
+ if (reassemble_state == SEND_CRLF)
+ {
+ unsigned copied;
+ frame_buf = session_data->hi_ss[source_id]->reassemble(session_data->flow,
+ bytes_sent_http, 0,(const unsigned char*)"\r\n0\r\n", 5, PKT_PDU_TAIL, copied);
+ assert(copied == 5);
+
+ assert(frame_buf.data != nullptr);
+ session_data->frame_data[source_id] = const_cast <uint8_t*>(frame_buf.data);
+ session_data->frame_data_size[source_id] = frame_buf.length;
+ }
+
+ return frame_buf;
+}
+
--- /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_data_cutter.h author Maya Dagon <mdagon@cisco.com>
+
+#ifndef HTTP2_DATA_CUTTER_H
+#define HTTP2_DATA_CUTTER_H
+
+#include "service_inspectors/http_inspect/http_common.h"
+#include "stream/stream_splitter.h"
+
+#include "http2_enum.h"
+#include "http2_flow_data.h"
+
+class Http2DataCutter
+{
+public:
+ Http2DataCutter(Http2FlowData* flow_data, uint32_t len, HttpCommon::SourceId
+ src_id, bool is_padded);
+ snort::StreamSplitter::Status scan(const uint8_t* data, uint32_t length,
+ uint32_t* flush_offset);
+ const snort::StreamBuffer reassemble(unsigned total, unsigned offset, const uint8_t* data,
+ unsigned len);
+
+private:
+
+ Http2FlowData* const session_data;
+ const HttpCommon::SourceId source_id;
+
+ // total per frame
+ const uint32_t frame_length;
+ uint32_t data_len;
+ uint32_t padding_len = 0;
+ // accumulating
+ uint32_t frame_bytes_seen = 0;
+ uint32_t bytes_sent_http = 0;
+ uint32_t hdr_bytes_read = 0;
+ uint32_t data_bytes_read = 0;
+ uint32_t padding_read = 0;
+ // per call
+ uint32_t cur_data;
+ uint32_t cur_padding;
+ uint32_t cur_data_offset;
+
+ //
+ // State machines
+ //
+
+ // data scan
+ enum DataState { PADDING_LENGTH, DATA, PADDING, FULL_FRAME };
+ enum DataState data_state;
+
+ // http scan
+ enum HttpScanState { NONE_SENT, HEADER_SENT };
+ enum HttpScanState http_state = NONE_SENT;
+
+ // reassemble
+ enum ReassembleState { SKIP_FRAME_HDR, SKIP_PADDING_LEN, SEND_CHUNK_HDR, SEND_DATA,
+ SKIP_PADDING, SEND_CRLF };
+ enum ReassembleState reassemble_state = SKIP_FRAME_HDR;
+
+ bool http2_scan(const uint8_t* data, uint32_t length, uint32_t* flush_offset);
+ snort::StreamSplitter::Status http_scan(const uint8_t* data, uint32_t* flush_offset);
+};
+
+#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_data_frame.cc author Maya Dagon <mdagon@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "http2_data_frame.h"
+
+#include "protocols/packet.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_flow_data.h"
+
+using namespace HttpCommon;
+using namespace snort;
+
+Http2DataFrame::Http2DataFrame(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_) :
+ Http2Frame(header_buffer, header_len, nullptr, 0, session_data_, source_id_)
+{
+ 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 = data_len;
+ dummy_pkt.data = data_buffer;
+ dummy_pkt.xtradata_mask = 0;
+ session_data->hi->eval(&dummy_pkt);
+ detection_required = dummy_pkt.is_detection_required();
+ xtradata_mask = dummy_pkt.xtradata_mask;
+}
+
+void Http2DataFrame::clear()
+{
+ Http2DummyPacket dummy_pkt;
+ dummy_pkt.flow = session_data->flow;
+ session_data->hi->clear(&dummy_pkt);
+}
+
--- /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_data_frame.h author Maya Dagon <mdagon@cisco.com>
+
+#ifndef HTTP2_DATA_FRAME_H
+#define HTTP2_DATA_FRAME_H
+
+#include "http2_frame.h"
+
+class Http2Frame;
+
+class Http2DataFrame : public Http2Frame
+{
+public:
+ ~Http2DataFrame() override {}
+ void clear() override;
+
+ uint32_t get_xtradata_mask() override { return xtradata_mask; }
+ bool is_detection_required() const override { return detection_required; }
+
+ friend Http2Frame* Http2Frame::new_frame(const uint8_t*, const int32_t, const uint8_t*,
+ const int32_t, Http2FlowData*, HttpCommon::SourceId);
+
+private:
+ Http2DataFrame(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);
+ uint32_t xtradata_mask = 0;
+ bool detection_required = false;
+};
+#endif
EVENT_FRAME_SEQUENCE = 13,
EVENT_DYNAMIC_TABLE_OVERFLOW = 14,
EVENT_INVALID_STARTLINE = 15,
+ EVENT_PADDING_LEN = 16,
EVENT__MAX_VALUE
};
INF_TOO_MANY_TABLE_SIZE_UPDATES = 24,
INF_INVALID_STARTLINE = 25,
INF_INVALID_HEADER = 26,
+ INF_PADDING_LEN = 27,
INF__MAX_VALUE
};
delete infractions[k];
delete events[k];
delete hi_ss[k];
+ delete[] frame_data[k];
+ delete[] frame_header[k];
}
}
void set_hi_msg_section(HttpMsgSection* section);
friend class Http2Frame;
+ friend class Http2DataFrame;
+ friend class Http2DataCutter;
friend class Http2HeadersFrame;
friend class Http2Hpack;
friend class Http2Inspect;
friend class Http2StatusLine;
friend class Http2Stream;
friend class Http2StreamSplitter;
+ friend snort::StreamSplitter::Status data_scan(Http2FlowData* session_data, const uint8_t* data,
+ uint32_t length, uint32_t* flush_offset, HttpCommon::SourceId source_id, uint32_t frame_length, bool is_padded);
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,
#include "http2_frame.h"
+#include "http2_data_frame.h"
#include "http2_enum.h"
#include "http2_flow_data.h"
#include "http2_headers_frame.h"
case FT_SETTINGS:
return new Http2SettingsFrame(header, header_len, data, data_len, session_data,
source_id);
+ case FT_DATA:
+ return new Http2DataFrame(header, header_len, data, data_len, session_data, source_id);
default:
return new Http2Frame(header, header_len, data, data_len, session_data, source_id);
}
start_line = hpack_decoder.get_start_line();
http1_header = hpack_decoder.get_decoded_headers(decoded_headers);
- if ((error_during_decode) || (session_data->hi_ss[source_id] == nullptr))
+ if (error_during_decode)
return;
// http_inspect scan() of start line
void Http2HeadersFrame::clear()
{
- if (error_during_decode || hi_abort || (session_data->hi == nullptr))
+ if (error_during_decode || hi_abort)
return;
Packet dummy_pkt(false);
dummy_pkt.flow = session_data->flow;
// FIXIT-H Workaround for unexpected eval() calls
// Avoid eval if scan/reassemble aborts
- if (session_data->frame_type[source_id] == FT__NONE)
+ if (session_data->frame_type[source_id] == FT__ABORT)
return;
Http2Stream* stream = session_data->get_current_stream(source_id);
#include "service_inspectors/http_inspect/http_flow_data.h"
+#include "http2_data_cutter.h"
+
using namespace HttpCommon;
Http2Stream::Http2Stream(uint32_t stream_id_, Http2FlowData* session_data_) :
{
delete current_frame;
delete hi_flow_data;
+ delete data_cutter[SRC_CLIENT];
+ delete data_cutter[SRC_SERVER];
}
void Http2Stream::eval_frame(const uint8_t* header_buffer, int32_t header_len,
}
#endif
+Http2DataCutter* Http2Stream::get_data_cutter(HttpCommon::SourceId source_id, uint32_t len, bool is_padded)
+{
+ if (!data_cutter[source_id])
+ data_cutter[source_id] = new Http2DataCutter(session_data, len, source_id, is_padded);
+ return data_cutter[source_id];
+}
#include "http2_frame.h"
-class HttpFlowData;
+class Http2DataCutter;
class Http2FlowData;
+class HttpFlowData;
class HttpMsgSection;
class Http2Stream
uint32_t get_xtradata_mask() { return (current_frame != nullptr) ?
current_frame->get_xtradata_mask() : 0; }
Http2Frame *get_current_frame() { return current_frame; }
+ Http2DataCutter* get_data_cutter(HttpCommon::SourceId source_id, uint32_t len=0, bool is_padded=false);
+ void set_data_cutter(Http2DataCutter* cutter, HttpCommon::SourceId source_id) { data_cutter[source_id] = cutter; }
+ bool get_abort_data_processing(HttpCommon::SourceId source_id) const { return abort_data_processing[source_id]; }
+ void set_abort_data_processing(HttpCommon::SourceId source_id) { abort_data_processing[source_id] = true; }
+
#ifdef REG_TEST
void print_frame(FILE* output);
#endif
Http2Frame* current_frame = nullptr;
HttpFlowData* hi_flow_data = nullptr;
HttpMsgSection* hi_msg_section = nullptr;
+ Http2DataCutter* data_cutter[2] = { nullptr, nullptr};
+ bool abort_data_processing[2] = {false, false};
};
#endif
pkt->flow->set_flow_data(session_data = new Http2FlowData(pkt->flow));
Http2Module::increment_peg_counts(PEG_FLOW);
}
-
- // General mechanism to abort using scan
- if (session_data->frame_type[source_id] == FT__ABORT)
- {
- session_data->frame_type[source_id] = FT__NONE;
- return HttpStreamSplitter::status_value(StreamSplitter::ABORT, true);
- }
-
+
#ifdef REG_TEST
uint32_t dummy_flush_offset;
}
#endif
+ // General mechanism to abort using scan
+ if (session_data->frame_type[source_id] == FT__ABORT)
+ return HttpStreamSplitter::status_value(StreamSplitter::ABORT, true);
+
const StreamSplitter::Status ret_val =
implement_scan(session_data, data, length, flush_offset, source_id);
+ if (ret_val == StreamSplitter::ABORT)
+ session_data->frame_type[source_id] = FT__ABORT;
+
#ifdef REG_TEST
if (HttpTestManager::use_test_input(HttpTestManager::IN_HTTP2))
if (ret_val == StreamSplitter::FLUSH)
#include "service_inspectors/http_inspect/http_common.h"
#include "service_inspectors/http_inspect/http_flow_data.h"
+#include "service_inspectors/http_inspect/http_stream_splitter.h"
#include "service_inspectors/http_inspect/http_test_input.h"
#include "service_inspectors/http_inspect/http_test_manager.h"
+#include "http2_data_cutter.h"
#include "http2_flow_data.h"
using namespace snort;
return frame_buffer[frame_type_index];
// If there was no frame header, this must be a piece of a long data frame
else
- return FT_DATA;
+ return FT_DATA;
}
static uint8_t get_frame_flags(const uint8_t* frame_buffer)
const uint8_t stream_id_index = 5;
assert(frame_buffer != nullptr);
return ((frame_buffer[stream_id_index] & 0x7f) << 24) +
- (frame_buffer[stream_id_index + 1] << 16) +
- (frame_buffer[stream_id_index + 2] << 8) +
- frame_buffer[stream_id_index + 3];
+ (frame_buffer[stream_id_index + 1] << 16) +
+ (frame_buffer[stream_id_index + 2] << 8) +
+ frame_buffer[stream_id_index + 3];
+}
+
+StreamSplitter::Status data_scan(Http2FlowData* session_data, const uint8_t* data,
+ uint32_t length, uint32_t* flush_offset, HttpCommon::SourceId source_id,
+ uint32_t frame_length, bool is_padded)
+{
+ Http2Stream* const stream = session_data->find_stream(session_data->current_stream[source_id]);
+ HttpFlowData* http_flow = nullptr;
+ if (stream)
+ {
+ http_flow = (HttpFlowData*)stream->get_hi_flow_data();
+ // temporary, till more than 1 data frame sent to http inspect is supported
+ if (stream->get_abort_data_processing(source_id))
+ return StreamSplitter::ABORT;
+ }
+
+ if (!stream || !http_flow || (frame_length > 0 and
+ (http_flow->get_type_expected(source_id) != HttpEnums::SEC_BODY_CHUNK)))
+ {
+ *session_data->infractions[source_id] += INF_FRAME_SEQUENCE;
+ session_data->events[source_id]->create_event(EVENT_FRAME_SEQUENCE);
+ return StreamSplitter::ABORT;
+ }
+
+ if (frame_length == 0 or frame_length > MAX_OCTETS)
+ return StreamSplitter::ABORT;
+
+ Http2DataCutter* data_cutter = stream->get_data_cutter(source_id, frame_length, is_padded);
+ return data_cutter->scan(data, length, flush_offset);
}
StreamSplitter::Status implement_scan(Http2FlowData* session_data, const uint8_t* data,
// Verify preface is correct, else generate loss of sync event and abort
switch (validate_preface(data, length, session_data->scan_octets_seen[source_id]))
{
- case V_GOOD:
- break;
- case V_BAD:
- session_data->events[source_id]->create_event(EVENT_PREFACE_MATCH_FAILURE);
- return StreamSplitter::ABORT;
- case V_TBD:
- session_data->scan_octets_seen[source_id] += length;
- return StreamSplitter::SEARCH;
+ case V_GOOD:
+ break;
+ case V_BAD:
+ session_data->events[source_id]->create_event(EVENT_PREFACE_MATCH_FAILURE);
+ return StreamSplitter::ABORT;
+ case V_TBD:
+ session_data->scan_octets_seen[source_id] += length;
+ return StreamSplitter::SEARCH;
}
*flush_offset = 24 - session_data->scan_octets_seen[source_id];
if (session_data->leftover_data[source_id] > DATA_SECTION_SIZE)
session_data->scan_remaining_frame_octets[source_id] = DATA_SECTION_SIZE;
else
- session_data->scan_remaining_frame_octets[source_id] =
- session_data->leftover_data[source_id];
+ session_data->scan_remaining_frame_octets[source_id] =
+ session_data->leftover_data[source_id];
session_data->total_bytes_in_split[source_id] = 0;
}
// We have the full frame header, compute some variables
const uint32_t frame_length = get_frame_length(session_data->
scan_frame_header[source_id]);
- const uint8_t type = get_frame_type(session_data->scan_frame_header[source_id]);
+ const uint8_t type = session_data->frame_type[source_id] = get_frame_type(
+ session_data->scan_frame_header[source_id]);
+ uint8_t frame_flags = get_frame_flags(session_data->
+ scan_frame_header[source_id]);
session_data->current_stream[source_id] =
get_stream_id(session_data->scan_frame_header[source_id]);
if (type == FT_DATA)
- {
- Http2Stream* const stream = session_data->find_stream(session_data->current_stream[source_id]);
- HttpFlowData* http_flow = nullptr;
- if (stream)
- http_flow = (HttpFlowData*)stream->get_hi_flow_data();
-
- if (!stream || !http_flow || (frame_length > 0 and
- (http_flow->get_type_expected(source_id) != HttpEnums::SEC_BODY_CHUNK)))
- {
- *session_data->infractions[source_id] += INF_FRAME_SEQUENCE;
- session_data->events[source_id]->create_event(EVENT_FRAME_SEQUENCE);
- status = StreamSplitter::ABORT;
- }
- }
+ return data_scan(session_data, data, length, flush_offset, source_id,
+ frame_length, ((frame_flags & PADDED) !=0));
// Compute frame section length once per frame
if (session_data->scan_remaining_frame_octets[source_id] == 0)
status = StreamSplitter::ABORT;
break;
}
- if ((type == FT_DATA) && (frame_length > DATA_SECTION_SIZE))
- {
- // Break up long data frames into pieces for detection
- session_data->scan_remaining_frame_octets[source_id] = DATA_SECTION_SIZE;
- session_data->total_bytes_in_split[source_id] = DATA_SECTION_SIZE +
- FRAME_HEADER_LENGTH;
- }
- else if (frame_length + FRAME_HEADER_LENGTH > MAX_OCTETS)
+
+ if (frame_length + FRAME_HEADER_LENGTH > MAX_OCTETS)
{
// FIXIT-M long non-data frame needs to be supported
status = StreamSplitter::ABORT;
}
// Have the full frame
- uint8_t frame_flags = get_frame_flags(session_data->scan_frame_header[source_id]);
- switch(type)
+ switch (type)
{
- case FT_HEADERS:
+ case FT_HEADERS:
+ if (!(frame_flags & END_HEADERS))
+ {
+ session_data->continuation_expected[source_id] = true;
+ status = StreamSplitter::SEARCH;
+ }
+ break;
+ case FT_CONTINUATION:
+ if (session_data->continuation_expected[source_id])
+ {
if (!(frame_flags & END_HEADERS))
- {
- session_data->continuation_expected[source_id] = true;
status = StreamSplitter::SEARCH;
- }
- break;
- case FT_CONTINUATION:
- if (session_data->continuation_expected[source_id])
- {
- if (!(frame_flags & END_HEADERS))
- status = StreamSplitter::SEARCH;
- else
- {
- // continuation frame ending headers
- status = StreamSplitter::FLUSH;
- session_data->continuation_expected[source_id] = false;
- }
- }
else
{
- // FIXIT-M CONTINUATION frames can also follow PUSH_PROMISE frames, which
- // are not currently supported
- *session_data->infractions[source_id] += INF_UNEXPECTED_CONTINUATION;
- session_data->events[source_id]->create_event(
- EVENT_UNEXPECTED_CONTINUATION);
- status = StreamSplitter::ABORT;
+ // continuation frame ending headers
+ status = StreamSplitter::FLUSH;
+ session_data->continuation_expected[source_id] = false;
}
- break;
- case FT_DATA:
- session_data->leftover_data[source_id] = frame_length -
- (session_data->total_bytes_in_split[source_id] - FRAME_HEADER_LENGTH);
- break;
+ }
+ else
+ {
+ // FIXIT-M CONTINUATION frames can also follow PUSH_PROMISE frames, which
+ // are not currently supported
+ *session_data->infractions[source_id] += INF_UNEXPECTED_CONTINUATION;
+ session_data->events[source_id]->create_event(
+ EVENT_UNEXPECTED_CONTINUATION);
+ status = StreamSplitter::ABORT;
+ }
+ break;
+ default:
+ break;
}
data_offset += session_data->scan_remaining_frame_octets[source_id];
*flush_offset = data_offset;
session_data->scan_octets_seen[source_id] = 0;
session_data->scan_remaining_frame_octets[source_id] = 0;
-
- } while (status == StreamSplitter::SEARCH && data_offset < length);
+ }
+ while (status == StreamSplitter::SEARCH && data_offset < length);
}
+
return status;
}
assert(offset+len <= total);
assert(total >= FRAME_HEADER_LENGTH);
assert(total <= MAX_OCTETS);
- assert(total == session_data->total_bytes_in_split[source_id]);
StreamBuffer frame_buf { nullptr, 0 };
- uint32_t data_offset = 0;
-
- if (offset == 0)
+ if (session_data->frame_type[source_id] == FT_DATA)
{
- // This is the first reassemble() for this frame and we need to allocate some buffers
- session_data->frame_header_size[source_id] = FRAME_HEADER_LENGTH *
- session_data->num_frame_headers[source_id];
- if (session_data->frame_header_size[source_id] > 0)
- session_data->frame_header[source_id] = new uint8_t[
- session_data->frame_header_size[source_id]];
-
- session_data->frame_data_size[source_id]= total -
- session_data->frame_header_size[source_id];
- if (session_data->frame_data_size[source_id] > 0)
- session_data->frame_data[source_id] = new uint8_t[
- session_data->frame_data_size[source_id]];
-
- session_data->frame_header_offset[source_id] = 0;
- session_data->frame_data_offset[source_id] = 0;
- session_data->remaining_frame_octets[source_id] =
- session_data->octets_before_first_header[source_id];
- session_data->padding_octets_in_frame[source_id] = 0;
+ Http2Stream* const stream = session_data->find_stream(
+ session_data->current_stream[source_id]);
+ Http2DataCutter* data_cutter = stream->get_data_cutter(source_id);
+ StreamBuffer http_frame_buf = data_cutter->reassemble(total, offset, data, len);
+ if (http_frame_buf.data)
+ {
+ delete data_cutter;
+ stream->set_data_cutter(nullptr, source_id);
+ stream->set_abort_data_processing(source_id);
+ session_data->frame_data[source_id] = const_cast<uint8_t*>(http_frame_buf.data);
+ session_data->frame_data_size[source_id] = http_frame_buf.length;
+ }
}
-
- do
+ else
{
- uint32_t octets_to_copy;
-
- // Read the padding length if necessary
- if (session_data->get_padding_len[source_id])
+ uint32_t data_offset = 0;
+
+ if (offset == 0)
{
- session_data->get_padding_len[source_id] = false;
- session_data->padding_octets_in_frame[source_id] = *(data + data_offset);
- data_offset += 1;
- session_data->remaining_frame_octets[source_id] -= 1;
- // Subtract the padding and padding length from the frame data size
- session_data->frame_data_size[source_id] -=
- (session_data->padding_octets_in_frame[source_id] + 1);
+ // This is the first reassemble() for this frame and we need to allocate some buffers
+ session_data->frame_header_size[source_id] = FRAME_HEADER_LENGTH *
+ session_data->num_frame_headers[source_id];
+ if (session_data->frame_header_size[source_id] > 0)
+ session_data->frame_header[source_id] = new uint8_t[
+ session_data->frame_header_size[source_id]];
+
+ session_data->frame_data_size[source_id]= total -
+ session_data->frame_header_size[source_id];
+ if (session_data->frame_data_size[source_id] > 0)
+ session_data->frame_data[source_id] = new uint8_t[
+ session_data->frame_data_size[source_id]];
+
+ session_data->frame_header_offset[source_id] = 0;
+ session_data->frame_data_offset[source_id] = 0;
+ session_data->remaining_frame_octets[source_id] =
+ session_data->octets_before_first_header[source_id];
+ session_data->padding_octets_in_frame[source_id] = 0;
}
- // Copy data into the frame buffer until we run out of data or reach the end of the current
- // frame's data
- const uint32_t remaining_frame_payload =
- session_data->remaining_frame_octets[source_id] -
- session_data->padding_octets_in_frame[source_id];
- octets_to_copy = remaining_frame_payload > len - data_offset ? len - data_offset :
- remaining_frame_payload;
- if (octets_to_copy > 0)
+ do
{
- memcpy(session_data->frame_data[source_id] + session_data->frame_data_offset[source_id],
- data + data_offset, octets_to_copy);
- }
- session_data->frame_data_offset[source_id] += octets_to_copy;
- session_data->remaining_frame_octets[source_id] -= octets_to_copy;
- data_offset += octets_to_copy;
+ uint32_t octets_to_copy;
- if (data_offset == len)
- break;
+ // Read the padding length if necessary
+ if (session_data->get_padding_len[source_id])
+ {
+ session_data->get_padding_len[source_id] = false;
+ session_data->padding_octets_in_frame[source_id] = *(data + data_offset);
+ data_offset += 1;
+ session_data->remaining_frame_octets[source_id] -= 1;
+ // Subtract the padding and padding length from the frame data size
+ session_data->frame_data_size[source_id] -=
+ (session_data->padding_octets_in_frame[source_id] + 1);
+ }
- // Skip over any padding
- uint32_t padding_bytes_to_skip = session_data->padding_octets_in_frame[source_id] >
- len - data_offset ? len - data_offset :
- session_data->padding_octets_in_frame[source_id];
- session_data->remaining_frame_octets[source_id] -= padding_bytes_to_skip;
- data_offset += padding_bytes_to_skip;
+ // Copy data into the frame buffer until we run out of data or reach the end of the
+ // current
+ // frame's data
+ const uint32_t remaining_frame_payload =
+ session_data->remaining_frame_octets[source_id] -
+ session_data->padding_octets_in_frame[source_id];
+ octets_to_copy = remaining_frame_payload > len - data_offset ? len - data_offset :
+ remaining_frame_payload;
+ if (octets_to_copy > 0)
+ {
+ memcpy(session_data->frame_data[source_id] +
+ session_data->frame_data_offset[source_id],
+ data + data_offset, octets_to_copy);
+ }
+ session_data->frame_data_offset[source_id] += octets_to_copy;
+ session_data->remaining_frame_octets[source_id] -= octets_to_copy;
+ data_offset += octets_to_copy;
- if (data_offset == len)
- break;
+ if (data_offset == len)
+ break;
- // Copy headers
- const uint32_t remaining_frame_header = FRAME_HEADER_LENGTH -
- (session_data->frame_header_offset[source_id] % FRAME_HEADER_LENGTH);
- octets_to_copy = remaining_frame_header > len - data_offset ? len - data_offset :
- remaining_frame_header;
- memcpy(session_data->frame_header[source_id] + session_data->frame_header_offset[source_id],
- data + data_offset, octets_to_copy);
- session_data->frame_header_offset[source_id] += octets_to_copy;
- data_offset += octets_to_copy;
-
- if (session_data->frame_header_offset[source_id] % FRAME_HEADER_LENGTH != 0)
- break;
+ // Skip over any padding
+ uint32_t padding_bytes_to_skip = session_data->padding_octets_in_frame[source_id] >
+ len - data_offset ? len - data_offset :
+ session_data->padding_octets_in_frame[source_id];
+ session_data->remaining_frame_octets[source_id] -= padding_bytes_to_skip;
+ data_offset += padding_bytes_to_skip;
- // If we just finished copying a header, parse and update frame variables
- session_data->remaining_frame_octets[source_id] =
- get_frame_length(session_data->frame_header[source_id] +
- session_data->frame_header_offset[source_id] - FRAME_HEADER_LENGTH);
+ if (data_offset == len)
+ break;
- uint8_t frame_flags = get_frame_flags(session_data->frame_header[source_id] +
- session_data->frame_header_offset[source_id] - FRAME_HEADER_LENGTH);
- if (frame_flags & PADDED)
- session_data->get_padding_len[source_id] = true;
- } while (data_offset < len);
+ // Copy headers
+ const uint32_t remaining_frame_header = FRAME_HEADER_LENGTH -
+ (session_data->frame_header_offset[source_id] % FRAME_HEADER_LENGTH);
+ octets_to_copy = remaining_frame_header > len - data_offset ? len - data_offset :
+ remaining_frame_header;
+ memcpy(session_data->frame_header[source_id] +
+ session_data->frame_header_offset[source_id],
+ data + data_offset, octets_to_copy);
+ session_data->frame_header_offset[source_id] += octets_to_copy;
+ data_offset += octets_to_copy;
+
+ if (session_data->frame_header_offset[source_id] % FRAME_HEADER_LENGTH != 0)
+ break;
+
+ // If we just finished copying a header, parse and update frame variables
+ session_data->remaining_frame_octets[source_id] =
+ get_frame_length(session_data->frame_header[source_id] +
+ session_data->frame_header_offset[source_id] - FRAME_HEADER_LENGTH);
+ uint8_t frame_flags = get_frame_flags(session_data->
+ scan_frame_header[source_id]);
+ if (frame_flags & PADDED)
+ session_data->get_padding_len[source_id] = true;
+ }
+ while (data_offset < len);
+ session_data->frame_type[source_id] = get_frame_type(
+ session_data->frame_header[source_id]);
+ }
if (flags & PKT_PDU_TAIL)
{
session_data->total_bytes_in_split[source_id] = 0;
// create pkt_data buffer
frame_buf.data = (const uint8_t*)"";
}
- session_data->frame_type[source_id] = get_frame_type(session_data->frame_header[source_id]);
return frame_buf;
}
{
const uint32_t preface_length = 24;
- static const uint8_t connection_prefix[] = {'P', 'R', 'I', ' ', '*', ' ', 'H', 'T', 'T', 'P',
- '/', '2', '.', '0', '\r', '\n', '\r', '\n', 'S', 'M', '\r', '\n', '\r', '\n'};
+ static const uint8_t connection_prefix[] = { 'P', 'R', 'I', ' ', '*', ' ', 'H', 'T', 'T', 'P',
+ '/', '2', '.', '0', '\r', '\n', '\r', '\n', 'S', 'M', '\r', '\n', '\r', '\n' };
assert(octets_seen < preface_length);
return V_GOOD;
}
+
{ EVENT_FRAME_SEQUENCE, "invalid HTTP/2 frame sequence" },
{ 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" },
{ 0, nullptr }
};
return MATCH;
}
-//-------------------------------------------------------------------------
-// http2_frame_data
-//-------------------------------------------------------------------------
-
-#undef IPS_OPT
-#define IPS_OPT "http2_frame_data"
-#undef IPS_HELP
-#define IPS_HELP "rule option to set detection cursor to the HTTP/2 frame body"
-
-static Module* frame_data_mod_ctor()
-{
- return new Http2CursorModule(IPS_OPT, IPS_HELP, HTTP2_BUFFER_FRAME_DATA, CAT_SET_OTHER,
- PSI_FRAME_DATA);
-}
-
-static const IpsApi frame_data_api =
-{
- {
- PT_IPS_OPTION,
- sizeof(IpsApi),
- IPSAPI_VERSION,
- 1,
- API_RESERVED,
- API_OPTIONS,
- IPS_OPT,
- IPS_HELP,
- frame_data_mod_ctor,
- Http2CursorModule::mod_dtor
- },
- OPT_TYPE_DETECTION,
- 0, PROTO_BIT__TCP,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- Http2IpsOption::opt_ctor,
- Http2IpsOption::opt_dtor,
- nullptr
-};
-
//-------------------------------------------------------------------------
// http2_frame_header
//-------------------------------------------------------------------------
// plugins
//-------------------------------------------------------------------------
-const BaseApi* ips_http2_frame_data = &frame_data_api.base;
const BaseApi* ips_http2_frame_header = &frame_header_api.base;
const BaseApi* ips_http2_decoded_header = &decoded_header_api.base;