From: Mike Stepanek (mstepane) Date: Mon, 23 Mar 2020 19:26:48 +0000 (+0000) Subject: Merge pull request #2088 in SNORT/snort3 from ~KATHARVE/snort3:nhi_h2 to master X-Git-Tag: 3.0.0-270~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8695146160f195a5479a60b8be835d851b50fb3d;p=thirdparty%2Fsnort3.git Merge pull request #2088 in SNORT/snort3 from ~KATHARVE/snort3:nhi_h2 to master Squashed commit of the following: commit eada91f3303497cbb76e33cc1dc6e54c5c34e5fd Author: Katura Harvey Date: Wed Mar 18 10:08:59 2020 -0400 http_inspect: create http2 message body type commit 242bff1e4f0c717a184f213a342ade9192b895de Author: mdagon Date: Mon Mar 9 15:39:02 2020 -0400 http2_inspect: refactor data cutter - preparation for multi packet processing --- diff --git a/src/service_inspectors/http2_inspect/CMakeLists.txt b/src/service_inspectors/http2_inspect/CMakeLists.txt index 4e8177303..5fd32c332 100644 --- a/src/service_inspectors/http2_inspect/CMakeLists.txt +++ b/src/service_inspectors/http2_inspect/CMakeLists.txt @@ -43,6 +43,8 @@ set (FILE_LIST http2_stream_splitter_impl.cc http2_stream_splitter.h http2_tables.cc + http2_utils.cc + http2_utils.h ips_http2.cc ips_http2.h ) diff --git a/src/service_inspectors/http2_inspect/http2_data_cutter.cc b/src/service_inspectors/http2_inspect/http2_data_cutter.cc index 87f3b013c..6f6852db8 100644 --- a/src/service_inspectors/http2_inspect/http2_data_cutter.cc +++ b/src/service_inspectors/http2_inspect/http2_data_cutter.cc @@ -27,37 +27,32 @@ #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<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) @@ -188,9 +172,14 @@ StreamSplitter::Status Http2DataCutter::http_scan(const uint8_t* data, uint32_t* } 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); @@ -208,13 +197,15 @@ const StreamBuffer Http2DataCutter::reassemble(unsigned, const uint8_t* data, { 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 + @@ -222,30 +213,24 @@ const StreamBuffer Http2DataCutter::reassemble(unsigned, const uint8_t* 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; @@ -263,7 +248,7 @@ const StreamBuffer Http2DataCutter::reassemble(unsigned, const uint8_t* data, 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; } @@ -275,7 +260,7 @@ const StreamBuffer Http2DataCutter::reassemble(unsigned, const uint8_t* data, cur_pos += cur_padding; reassemble_padding_read += cur_padding; if (reassemble_padding_read == padding_len) - reassemble_state = SEND_CRLF; + reassemble_state = CLEANUP; break; } @@ -284,12 +269,11 @@ const StreamBuffer Http2DataCutter::reassemble(unsigned, const uint8_t* data, } } - 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) diff --git a/src/service_inspectors/http2_inspect/http2_data_cutter.h b/src/service_inspectors/http2_inspect/http2_data_cutter.h index 1e79cbfb6..fcdb34bd4 100644 --- a/src/service_inspectors/http2_inspect/http2_data_cutter.h +++ b/src/service_inspectors/http2_inspect/http2_data_cutter.h @@ -29,10 +29,9 @@ 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); @@ -42,9 +41,10 @@ private: 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; @@ -70,16 +70,12 @@ private: 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); }; diff --git a/src/service_inspectors/http2_inspect/http2_flow_data.h b/src/service_inspectors/http2_inspect/http2_flow_data.h index d0b6895cd..b5b2900d8 100644 --- a/src/service_inspectors/http2_inspect/http2_flow_data.h +++ b/src/service_inspectors/http2_inspect/http2_flow_data.h @@ -74,8 +74,8 @@ public: 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, diff --git a/src/service_inspectors/http2_inspect/http2_headers_frame.cc b/src/service_inspectors/http2_inspect/http2_headers_frame.cc index 46254299d..c2e474305 100644 --- a/src/service_inspectors/http2_inspect/http2_headers_frame.cc +++ b/src/service_inspectors/http2_inspect/http2_headers_frame.cc @@ -50,9 +50,6 @@ Http2HeadersFrame::Http2HeadersFrame(const uint8_t* header_buffer, const int32_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]; @@ -64,9 +61,8 @@ Http2HeadersFrame::Http2HeadersFrame(const uint8_t* header_buffer, const int32_t // 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; diff --git a/src/service_inspectors/http2_inspect/http2_hpack.cc b/src/service_inspectors/http2_inspect/http2_hpack.cc index ac47b7818..9691261e5 100644 --- a/src/service_inspectors/http2_inspect/http2_hpack.cc +++ b/src/service_inspectors/http2_inspect/http2_hpack.cc @@ -354,7 +354,7 @@ bool Http2HpackDecoder::decode_header_line(const uint8_t* encoded_header_buffer, 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; @@ -390,15 +390,8 @@ bool Http2HpackDecoder::decode_headers(const uint8_t* encoded_headers, 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 diff --git a/src/service_inspectors/http2_inspect/http2_hpack.h b/src/service_inspectors/http2_inspect/http2_hpack.h index 5fa92e3d9..f2d2e9407 100644 --- a/src/service_inspectors/http2_inspect/http2_hpack.h +++ b/src/service_inspectors/http2_inspect/http2_hpack.h @@ -39,7 +39,7 @@ public: 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, diff --git a/src/service_inspectors/http2_inspect/http2_stream.cc b/src/service_inspectors/http2_inspect/http2_stream.cc index bd212718f..ea96b2734 100644 --- a/src/service_inspectors/http2_inspect/http2_stream.cc +++ b/src/service_inspectors/http2_inspect/http2_stream.cc @@ -74,9 +74,9 @@ void Http2Stream::print_frame(FILE* output) } #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]; } diff --git a/src/service_inspectors/http2_inspect/http2_stream.h b/src/service_inspectors/http2_inspect/http2_stream.h index 4476c1055..d1215480e 100644 --- a/src/service_inspectors/http2_inspect/http2_stream.h +++ b/src/service_inspectors/http2_inspect/http2_stream.h @@ -48,10 +48,8 @@ public: 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; } diff --git a/src/service_inspectors/http2_inspect/http2_stream_splitter_impl.cc b/src/service_inspectors/http2_inspect/http2_stream_splitter_impl.cc index 16e35dfde..213f81689 100644 --- a/src/service_inspectors/http2_inspect/http2_stream_splitter_impl.cc +++ b/src/service_inspectors/http2_inspect/http2_stream_splitter_impl.cc @@ -33,48 +33,16 @@ #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; @@ -87,7 +55,7 @@ StreamSplitter::Status data_scan(Http2FlowData* session_data, const uint8_t* dat } 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); @@ -97,8 +65,8 @@ StreamSplitter::Status data_scan(Http2FlowData* session_data, const uint8_t* dat 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, @@ -253,7 +221,7 @@ StreamSplitter::Status implement_scan(Http2FlowData* session_data, const uint8_t 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); @@ -271,7 +239,6 @@ const StreamBuffer implement_reassemble(Http2FlowData* session_data, unsigned to HttpCommon::SourceId source_id) { assert(offset+len <= total); - assert(total >= FRAME_HEADER_LENGTH); assert(total <= MAX_OCTETS); StreamBuffer frame_buf { nullptr, 0 }; @@ -284,13 +251,13 @@ const StreamBuffer implement_reassemble(Http2FlowData* session_data, unsigned to 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(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) diff --git a/src/service_inspectors/http2_inspect/http2_utils.cc b/src/service_inspectors/http2_inspect/http2_utils.cc new file mode 100644 index 000000000..620803cb0 --- /dev/null +++ b/src/service_inspectors/http2_inspect/http2_utils.cc @@ -0,0 +1,60 @@ +//-------------------------------------------------------------------------- +// 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 + +#include "http2_utils.h" + +#include + +#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]; +} diff --git a/src/service_inspectors/http2_inspect/http2_utils.h b/src/service_inspectors/http2_inspect/http2_utils.h new file mode 100644 index 000000000..6a88f1e90 --- /dev/null +++ b/src/service_inspectors/http2_inspect/http2_utils.h @@ -0,0 +1,37 @@ +//-------------------------------------------------------------------------- +// 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 + +#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 diff --git a/src/service_inspectors/http_inspect/CMakeLists.txt b/src/service_inspectors/http_inspect/CMakeLists.txt index 7e0a49055..57c1d52db 100644 --- a/src/service_inspectors/http_inspect/CMakeLists.txt +++ b/src/service_inspectors/http_inspect/CMakeLists.txt @@ -25,6 +25,8 @@ set (FILE_LIST 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 diff --git a/src/service_inspectors/http_inspect/dev_notes.txt b/src/service_inspectors/http_inspect/dev_notes.txt index 44031bbe0..f0f1ccbf0 100644 --- a/src/service_inspectors/http_inspect/dev_notes.txt +++ b/src/service_inspectors/http_inspect/dev_notes.txt @@ -50,7 +50,7 @@ processed together. There are seven types of message section: 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 @@ -63,6 +63,7 @@ are eleven message section classes that inherit as follows. An asterisk denotes 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 diff --git a/src/service_inspectors/http_inspect/http_cutter.cc b/src/service_inspectors/http_inspect/http_cutter.cc index da17cc38b..30c9bc273 100644 --- a/src/service_inspectors/http_inspect/http_cutter.cc +++ b/src/service_inspectors/http_inspect/http_cutter.cc @@ -27,7 +27,7 @@ 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++) { @@ -155,7 +155,7 @@ HttpStartCutter::ValidationResult HttpStatusCutter::validate(uint8_t octet, } 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 @@ -282,7 +282,7 @@ HttpBodyCutter::~HttpBodyCutter() } 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); @@ -358,7 +358,7 @@ ScanResult HttpBodyClCutter::cut(const uint8_t* buffer, uint32_t length, HttpInf } 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) { @@ -395,7 +395,7 @@ ScanResult HttpBodyOldCutter::cut(const uint8_t* buffer, uint32_t length, HttpIn } 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); @@ -687,6 +687,42 @@ ScanResult HttpBodyChunkCutter::cut(const uint8_t* buffer, uint32_t length, 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(). diff --git a/src/service_inspectors/http_inspect/http_cutter.h b/src/service_inspectors/http_inspect/http_cutter.h index a83b7f81e..1ea351b24 100644 --- a/src/service_inspectors/http_inspect/http_cutter.h +++ b/src/service_inspectors/http_inspect/http_cutter.h @@ -35,8 +35,8 @@ class HttpCutter 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; } @@ -56,7 +56,7 @@ class HttpStartCutter : public HttpCutter { 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 }; @@ -85,7 +85,7 @@ class HttpHeaderCutter : public HttpCutter { 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: @@ -124,7 +124,7 @@ public: 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; @@ -136,7 +136,7 @@ public: 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 @@ -145,8 +145,8 @@ public: 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(); } @@ -161,5 +161,15 @@ private: 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 diff --git a/src/service_inspectors/http_inspect/http_enum.h b/src/service_inspectors/http_inspect/http_enum.h index 495943bee..cbfce493f 100644 --- a/src/service_inspectors/http_inspect/http_enum.h +++ b/src/service_inspectors/http_inspect/http_enum.h @@ -39,7 +39,7 @@ static const uint64_t FORM_REQUEST = 0x1; // 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 }; diff --git a/src/service_inspectors/http_inspect/http_flow_data.h b/src/service_inspectors/http_inspect/http_flow_data.h index 249bb9343..a4527e472 100644 --- a/src/service_inspectors/http_inspect/http_flow_data.h +++ b/src/service_inspectors/http_inspect/http_flow_data.h @@ -59,6 +59,7 @@ public: friend class HttpMsgBody; friend class HttpMsgBodyChunk; friend class HttpMsgBodyCl; + friend class HttpMsgBodyH2; friend class HttpMsgBodyOld; friend class HttpQueryParser; friend class HttpStreamSplitter; @@ -70,8 +71,13 @@ public: 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); diff --git a/src/service_inspectors/http_inspect/http_inspect.cc b/src/service_inspectors/http_inspect/http_inspect.cc index e2f43fd85..8d30233fe 100644 --- a/src/service_inspectors/http_inspect/http_inspect.cc +++ b/src/service_inspectors/http_inspect/http_inspect.cc @@ -40,6 +40,7 @@ #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" @@ -453,6 +454,10 @@ bool HttpInspect::process(const uint8_t* data, const uint16_t dsize, Flow* const 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); diff --git a/src/service_inspectors/http_inspect/http_msg_body_h2.cc b/src/service_inspectors/http_inspect/http_msg_body_h2.cc new file mode 100644 index 000000000..ea0a87551 --- /dev/null +++ b/src/service_inspectors/http_inspect/http_msg_body_h2.cc @@ -0,0 +1,51 @@ +//-------------------------------------------------------------------------- +// 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 + +#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 + diff --git a/src/service_inspectors/http_inspect/http_msg_body_h2.h b/src/service_inspectors/http_inspect/http_msg_body_h2.h new file mode 100644 index 000000000..ecc9b8809 --- /dev/null +++ b/src/service_inspectors/http_inspect/http_msg_body_h2.h @@ -0,0 +1,45 @@ +//-------------------------------------------------------------------------- +// 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 + +#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 + diff --git a/src/service_inspectors/http_inspect/http_msg_header.cc b/src/service_inspectors/http_inspect/http_msg_header.cc index 7cab6bef3..855c74507 100644 --- a/src/service_inspectors/http_inspect/http_msg_header.cc +++ b/src/service_inspectors/http_inspect/http_msg_header.cc @@ -194,6 +194,14 @@ void HttpMsgHeader::update_flow() 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)) { diff --git a/src/service_inspectors/http_inspect/http_stream_splitter_finish.cc b/src/service_inspectors/http_inspect/http_stream_splitter_finish.cc index 6f4bc4aac..702775cdc 100644 --- a/src/service_inspectors/http_inspect/http_stream_splitter_finish.cc +++ b/src/service_inspectors/http_inspect/http_stream_splitter_finish.cc @@ -102,17 +102,17 @@ bool HttpStreamSplitter::finish(Flow* flow) 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 diff --git a/src/service_inspectors/http_inspect/http_stream_splitter_reassemble.cc b/src/service_inspectors/http_inspect/http_stream_splitter_reassemble.cc index 7b21953aa..fde9e491b 100644 --- a/src/service_inspectors/http_inspect/http_stream_splitter_reassemble.cc +++ b/src/service_inspectors/http_inspect/http_stream_splitter_reassemble.cc @@ -350,6 +350,7 @@ const StreamBuffer HttpStreamSplitter::reassemble(Flow* flow, unsigned total, { 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); @@ -363,7 +364,8 @@ const StreamBuffer HttpStreamSplitter::reassemble(Flow* flow, unsigned total, 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) { diff --git a/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc b/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc index 51fa92165..587fd7eaf 100644 --- a/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc +++ b/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc @@ -84,6 +84,10 @@ HttpCutter* HttpStreamSplitter::get_cutter(SectionType type, 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; @@ -222,7 +226,7 @@ StreamSplitter::Status HttpStreamSplitter::scan(Packet* pkt, const uint8_t* data 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: