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];
// 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 };
// 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++) {
}
}
-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 <CR>
- 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 <CR><LF><CR><LF> 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;
}
}
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]; };
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;
}
// 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;
}
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 <SP> or <LF>. 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 <SP> or <LF>. 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;
}
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);
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();
}
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.
}
}
-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 <CR> and <LF> 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 <CR> and <LF> 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;
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;
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;
}
}
buffer += peek_octets;
length -= peek_octets;
- peek_octets = 0;
for (uint32_t k = 0; k < length; k++) {
- // Count the alternating <CR> and <LF> 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;
}
if (complete) {
peek_octets = 0;
peek_status = SCAN_NOTFOUND;
+ first_lf = 0;
}
NHttpSplitter::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 <CR> and <LF> 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();
+}
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;
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 {
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;
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
// 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) {
{
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;
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;
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:
// 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;
}
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 {
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()) {
buffer_length = 0;
return nullptr;
case RES_AGGREGATE:
- buffer_length += offset + len;
+ buffer_length += offset + len - num_excess;
return nullptr;
}
}
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;
#
# 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.