http2_stream_splitter_impl.cc
http2_stream_splitter.h
http2_tables.cc
+ http2_utils.cc
+ http2_utils.h
ips_http2.cc
ips_http2.h
)
#include "service_inspectors/http_inspect/http_stream_splitter.h"
#include "http2_dummy_packet.h"
+#include "http2_utils.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;
-}
+Http2DataCutter::Http2DataCutter(Http2FlowData* _session_data, HttpCommon::SourceId src_id) :
+ session_data(_session_data), source_id(src_id)
+{ }
// 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)
+ uint32_t* flush_offset, uint32_t frame_len, uint8_t flags)
{
*flush_offset = cur_data_offset = cur_data = cur_padding = 0;
if (frame_bytes_seen == 0)
{
+ frame_length = data_len = frame_len;
+ padding_len = data_bytes_read = padding_read = 0;
+ frame_flags = flags;
frame_bytes_seen = cur_data_offset = FRAME_HEADER_LENGTH;
length -= FRAME_HEADER_LENGTH;
*flush_offset = FRAME_HEADER_LENGTH;
+ data_state = ((frame_flags & PADDED) !=0) ? PADDING_LENGTH : DATA;
}
uint32_t cur_pos = leftover_bytes;
return true;
}
-// Call http scan. Wrap data with chunk header and end of chunk.
+// Call http scan. After all data in first frame has been sent, set http2_end_stream flag and send
+// zero-length buffer to flush through detection
StreamSplitter::Status Http2DataCutter::http_scan(const uint8_t* data, uint32_t* flush_offset)
{
StreamSplitter::Status scan_result = StreamSplitter::SEARCH;
dummy_pkt.flow = session_data->flow;
uint32_t unused = 0;
- // first phase supports only flush of full packet
- switch (http_state)
+ if (cur_data || leftover_bytes)
{
- case NONE_SENT:
- {
- if (cur_data)
+ scan_result = session_data->hi_ss[source_id]->scan(&dummy_pkt, data + cur_data_offset,
+ cur_data + leftover_bytes, unused, &http_flush_offset);
+
+ if (scan_result == StreamSplitter::FLUSH)
{
- 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;
+ bytes_sent_http += http_flush_offset;
+ leftover_bytes = cur_data + leftover_bytes - http_flush_offset;
+ *flush_offset -= leftover_bytes;
+ session_data->mid_packet[source_id] = ( leftover_bytes > 0 ) ? true : false;
}
- } // fallthrough
- case HEADER_SENT:
- {
- if (cur_data || leftover_bytes)
+ else if (scan_result == StreamSplitter::SEARCH)
{
- scan_result = session_data->hi_ss[source_id]->scan(&dummy_pkt, data + cur_data_offset,
- cur_data + leftover_bytes, unused, &http_flush_offset);
-
- if (scan_result != StreamSplitter::SEARCH)
- {
- if (scan_result == StreamSplitter::FLUSH)
- {
- bytes_sent_http += http_flush_offset;
- leftover_bytes = cur_data + leftover_bytes - http_flush_offset;
- *flush_offset -= leftover_bytes;
- session_data->mid_packet[source_id] = true;
- return scan_result;
- }
- else
- return StreamSplitter::ABORT;
- }
-
bytes_sent_http += (cur_data + leftover_bytes);
leftover_bytes = 0;
}
- if (data_state == FULL_FRAME)
+ else if (scan_result == StreamSplitter::ABORT)
+ return StreamSplitter::ABORT;
+ }
+ if (data_state == FULL_FRAME)
+ {
+ if (leftover_bytes == 0)
{
- 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;
+ session_data->get_current_stream(source_id)->get_hi_flow_data()->
+ set_http2_end_stream(source_id);
+ scan_result = session_data->hi_ss[source_id]->scan(&dummy_pkt, nullptr, 0, unused,
+ &http_flush_offset);
assert(scan_result == StreamSplitter::FLUSH);
+
+ // FIXIT-H for now only a single data frame is processed
+ Http2Stream* const stream = session_data->find_stream(
+ session_data->current_stream[source_id]);
+ stream->set_abort_data_processing(source_id);
+
+ // Done with this frame, cleanup
session_data->mid_packet[source_id] = false;
session_data->scan_octets_seen[source_id] = 0;
session_data->scan_remaining_frame_octets[source_id] = 0;
+ frame_bytes_seen = 0;
}
- }
}
if (scan_result != StreamSplitter::FLUSH)
}
StreamSplitter::Status Http2DataCutter::scan(const uint8_t* data, uint32_t length,
- uint32_t* flush_offset)
+ uint32_t* flush_offset, uint32_t frame_len, uint8_t frame_flags)
{
- if (!http2_scan(data, length, flush_offset))
+ // FIXIT-H temporary, until more than 1 data frame sent to http inspect is supported
+ Http2Stream* const stream = session_data->find_stream(session_data->current_stream[source_id]);
+ if (stream->get_abort_data_processing(source_id))
+ return StreamSplitter::ABORT;
+
+ if (!http2_scan(data, length, flush_offset, frame_len, frame_flags))
return StreamSplitter::ABORT;
return http_scan(data, flush_offset);
{
switch (reassemble_state)
{
- case SKIP_FRAME_HDR:
+ case GET_FRAME_HDR:
{
if (reassemble_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;
+ padding_len = 0;
}
+
const uint32_t missing = FRAME_HEADER_LENGTH - reassemble_hdr_bytes_read;
const uint32_t cur_frame = ((len - cur_pos) < missing) ? (len - cur_pos) : missing;
memcpy(session_data->frame_header[source_id] + reassemble_hdr_bytes_read, data +
cur_frame);
reassemble_hdr_bytes_read += cur_frame;
cur_pos += cur_frame;
+
if (reassemble_hdr_bytes_read == FRAME_HEADER_LENGTH)
{
+ data_len = frame_length = get_frame_length(session_data->frame_header[source_id]);
+ frame_flags = get_frame_flags(session_data->frame_header[source_id]);
cur_data_offset = cur_pos;
- reassemble_state = (padding_len) ? SKIP_PADDING_LEN : SEND_CHUNK_HDR;
+ reassemble_state = ((frame_flags & PADDED) !=0) ? GET_PADDING_LEN : SEND_DATA;
}
break;
}
- case SKIP_PADDING_LEN:
+ case GET_PADDING_LEN:
+ padding_len = *(data + cur_pos);
+ data_len -= (padding_len + 1);
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_bytes_sent += copied;
reassemble_state = SEND_DATA;
- } // fallthrough
+ break;
case SEND_DATA:
{
const uint32_t missing = data_len - reassemble_data_bytes_read;
reassemble_bytes_sent += copied;
if (reassemble_data_bytes_read == data_len)
- reassemble_state = (padding_len) ? SKIP_PADDING : SEND_CRLF;
+ reassemble_state = (padding_len) ? SKIP_PADDING : CLEANUP;
break;
}
cur_pos += cur_padding;
reassemble_padding_read += cur_padding;
if (reassemble_padding_read == padding_len)
- reassemble_state = SEND_CRLF;
+ reassemble_state = CLEANUP;
break;
}
}
}
- if (reassemble_state == SEND_CRLF)
+ if (reassemble_state == CLEANUP)
{
- 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);
+ // Done with this packet
+ reassemble_state = GET_FRAME_HDR;
+ reassemble_hdr_bytes_read = reassemble_data_bytes_read = reassemble_padding_read = 0;
}
if (frame_buf.data != nullptr)
class Http2DataCutter
{
public:
- Http2DataCutter(Http2FlowData* flow_data, uint32_t len, HttpCommon::SourceId
- src_id, bool is_padded);
+ Http2DataCutter(Http2FlowData* flow_data, HttpCommon::SourceId src_id);
snort::StreamSplitter::Status scan(const uint8_t* data, uint32_t length,
- uint32_t* flush_offset);
+ uint32_t* flush_offset, uint32_t frame_len =0, uint8_t frame_flags =0);
const snort::StreamBuffer reassemble(unsigned total, const uint8_t* data,
unsigned len);
const HttpCommon::SourceId source_id;
// total per frame
- const uint32_t frame_length;
+ uint32_t frame_length;
uint32_t data_len;
uint32_t padding_len = 0;
+ uint8_t frame_flags;
// accumulating - scan
uint32_t frame_bytes_seen = 0;
uint32_t bytes_sent_http = 0;
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;
+ enum ReassembleState { GET_FRAME_HDR, GET_PADDING_LEN, SEND_DATA, SKIP_PADDING, CLEANUP };
+ enum ReassembleState reassemble_state = GET_FRAME_HDR;
- bool http2_scan(const uint8_t* data, uint32_t length, uint32_t* flush_offset);
+ bool http2_scan(const uint8_t* data, uint32_t length, uint32_t* flush_offset,
+ uint32_t frame_len, uint8_t frame_flags);
snort::StreamSplitter::Status http_scan(const uint8_t* data, uint32_t* flush_offset);
};
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);
+ uint32_t length, uint32_t* flush_offset, HttpCommon::SourceId source_id,
+ uint32_t frame_length, uint8_t frame_flags);
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,
if (get_flags() & PRIORITY)
hpack_headers_offset = 5;
- // No message body after stream bit is set
- bool no_message_body = (get_flags() & END_STREAM);
-
// Set up the decoding context
Http2HpackDecoder& hpack_decoder = session_data->hpack_decoder[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->events[source_id],
- session_data->infractions[source_id], no_message_body))
+ hpack_headers_offset, decoded_headers, start_line_generator,
+ session_data->events[source_id], session_data->infractions[source_id]))
{
session_data->frame_type[source_id] = FT__ABORT;
error_during_decode = true;
bool Http2HpackDecoder::decode_headers(const uint8_t* encoded_headers,
const uint32_t encoded_headers_length, uint8_t* decoded_headers,
Http2StartLine *start_line_generator, Http2EventGen* stream_events,
- Http2Infractions* stream_infractions, bool no_message_body)
+ Http2Infractions* stream_infractions)
{
uint32_t total_bytes_consumed = 0;
uint32_t line_bytes_consumed = 0;
frame boundaries to http_inspect and http_inspect can expect chunked data during inspection */
if (success)
{
- if (no_message_body)
- success = write_decoded_headers((const uint8_t*)"\r\n", 2, decoded_headers +
- decoded_headers_size, MAX_OCTETS - decoded_headers_size, line_bytes_written);
- else
- {
- const uint8_t chunk_hdr[] = "transfer-encoding: chunked\r\n\r\n";
- success = write_decoded_headers(chunk_hdr, sizeof(chunk_hdr) - 1, decoded_headers +
- decoded_headers_size, MAX_OCTETS - decoded_headers_size, line_bytes_written);
- }
+ success = write_decoded_headers((const uint8_t*)"\r\n", 2, decoded_headers +
+ decoded_headers_size, MAX_OCTETS - decoded_headers_size, line_bytes_written);
decoded_headers_size += line_bytes_written;
}
else
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,
- Http2EventGen* stream_events, Http2Infractions* stream_infractions, bool no_message_body);
+ Http2EventGen* stream_events, Http2Infractions* stream_infractions);
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,
}
#endif
-Http2DataCutter* Http2Stream::get_data_cutter(HttpCommon::SourceId source_id, uint32_t len, bool is_padded)
+Http2DataCutter* Http2Stream::get_data_cutter(HttpCommon::SourceId source_id)
{
if (!data_cutter[source_id])
- data_cutter[source_id] = new Http2DataCutter(session_data, len, source_id, is_padded);
+ data_cutter[source_id] = new Http2DataCutter(session_data, source_id);
return data_cutter[source_id];
}
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);
-
+
+ Http2DataCutter* get_data_cutter(HttpCommon::SourceId source_id);
void set_data_cutter(Http2DataCutter* cutter, HttpCommon::SourceId source_id)
{ data_cutter[source_id] = cutter; }
#include "http2_data_cutter.h"
#include "http2_flow_data.h"
+#include "http2_utils.h"
using namespace snort;
using namespace HttpCommon;
using namespace Http2Enums;
-static uint32_t get_frame_length(const uint8_t* frame_buffer)
-{
- return (frame_buffer[0] << 16) + (frame_buffer[1] << 8) + frame_buffer[2];
-}
-
-static uint8_t get_frame_type(const uint8_t* frame_buffer)
-{
- const uint8_t frame_type_index = 3;
- if (frame_buffer)
- 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;
-}
-
-static uint8_t get_frame_flags(const uint8_t* frame_buffer)
-{
- const uint8_t frame_flags_index = 4;
- if (frame_buffer)
- return frame_buffer[frame_flags_index];
- else
- return NO_HEADER;
-}
-
-static uint8_t get_stream_id(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];
-}
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)
+ uint32_t frame_length, uint8_t frame_flags)
{
Http2Stream* const stream = session_data->find_stream(session_data->current_stream[source_id]);
HttpFlowData* http_flow = nullptr;
}
if (!stream || !http_flow || (frame_length > 0 and
- (http_flow->get_type_expected(source_id) != HttpEnums::SEC_BODY_CHUNK)))
+ (http_flow->get_type_expected(source_id) != HttpEnums::SEC_BODY_H2)))
{
*session_data->infractions[source_id] += INF_FRAME_SEQUENCE;
session_data->events[source_id]->create_event(EVENT_FRAME_SEQUENCE);
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);
+ Http2DataCutter* data_cutter = stream->get_data_cutter(source_id);
+ return data_cutter->scan(data, length, flush_offset, frame_length, frame_flags);
}
StreamSplitter::Status non_data_scan(Http2FlowData* session_data,
if (type == FT_DATA)
return data_scan(session_data, data, length, flush_offset, source_id,
- frame_length, ((frame_flags & PADDED) !=0));
+ frame_length, frame_flags);
else
status = non_data_scan(session_data, length, flush_offset, source_id,
frame_length, type, frame_flags, data_offset);
HttpCommon::SourceId source_id)
{
assert(offset+len <= total);
- assert(total >= FRAME_HEADER_LENGTH);
assert(total <= MAX_OCTETS);
StreamBuffer frame_buf { nullptr, 0 };
StreamBuffer http_frame_buf = data_cutter->reassemble(total, data, len);
if (http_frame_buf.data)
{
- 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;
}
}
else
{
+ assert(total >= FRAME_HEADER_LENGTH);
uint32_t data_offset = 0;
if (offset == 0)
--- /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_utils.cc author Maya Dagon <mdagon@cisco.com>
+
+#include "http2_utils.h"
+
+#include <cassert>
+
+#include "http2_enum.h"
+
+using namespace Http2Enums;
+
+uint32_t get_frame_length(const uint8_t* frame_header_buffer)
+{
+ return (frame_header_buffer[0] << 16) + (frame_header_buffer[1] << 8) + frame_header_buffer[2];
+}
+
+uint8_t get_frame_type(const uint8_t* frame_header_buffer)
+{
+ const uint8_t frame_type_index = 3;
+ if (frame_header_buffer)
+ return frame_header_buffer[frame_type_index];
+ // If there was no frame header, this must be a piece of a long data frame
+ else
+ return FT_DATA;
+}
+
+uint8_t get_frame_flags(const uint8_t* frame_header_buffer)
+{
+ const uint8_t frame_flags_index = 4;
+ if (frame_header_buffer)
+ return frame_header_buffer[frame_flags_index];
+ else
+ return NO_HEADER;
+}
+
+uint8_t get_stream_id(const uint8_t* frame_header_buffer)
+{
+ const uint8_t stream_id_index = 5;
+ assert(frame_header_buffer != nullptr);
+ return ((frame_header_buffer[stream_id_index] & 0x7f) << 24) +
+ (frame_header_buffer[stream_id_index + 1] << 16) +
+ (frame_header_buffer[stream_id_index + 2] << 8) +
+ frame_header_buffer[stream_id_index + 3];
+}
--- /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_utils.h author Maya Dagon <mdagon@cisco.com>
+
+#ifndef HTTP2_UTILS_H
+#define HTTP2_UTILS_H
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "main/snort_types.h"
+
+// Frame header parsing utils.
+// Assumption is that if input isn't null, it contains full frame header
+
+uint32_t get_frame_length(const uint8_t* frame_header_buffer);
+uint8_t get_frame_type(const uint8_t* frame_header_buffer);
+uint8_t get_frame_flags(const uint8_t* frame_header_buffer);
+uint8_t get_stream_id(const uint8_t* frame_header_buffer);
+
+#endif
http_msg_body_chunk.h
http_msg_body_cl.cc
http_msg_body_cl.h
+ http_msg_body_h2.cc
+ http_msg_body_h2.h
http_msg_body_old.cc
http_msg_body_old.h
http_msg_trailer.cc
7. Trailers (all header lines following a chunked body as a group)
Message sections are represented by message section objects that contain and process them. There
-are eleven message section classes that inherit as follows. An asterisk denotes a virtual class.
+are twelve message section classes that inherit as follows. An asterisk denotes a virtual class.
1. HttpMsgSection* - top level with all common elements
2. HttpMsgStart* : HttpMsgSection - common elements of request and status
9. HttpMsgBodyCl : HttpMsgBody
10. HttpMsgBodyChunk : HttpMsgBody
11. HttpMsgBodyOld : HttpMsgBody
+12. HttpMsgBodyH2 : HttpMsgBody
An HttpTransaction is a container that keeps all the sections of a message together and associates
the request message with the response message. Transactions may be organized into pipelines when an
using namespace HttpEnums;
ScanResult HttpStartCutter::cut(const uint8_t* buffer, uint32_t length,
- HttpInfractions* infractions, HttpEventGen* events, uint32_t, bool)
+ HttpInfractions* infractions, HttpEventGen* events, uint32_t, bool, bool)
{
for (uint32_t k = 0; k < length; k++)
{
}
ScanResult HttpHeaderCutter::cut(const uint8_t* buffer, uint32_t length,
- HttpInfractions* infractions, HttpEventGen* events, uint32_t, bool)
+ HttpInfractions* infractions, HttpEventGen* events, uint32_t, bool, bool)
{
// Header separators: leading \r\n, leading \n, nonleading \r\n\r\n, nonleading \n\r\n,
// nonleading \r\n\n, and nonleading \n\n. The separator itself becomes num_excess which is
}
ScanResult HttpBodyClCutter::cut(const uint8_t* buffer, uint32_t length, HttpInfractions*,
- HttpEventGen*, uint32_t flow_target, bool stretch)
+ HttpEventGen*, uint32_t flow_target, bool stretch, bool)
{
assert(remaining > octets_seen);
}
ScanResult HttpBodyOldCutter::cut(const uint8_t* buffer, uint32_t length, HttpInfractions*,
- HttpEventGen*, uint32_t flow_target, bool stretch)
+ HttpEventGen*, uint32_t flow_target, bool stretch, bool)
{
if (flow_target == 0)
{
}
ScanResult HttpBodyChunkCutter::cut(const uint8_t* buffer, uint32_t length,
- HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch)
+ HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch, bool)
{
// Are we skipping through the rest of this chunked body to the trailers and the next message?
const bool discard_mode = (flow_target == 0);
return detain_this_packet ? SCAN_NOT_FOUND_DETAIN : SCAN_NOT_FOUND;
}
+ScanResult HttpBodyH2Cutter::cut(const uint8_t* buffer, uint32_t length, HttpInfractions*,
+ HttpEventGen*, uint32_t flow_target, bool stretch, bool h2_end_stream)
+{
+ //FIXIT-M detained inspection not yet supported for http2
+ UNUSED(buffer);
+
+ // FIXIT-M stretch not yet supported for http2 message bodies
+ UNUSED(stretch);
+
+ if (flow_target == 0)
+ {
+ num_flush = length;
+ return SCAN_DISCARD_PIECE;
+ }
+ if (!h2_end_stream)
+ {
+ if (octets_seen + length < flow_target)
+ {
+ // Not enough data yet to create a message section
+ octets_seen += length;
+ return SCAN_NOT_FOUND;
+ }
+ else
+ {
+ num_flush = flow_target - octets_seen;
+ return SCAN_FOUND_PIECE;
+ }
+ }
+ else
+ {
+ // For now if end_stream is set for scan, a zero-length buffer is always sent to flush
+ num_flush = 0;
+ return SCAN_FOUND;
+ }
+}
+
// This method searches the input stream looking for the beginning of a script or other dangerous
// content that requires detained inspection. Exactly what we are looking for is encapsulated in
// dangerous().
public:
virtual ~HttpCutter() = default;
virtual HttpEnums::ScanResult cut(const uint8_t* buffer, uint32_t length,
- HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch)
- = 0;
+ HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch,
+ bool h2_end_stream) = 0;
uint32_t get_num_flush() const { return num_flush; }
uint32_t get_octets_seen() const { return octets_seen; }
uint32_t get_num_excess() const { return num_crlf; }
{
public:
HttpEnums::ScanResult cut(const uint8_t* buffer, uint32_t length,
- HttpInfractions* infractions, HttpEventGen* events, uint32_t, bool) override;
+ HttpInfractions* infractions, HttpEventGen* events, uint32_t, bool, bool) override;
protected:
enum ValidationResult { V_GOOD, V_BAD, V_TBD };
{
public:
HttpEnums::ScanResult cut(const uint8_t* buffer, uint32_t length,
- HttpInfractions* infractions, HttpEventGen* events, uint32_t, bool) override;
+ HttpInfractions* infractions, HttpEventGen* events, uint32_t, bool, bool) override;
uint32_t get_num_head_lines() const override { return num_head_lines; }
private:
HttpBodyCutter(detained_inspection, compression), remaining(expected_length)
{ assert(remaining > 0); }
HttpEnums::ScanResult cut(const uint8_t*, uint32_t length, HttpInfractions*, HttpEventGen*,
- uint32_t flow_target, bool stretch) override;
+ uint32_t flow_target, bool stretch, bool) override;
private:
int64_t remaining;
explicit HttpBodyOldCutter(bool detained_inspection, HttpEnums::CompressId compression) :
HttpBodyCutter(detained_inspection, compression) {}
HttpEnums::ScanResult cut(const uint8_t*, uint32_t, HttpInfractions*, HttpEventGen*,
- uint32_t flow_target, bool stretch) override;
+ uint32_t flow_target, bool stretch, bool) override;
};
class HttpBodyChunkCutter : public HttpBodyCutter
explicit HttpBodyChunkCutter(bool detained_inspection, HttpEnums::CompressId compression) :
HttpBodyCutter(detained_inspection, compression) {}
HttpEnums::ScanResult cut(const uint8_t* buffer, uint32_t length,
- HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch)
- override;
+ HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch,
+ bool) override;
bool get_is_broken_chunk() const override { return curr_state == HttpEnums::CHUNK_BAD; }
uint32_t get_num_good_chunks() const override { return num_good_chunks; }
void soft_reset() override { num_good_chunks = 0; HttpBodyCutter::soft_reset(); }
uint32_t num_good_chunks = 0; // that end in the current section
};
+class HttpBodyH2Cutter : public HttpBodyCutter
+{
+public:
+ explicit HttpBodyH2Cutter(bool detained_inspection, HttpEnums::CompressId compression) :
+ HttpBodyCutter(detained_inspection, compression) {}
+ HttpEnums::ScanResult cut(const uint8_t*, uint32_t, HttpInfractions*, HttpEventGen*,
+ uint32_t flow_target, bool stretch, bool h2_end_stream) override;
+};
+
+
#endif
// Type of message section
enum SectionType { SEC_DISCARD = -19, SEC_ABORT = -18, SEC__NOT_COMPUTE=-14, SEC__NOT_PRESENT=-11,
SEC_REQUEST = 2, SEC_STATUS, SEC_HEADER, SEC_BODY_CL, SEC_BODY_CHUNK, SEC_TRAILER,
- SEC_BODY_OLD };
+ SEC_BODY_OLD, SEC_BODY_H2 };
enum DetectionStatus { DET_REACTIVATING = 1, DET_ON, DET_DEACTIVATING, DET_OFF };
friend class HttpMsgBody;
friend class HttpMsgBodyChunk;
friend class HttpMsgBodyCl;
+ friend class HttpMsgBodyH2;
friend class HttpMsgBodyOld;
friend class HttpQueryParser;
friend class HttpStreamSplitter;
HttpEnums::SectionType get_type_expected(HttpCommon::SourceId source_id)
{ return type_expected[source_id]; }
+ void set_http2_end_stream(HttpCommon::SourceId source_id)
+ { http2_end_stream[source_id] = true; }
+
private:
+ // HTTP/2 handling
bool for_http2 = false;
+ bool http2_end_stream[2] = { false, false };
// Convenience routines
void half_reset(HttpCommon::SourceId source_id);
#include "http_msg_body.h"
#include "http_msg_body_chunk.h"
#include "http_msg_body_cl.h"
+#include "http_msg_body_h2.h"
#include "http_msg_body_old.h"
#include "http_msg_header.h"
#include "http_msg_request.h"
current_section = new HttpMsgBodyChunk(
data, dsize, session_data, source_id, buf_owner, flow, params);
break;
+ case SEC_BODY_H2:
+ current_section = new HttpMsgBodyH2(
+ data, dsize, session_data, source_id, buf_owner, flow, params);
+ break;
case SEC_TRAILER:
current_section = new HttpMsgTrailer(
data, dsize, session_data, source_id, buf_owner, flow, params);
--- /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.
+//--------------------------------------------------------------------------
+// http_msg_body_h2.cc author Katura Harvey <katharve@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "http_msg_body_h2.h"
+
+void HttpMsgBodyH2::update_flow()
+{
+ session_data->body_octets[source_id] = body_octets;
+ if (session_data->http2_end_stream[source_id])
+ {
+ // FIXIT-H check content length header against bytes received
+
+ session_data->trailer_prep(source_id);
+ session_data->http2_end_stream[source_id] = false;
+ }
+ else
+ {
+ //FIXIT-H check have not exceeded content length
+ update_depth();
+ }
+}
+
+#ifdef REG_TEST
+void HttpMsgBodyH2::print_section(FILE* output)
+{
+ HttpMsgSection::print_section_title(output, "HTTP/2 body");
+ fprintf(output, "octets seen %" PRIi64 "\n", body_octets);
+ print_body_section(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.
+//--------------------------------------------------------------------------
+// http_msg_body_h2.h author Katura Harvey <katharve@cisco.com>
+
+#ifndef HTTP_MSG_BODY_H2_H
+#define HTTP_MSG_BODY_H2_H
+
+#include "http_common.h"
+#include "http_msg_body.h"
+
+//-------------------------------------------------------------------------
+// HttpMsgBodyH2 class
+//-------------------------------------------------------------------------
+
+class HttpMsgBodyH2 : public HttpMsgBody
+{
+public:
+ HttpMsgBodyH2(const uint8_t* buffer, const uint16_t buf_size, HttpFlowData* session_data_,
+ HttpCommon::SourceId source_id_, bool buf_owner, snort::Flow* flow_,
+ const HttpParaList* params_)
+ : HttpMsgBody(buffer, buf_size, session_data_, source_id_, buf_owner, flow_, params_) {}
+ void update_flow() override;
+
+#ifdef REG_TEST
+ void print_section(FILE* output) override;
+#endif
+};
+
+#endif
+
return;
}
+ if (session_data->for_http2)
+ {
+ // FIXIT-H check for transfer-encoding and content-length headers
+ session_data->type_expected[source_id] = SEC_BODY_H2;
+ prepare_body();
+ return;
+ }
+
const Field& te_header = get_header_value_norm(HEAD_TRANSFER_ENCODING);
if ((te_header.length() > 0) && (version_id == VERS_1_0))
{
return true;
}
- // FIXIT-M No longer necessary to send an empty body section because the header section is
+ // FIXIT-H No longer necessary to send an empty body section because the header section is
// always forwarded to detection.
// If the message has been truncated immediately following the start line or immediately
// following the headers (a body was expected) then we need to process an empty section to
// provide an inspection section. Otherwise the start line and headers won't go through
// detection.
if (((session_data->type_expected[source_id] == SEC_HEADER) ||
- (session_data->type_expected[source_id] == SEC_BODY_CL) ||
- (session_data->type_expected[source_id] == SEC_BODY_CHUNK) ||
- (session_data->type_expected[source_id] == SEC_BODY_OLD)) &&
- (session_data->cutter[source_id] == nullptr) &&
+ (session_data->type_expected[source_id] == SEC_BODY_CL) ||
+ (session_data->type_expected[source_id] == SEC_BODY_CHUNK) ||
+ (session_data->type_expected[source_id] == SEC_BODY_OLD)) &&
+ (session_data->cutter[source_id] == nullptr) &&
(session_data->section_type[source_id] == SEC__NOT_COMPUTE))
{
// Set up to process empty message section
{
session_data->half_reset(source_id);
}
+ // FIXIT-M update this to include H2 message once H2I supports trailers and finish()
else if (session_data->type_expected[source_id] == SEC_BODY_CHUNK)
{
session_data->trailer_prep(source_id);
const bool is_body = (session_data->section_type[source_id] == SEC_BODY_CHUNK) ||
(session_data->section_type[source_id] == SEC_BODY_CL) ||
- (session_data->section_type[source_id] == SEC_BODY_OLD);
+ (session_data->section_type[source_id] == SEC_BODY_OLD) ||
+ (session_data->section_type[source_id] == SEC_BODY_H2);
uint8_t*& buffer = session_data->section_buffer[source_id];
if (buffer == nullptr)
{
return (HttpCutter*)new HttpBodyOldCutter(
session_data->detained_inspection[source_id],
session_data->compression[source_id]);
+ case SEC_BODY_H2:
+ return (HttpCutter*)new HttpBodyH2Cutter(
+ session_data->detained_inspection[source_id],
+ session_data->compression[source_id]);
default:
assert(false);
return nullptr;
const ScanResult cut_result = cutter->cut(data, (length <= max_length) ? length :
max_length, session_data->get_infractions(source_id), session_data->events[source_id],
session_data->section_size_target[source_id],
- session_data->stretch_section_to_packet[source_id]);
+ session_data->stretch_section_to_packet[source_id], session_data->http2_end_stream[source_id]);
switch (cut_result)
{
case SCAN_NOT_FOUND: