From: Tom Peters Date: Wed, 1 Oct 2014 15:38:10 +0000 (-0400) Subject: Parsing enhancements including LF without CR X-Git-Tag: 3.0.0-233~1400 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f2ba1879104676a26bebee0f9a5d8ab9e39d7b68;p=thirdparty%2Fsnort3.git Parsing enhancements including LF without CR --- diff --git a/src/service_inspectors/nhttp_inspect/nhttp_flow_data.h b/src/service_inspectors/nhttp_inspect/nhttp_flow_data.h index c6d3a1a15..6693ca1b7 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_flow_data.h +++ b/src/service_inspectors/nhttp_inspect/nhttp_flow_data.h @@ -60,8 +60,7 @@ private: void half_reset(NHttpEnums::SourceId source_id); // StreamSplitter internal data - NHttpRequestSplitter request_splitter[2]; - NHttpStatusSplitter status_splitter[2]; + NHttpStartSplitter start_splitter[2]; NHttpHeaderSplitter header_splitter[2]; NHttpChunkSplitter chunk_splitter[2]; NHttpTrailerSplitter trailer_splitter[2]; @@ -75,6 +74,7 @@ private: // StreamSplitter => Inspector (facts about the most recent message section) // 0 element refers to client request, 1 element refers to server response NHttpEnums::SectionType section_type[2] = { NHttpEnums::SEC__NOTCOMPUTE, NHttpEnums::SEC__NOTCOMPUTE }; + uint32_t num_excess[2] = { 0, 0 }; bool tcp_close[2] = { false, false }; uint64_t infractions[2] = { 0, 0 }; diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.cc index 60b66aaf7..5f4412db5 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.cc @@ -40,7 +40,6 @@ using namespace NHttpEnums; // All the header processing that is done for every message (i.e. not just-in-time) is done here. void NHttpMsgHeadShared::analyze() { - parse_whole(); parse_header_block(); parse_header_lines(); for (int j=0; j < num_headers; j++) { @@ -49,64 +48,19 @@ void NHttpMsgHeadShared::analyze() { } } -void NHttpMsgHeadShared::parse_whole() { - // Normal case with header fields - if (!tcp_close && (msg_text.length >= 5)) { - headers.start = msg_text.start; - headers.length = msg_text.length - 4; - assert(!memcmp(msg_text.start+msg_text.length-4, "\r\n\r\n", 4)); - } - // Normal case no header fields - else if (!tcp_close) { - headers.length = STAT_NOTPRESENT; - assert((msg_text.length == 2) && !memcmp(msg_text.start, "\r\n", 2)); - } - // Normal case with header fields and TCP connection close - else if ((msg_text.length >= 5) && !memcmp(msg_text.start+msg_text.length-4, "\r\n\r\n", 4)) { - headers.start = msg_text.start; - headers.length = msg_text.length - 4; - } - // Normal case no header fields and TCP connection close - else if ((msg_text.length == 2) && !memcmp(msg_text.start, "\r\n", 2)) { - headers.length = STAT_NOTPRESENT; - } - // Abnormal cases truncated by TCP connection close - else { - infractions |= INF_TRUNCATED; - // Lone - if ((msg_text.length == 1) && (msg_text.start[0] == '\r')) { - headers.length = STAT_NOTPRESENT; - } - // Truncation occurred somewhere in the header fields - else { - headers.start = msg_text.start; - headers.length = msg_text.length; - // When present, remove partial sequence from the end - if ((msg_text.length >= 4) && !memcmp(msg_text.start+msg_text.length-3, "\r\n\r", 3)) headers.length -= 3; - else if ((msg_text.length >= 3) && !memcmp(msg_text.start+msg_text.length-2, "\r\n", 2)) headers.length -= 2; - else if ((msg_text.length >= 2) && (msg_text.start[msg_text.length-1] == '\r')) headers.length -= 1; - } - } -} - // Divide up the block of header fields into individual header field lines. void NHttpMsgHeadShared::parse_header_block() { - if (headers.length < 0) { - num_headers = STAT_NOSOURCE; - return; - } - int32_t bytes_used = 0; num_headers = 0; - while (bytes_used < headers.length) { - header_line[num_headers].start = headers.start + bytes_used; - header_line[num_headers].length = find_crlf(header_line[num_headers].start, headers.length - bytes_used, true); + while (bytes_used < msg_text.length) { + header_line[num_headers].start = msg_text.start + bytes_used; + header_line[num_headers].length = find_crlf(header_line[num_headers].start, msg_text.length - bytes_used); bytes_used += header_line[num_headers++].length + 2; if (num_headers >= MAXHEADERS) { break; } } - if (bytes_used < headers.length) { + if (bytes_used < msg_text.length) { infractions |= INF_TOOMANYHEADERS; } } diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.h b/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.h index 94c58848e..6e014e3a3 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.h +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.h @@ -44,7 +44,7 @@ public: void gen_events(); int32_t get_num_headers() const { return num_headers; }; - const Field& get_headers() const { return headers; }; + const Field& get_headers() const { return msg_text; }; const Field& get_header_line(int k) const { return header_line[k]; }; const Field& get_header_name(int k) const { return header_name[k]; }; const Field& get_header_value(int k) const { return header_value[k]; }; @@ -72,15 +72,12 @@ protected: static const StrCode header_list[]; static const StrCode trans_code_list[]; - void parse_whole(); void parse_header_block(); void parse_header_lines(); void derive_header_name_id(int index); void print_headers(FILE *output); - Field headers; - // All of these are indexed by the relative position of the header field in the message static const int MAXHEADERS = 200; // I'm an arbitrary number. Need to revisit. int32_t num_headers = NHttpEnums::STAT_NOTCOMPUTE; diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_header.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_header.cc index c7f5bd5b2..f005baefc 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_msg_header.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_header.cc @@ -119,7 +119,7 @@ ProcessResult NHttpMsgHeader::worth_detection() { } // Do not send empty headers by themselves to detection - return ((headers.length != STAT_NOTPRESENT) || (session_data->section_buffer_length[source_id] > 0)) + return ((msg_text.length > 0) || (session_data->section_buffer_length[source_id] > 0)) ? RES_INSPECT : RES_IGNORE; } diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_section.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_section.cc index dd1de27a1..fb4ffbc93 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_msg_section.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_section.cc @@ -56,14 +56,13 @@ NHttpMsgSection::NHttpMsgSection(const uint8_t *buffer, const uint16_t buf_size, delete_msg_on_destruct(buf_owner) {} -// Return the number of octets before the first CRLF. Return length if CRLF not present. -// -// wrappable: CRLF does not count in a header field when immediately followed by or . These whitespace characters -// at the beginning of the next line indicate that the previous header has wrapped and is continuing on the next line. -uint32_t NHttpMsgSection::find_crlf(const uint8_t* buffer, int32_t length, bool wrappable) { +// Return the number of octets before the CRLF that ends a header. Return length if CRLF not present. CRLF does not +// count when immediately followed by or . These whitespace characters at the beginning of the next line +// indicate that the previous header has wrapped and is continuing on the next line. +uint32_t NHttpMsgSection::find_crlf(const uint8_t* buffer, int32_t length) { for (int32_t k=0; k < length-1; k++) { if ((buffer[k] == '\r') && (buffer[k+1] == '\n')) - if (!wrappable || (k+2 >= length) || ((buffer[k+2] != ' ') && (buffer[k+2] != '\t'))) return k; + if ((k+2 >= length) || ((buffer[k+2] != ' ') && (buffer[k+2] != '\t'))) return k; } return length; } diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_section.h b/src/service_inspectors/nhttp_inspect/nhttp_msg_section.h index 06f63e52f..31435a01f 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_msg_section.h +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_section.h @@ -59,7 +59,7 @@ protected: NHttpEnums::SourceId source_id_, bool buf_owner); // Convenience methods - static uint32_t find_crlf(const uint8_t* buffer, int32_t length, bool wrappable); + static uint32_t find_crlf(const uint8_t* buffer, int32_t length); void print_message_title(FILE *output, const char *title) const; void print_message_wrapup(FILE *output) const; void create_event(NHttpEnums::EventSid sid); diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_start.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_start.cc index c2c8bceae..5924383ce 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_msg_start.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_start.cc @@ -40,9 +40,7 @@ using namespace NHttpEnums; void NHttpMsgStart::analyze() { start_line.start = msg_text.start; - start_line.length = find_crlf(start_line.start, msg_text.length, false); - // special case of TCP close between CR and LF - if (tcp_close && (msg_text.length == start_line.length) && (start_line.start[start_line.length-1] == '\r')) start_line.length--; + start_line.length = msg_text.length; parse_start_line(); derive_version_id(); } diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_trailer.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_trailer.cc index ed4f7ce89..17c5c654c 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_msg_trailer.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_trailer.cc @@ -68,7 +68,7 @@ void NHttpMsgTrailer::update_flow() { ProcessResult NHttpMsgTrailer::worth_detection() { // Do not send empty trailers to detection - return (headers.length != STAT_NOTPRESENT) ? RES_INSPECT : RES_IGNORE; + return (msg_text.length > 0) ? RES_INSPECT : RES_IGNORE; } // Legacy support function. Puts message fields into the buffers used by old Snort. diff --git a/src/service_inspectors/nhttp_inspect/nhttp_splitter.cc b/src/service_inspectors/nhttp_inspect/nhttp_splitter.cc index 419aa13d2..b4320e081 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_splitter.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_splitter.cc @@ -40,48 +40,44 @@ void NHttpSplitter::conditional_reset() { } } -ScanResult NHttpRequestSplitter::split(const uint8_t* buffer, uint32_t length) { +ScanResult NHttpStartSplitter::split(const uint8_t* buffer, uint32_t length) { conditional_reset(); for (uint32_t k = 0; k < length; k++) { - // Count the alternating and characters we have seen in a row - if (((buffer[k] == '\r') && (num_crlf == 0)) || - ((buffer[k] == '\n') && (num_crlf == 1))) { - num_crlf++; - if (num_crlf < 2) { - continue; + // Discard magic six white space characters CR, LF, Tab, VT, FF, and SP when they occur before the start line. + // If we have seen nothing but white space so far ... + if (num_crlf == octets_seen + k) { + if ((buffer[k] == 32) || ((buffer[k] >= 9) && (buffer[k] <= 13))) { + if (num_crlf < MAX_LEADING_WHITESPACE) { + num_crlf++; + continue; + } + else { + complete = true; + return SCAN_ABORT; + } + } + if (num_crlf > 0) { + num_flush = k; // current octet not flushed with white space + complete = true; + return SCAN_DISCARD; } } - else { - num_crlf = 0; - continue; - } - num_flush = k+1; - // If the first two octets are CRLF then they must be discarded. - complete = true; - return ((octets_seen + k + 1) == 2) ? SCAN_DISCARD : SCAN_FOUND; - } - octets_seen += length; - return SCAN_NOTFOUND; -} -ScanResult NHttpStatusSplitter::split(const uint8_t* buffer, uint32_t length) { - conditional_reset(); - for (uint32_t k = 0; k < length; k++) { - // Count the alternating and characters we have seen in a row - if (((buffer[k] == '\r') && (num_crlf == 0)) || - ((buffer[k] == '\n') && (num_crlf == 1))) { + // If we get this far then the leading white space issue is behind us and num_crlf was reset to zero + if (buffer[k] == '\n') { num_crlf++; - if (num_crlf < 2) { - continue; - } + num_flush = k+1; + complete = true; + return SCAN_FOUND; } - else { - num_crlf = 0; - continue; + if (num_crlf == 1) { + // CR not followed by LF + complete = true; + return SCAN_ABORT; + } + if (buffer[k] == '\r') { + num_crlf = 1; } - num_flush = k+1; - // If the first two octets are CRLF then they must be discarded. - return ((octets_seen + k + 1) == 2) ? SCAN_DISCARD : SCAN_FOUND; } octets_seen += length; return SCAN_NOTFOUND; @@ -96,13 +92,9 @@ ScanResult NHttpChunkSplitter::split(const uint8_t* buffer, uint32_t length) { return SCAN_FOUND; } for (uint32_t k = 0; k < length; k++) { - if (buffer[k] == '\r') { - num_crlf = 1; - continue; - } - if ((buffer[k] == '\n') && (num_crlf == 1)) { - if ((octets_seen + k + 1) == 2) { - // \r\n leftover from previous chunk + if (buffer[k] == '\n') { + if (octets_seen + k == num_crlf) { + // \r\n or \n leftover from previous chunk complete = true; num_flush = k+1; return SCAN_DISCARD; @@ -117,7 +109,15 @@ ScanResult NHttpChunkSplitter::split(const uint8_t* buffer, uint32_t length) { num_flush = k+1; return SCAN_DISCARD; } - num_crlf = 0; + if (num_crlf == 1) { + // CR not followed by LF + complete = true; + return SCAN_ABORT; + } + if (buffer[k] == '\r') { + num_crlf = 1; + continue; + } if (buffer[k] == ';') { semicolon = true; } @@ -164,29 +164,33 @@ ScanResult NHttpHeaderSplitter::split(const uint8_t* buffer, uint32_t length) { } buffer += peek_octets; length -= peek_octets; - peek_octets = 0; for (uint32_t k = 0; k < length; k++) { - // Count the alternating and characters we have seen in a row - if (((buffer[k] == '\r') && (num_crlf%2 == 0)) || - ((buffer[k] == '\n') && (num_crlf%2 == 1))) { + if (buffer[k] == '\n') { num_crlf++; - if ((num_crlf == 2) && (octets_seen + k + 1) == 2) { - num_flush = k+1; + if ((first_lf == 0) && (num_crlf < octets_seen + k + 1)) { + first_lf = num_crlf; + } + else { + num_flush = k + 1 + peek_octets; complete = true; return SCAN_FOUND; } - if (num_crlf < 4) { - continue; + } + else if (buffer[k] == '\r') { + if (num_crlf == first_lf) { + num_crlf++; + } + else { + num_crlf = 1; + first_lf = 0; } } else { num_crlf = 0; - continue; + first_lf = 0; } - num_flush = k + 1 + peek_octets; - complete = true; - return SCAN_FOUND; } + peek_octets = 0; octets_seen += length; return SCAN_NOTFOUND; } @@ -202,6 +206,7 @@ void NHttpHeaderSplitter::conditional_reset() { if (complete) { peek_octets = 0; peek_status = SCAN_NOTFOUND; + first_lf = 0; } NHttpSplitter::conditional_reset(); } @@ -209,30 +214,40 @@ void NHttpHeaderSplitter::conditional_reset() { ScanResult NHttpTrailerSplitter::split(const uint8_t* buffer, uint32_t length) { conditional_reset(); for (uint32_t k = 0; k < length; k++) { - // Count the alternating and characters we have seen in a row - if (((buffer[k] == '\r') && (num_crlf%2 == 0)) || - ((buffer[k] == '\n') && (num_crlf%2 == 1))) { + if (buffer[k] == '\n') { num_crlf++; - if ((num_crlf == 2) && (octets_seen + k + 1) == 2) { - num_flush = k+1; + if ((first_lf == 0) && (num_crlf < octets_seen + k + 1)) { + first_lf = num_crlf; + } + else { + num_flush = k + 1; complete = true; return SCAN_FOUND; } - if (num_crlf < 4) { - continue; + } + else if (buffer[k] == '\r') { + if (num_crlf == first_lf) { + num_crlf++; + } + else { + num_crlf = 1; + first_lf = 0; } } else { num_crlf = 0; - continue; + first_lf = 0; } - num_flush = k+1; - complete = true; - return SCAN_FOUND; } octets_seen += length; return SCAN_NOTFOUND; } +void NHttpTrailerSplitter::conditional_reset() { + if (complete) { + first_lf = 0; + } + NHttpSplitter::conditional_reset(); +} diff --git a/src/service_inspectors/nhttp_inspect/nhttp_splitter.h b/src/service_inspectors/nhttp_inspect/nhttp_splitter.h index a1b1a234f..6421d3a37 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_splitter.h +++ b/src/service_inspectors/nhttp_inspect/nhttp_splitter.h @@ -43,6 +43,7 @@ public: virtual NHttpEnums::ScanResult peek(const uint8_t*, uint32_t) { assert(0); return NHttpEnums::SCAN_NOTFOUND; }; uint32_t get_num_flush() { return num_flush; }; virtual uint32_t get_octets_seen() { return octets_seen; }; + virtual uint32_t get_num_excess() { return 0; }; protected: uint32_t octets_seen = 0; @@ -53,14 +54,12 @@ protected: virtual void conditional_reset(); }; -class NHttpRequestSplitter : public NHttpSplitter { -public: - NHttpEnums::ScanResult split(const uint8_t* buffer, uint32_t length); -}; - -class NHttpStatusSplitter : public NHttpSplitter { +class NHttpStartSplitter : public NHttpSplitter { public: NHttpEnums::ScanResult split(const uint8_t* buffer, uint32_t length); + uint32_t get_num_excess() { return num_crlf; }; +private: + static const int MAX_LEADING_WHITESPACE = 20; }; class NHttpHeaderSplitter : public NHttpSplitter { @@ -69,14 +68,17 @@ public: NHttpEnums::ScanResult peek(const uint8_t* buffer, uint32_t length); void conditional_reset(); uint32_t get_octets_seen() { return octets_seen - peek_octets; }; + uint32_t get_num_excess() { return num_crlf; }; private: uint32_t peek_octets = 0; + unsigned first_lf = 0; NHttpEnums::ScanResult peek_status = NHttpEnums::SCAN_NOTFOUND; }; class NHttpChunkSplitter : public NHttpSplitter { public: NHttpEnums::ScanResult split(const uint8_t* buffer, uint32_t length); + uint32_t get_num_excess() { return octets_seen; }; void conditional_reset(); private: uint32_t expected_length = 0; @@ -89,6 +91,10 @@ private: class NHttpTrailerSplitter : public NHttpSplitter { public: NHttpEnums::ScanResult split(const uint8_t* buffer, uint32_t length); + void conditional_reset(); + uint32_t get_num_excess() { return num_crlf; }; +private: + unsigned first_lf = 0; }; #endif diff --git a/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter.cc b/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter.cc index 3545f3d6b..a0fbb7252 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter.cc @@ -42,8 +42,10 @@ using namespace NHttpEnums; // Convenience function. All the housekeeping that must be done before we can return FLUSH to stream. void NHttpStreamSplitter::prepare_flush(NHttpFlowData* session_data, uint32_t* flush_offset, SourceId source_id, - SectionType section_type, bool tcp_close, uint64_t infractions, uint32_t num_octets, uint32_t length) { + SectionType section_type, bool tcp_close, uint64_t infractions, uint32_t num_octets, uint32_t length, + uint32_t num_excess) { session_data->section_type[source_id] = section_type; + session_data->num_excess[source_id] = num_excess; session_data->tcp_close[source_id] = tcp_close; session_data->infractions[source_id] = infractions; switch (section_type) { @@ -136,8 +138,8 @@ StreamSplitter::Status NHttpStreamSplitter::scan (Flow* flow, const uint8_t* dat { NHttpSplitter* splitter; switch (type) { - case SEC_REQUEST: splitter = (NHttpSplitter*)&session_data->request_splitter[source_id]; break; - case SEC_STATUS: splitter = (NHttpSplitter*)&session_data->status_splitter[source_id]; break; + case SEC_REQUEST: + case SEC_STATUS: splitter = (NHttpSplitter*)&session_data->start_splitter[source_id]; break; case SEC_CHUNK: splitter = (NHttpSplitter*)&session_data->chunk_splitter[source_id]; break; case SEC_HEADER: splitter = (NHttpSplitter*)&session_data->header_splitter[source_id]; break; case SEC_TRAILER: splitter = (NHttpSplitter*)&session_data->trailer_splitter[source_id]; break; @@ -150,30 +152,31 @@ StreamSplitter::Status NHttpStreamSplitter::scan (Flow* flow, const uint8_t* dat switch (split_result) { case SCAN_NOTFOUND: if (splitter->get_octets_seen() == 63780) { - prepare_flush(session_data, flush_offset, source_id, SEC_DISCARD, false, 0, 0, length); + prepare_flush(session_data, flush_offset, source_id, SEC_DISCARD, false, 0, 0, length, 0); session_data->type_expected[source_id] = SEC_ABORT; return StreamSplitter::ABORT; } if (tcp_close) { - prepare_flush(session_data, flush_offset, source_id, type, true, INF_TRUNCATED, length, length); + prepare_flush(session_data, flush_offset, source_id, type, true, INF_TRUNCATED, length, length, + splitter->get_num_excess()); return StreamSplitter::FLUSH; } // Incomplete headers wait patiently for more data return StreamSplitter::SEARCH; case SCAN_ABORT: - prepare_flush(session_data, flush_offset, source_id, SEC_DISCARD, false, 0, 0, length); + prepare_flush(session_data, flush_offset, source_id, SEC_ABORT, false, 0, length, length, 0); session_data->type_expected[source_id] = SEC_ABORT; return StreamSplitter::FLUSH; case SCAN_DISCARD: { const uint32_t flush_octets = splitter->get_num_flush(); prepare_flush(session_data, flush_offset, source_id, SEC_DISCARD, tcp_close && (flush_octets >= length), 0, - flush_octets, length); + flush_octets, length, 0); return StreamSplitter::FLUSH; } case SCAN_FOUND: { const uint32_t flush_octets = splitter->get_num_flush(); prepare_flush(session_data, flush_offset, source_id, type, tcp_close && (flush_octets == length), 0, - flush_octets, length); + flush_octets, length, splitter->get_num_excess()); if ((type == SEC_REQUEST) || (type == SEC_STATUS)) { // Look ahead to see if entire header section is already here so we can aggregate it for detection. const uint32_t peek_max_length = ((length - flush_octets <= 63780)) ? (length - flush_octets) : 63780; @@ -188,7 +191,7 @@ StreamSplitter::Status NHttpStreamSplitter::scan (Flow* flow, const uint8_t* dat case SEC_BODY: { prepare_flush(session_data, flush_offset, source_id, type, tcp_close && (length <= session_data->data_length[source_id]), - 0, session_data->data_length[source_id], length); + 0, session_data->data_length[source_id], length, 0); return StreamSplitter::FLUSH; } case SEC_ABORT: @@ -229,7 +232,12 @@ const StreamBuffer* NHttpStreamSplitter::reassemble(Flow* flow, unsigned total, // FIXIT-P stream should be enhanced to do discarding for us // For now flush-then-discard here is how scan() handles things we don't need to examine. - if (session_data->section_type[source_id] == SEC_DISCARD) { + if ((session_data->section_type[source_id] == SEC_DISCARD) || + (session_data->section_type[source_id] == SEC_ABORT)) { + if (NHttpTestManager::use_test_output()) { + fprintf(NHttpTestManager::get_output_file(), "%s %u octets\n\n", + (session_data->section_type[source_id] == SEC_DISCARD) ? "Discarded" : "Aborted", len); + } return nullptr; } @@ -245,13 +253,15 @@ const StreamBuffer* NHttpStreamSplitter::reassemble(Flow* flow, unsigned total, session_data->unused_octets_visible[source_id])]; } - memcpy(buffer + buffer_length + offset, data, len); + uint32_t num_excess = session_data->num_excess[source_id]; + unsigned num_to_copy = (len <= total - offset - num_excess) ? len : total - offset - num_excess; + memcpy(buffer + buffer_length + offset, data, num_to_copy); if (flags & PKT_PDU_TAIL) { ProcessResult send_to_detection; if (!is_chunk) { // start line/headers/body individual section processing with aggregation prior to being sent to detection // only the last section added to the buffer goes to the inspector - send_to_detection = my_inspector->process(buffer + buffer_length, offset + len, flow, source_id, + send_to_detection = my_inspector->process(buffer + buffer_length, offset + len - num_excess, flow, source_id, buffer_length == 0); } else { @@ -277,7 +287,7 @@ const StreamBuffer* NHttpStreamSplitter::reassemble(Flow* flow, unsigned total, switch (send_to_detection) { case RES_INSPECT: nhttp_buf.data = buffer; - nhttp_buf.length = buffer_length + offset + len; + nhttp_buf.length = buffer_length + offset + len - num_excess; buffer = nullptr; buffer_length = 0; if (NHttpTestManager::use_test_output()) { @@ -289,7 +299,7 @@ const StreamBuffer* NHttpStreamSplitter::reassemble(Flow* flow, unsigned total, buffer_length = 0; return nullptr; case RES_AGGREGATE: - buffer_length += offset + len; + buffer_length += offset + len - num_excess; return nullptr; } } diff --git a/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter.h b/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter.h index 4258a4cc7..ecb978db4 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter.h +++ b/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter.h @@ -46,7 +46,8 @@ public: unsigned max() { return NHttpTestManager::use_test_input() ? 16384 : paf_max; }; private: void prepare_flush(NHttpFlowData* session_data, uint32_t* flush_offset, NHttpEnums::SourceId source_id, - NHttpEnums::SectionType section_type, bool tcp_close, uint64_t infractions, uint32_t num_octets, uint32_t length); + NHttpEnums::SectionType section_type, bool tcp_close, uint64_t infractions, uint32_t num_octets, uint32_t length, + uint32_t num_excess); void create_event(NHttpEnums::EventSid sid); uint32_t size_buffer_needed(unsigned total, NHttpEnums::SectionType type, uint32_t possible_additional); NHttpInspect* const my_inspector; diff --git a/src/service_inspectors/nhttp_inspect/nhttp_test_msgs.txt b/src/service_inspectors/nhttp_inspect/nhttp_test_msgs.txt index 4693d4164..37e6fcedb 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_test_msgs.txt +++ b/src/service_inspectors/nhttp_inspect/nhttp_test_msgs.txt @@ -36,10 +36,6 @@ # # Fill data will not be provided for a paragraph that is preceded by tcpclose. The body or chunk will terminate at the end of the paragraph. # -# There must not be excess data for a test case. Once a data stream ends with a TCP close there must be a break command before further data is sent. -# Similarly a message section that triggers an abort of processing must end the paragraph and be followed by a break. These rules apply half-duplex -# so it would be possible to send data in the opposite direction before the break. -# # This test tool does not implement the feature of being hardened against bad input. If you write a badly formatted or improper test case the # program may assert or crash. The responsibility is on the developer to get it right. Currently that is the best use of resources.