using namespace HttpEnums;
using namespace HttpCommon;
+bool HttpStartCutter::find_eol(uint8_t octet, uint32_t idx, HttpInfractions* infractions, HttpEventGen* events)
+{
+ if (octet == '\n')
+ {
+ num_crlf++;
+ if (num_crlf == 1)
+ {
+ // There was no CR before this
+ *infractions += INF_LF_WITHOUT_CR;
+ events->create_event(EVENT_LF_WITHOUT_CR);
+ }
+ num_flush = idx + 1;
+ return true;
+ }
+ if (num_crlf == 1)
+ {
+ // CR not followed by LF
+ *infractions += INF_CR_WITHOUT_LF;
+ events->create_event(EVENT_CR_WITHOUT_LF);
+ num_flush = idx; // current octet not flushed
+ return true;
+ }
+ if (octet == '\r')
+ {
+ num_crlf = 1;
+ }
+
+ return false;
+}
+
ScanResult HttpStartCutter::cut(const uint8_t* buffer, uint32_t length,
HttpInfractions* infractions, HttpEventGen* events, uint32_t, bool, HXBodyState)
{
break;
}
}
- if (buffer[k] == '\n')
- {
- num_crlf++;
- if (num_crlf == 1)
- {
- // There was no CR before this
- *infractions += INF_LF_WITHOUT_CR;
- events->create_event(EVENT_LF_WITHOUT_CR);
- }
- num_flush = k+1;
- return SCAN_FOUND;
- }
- if (num_crlf == 1)
- { // CR not followed by LF
- *infractions += INF_CR_WITHOUT_LF;
- events->create_event(EVENT_CR_WITHOUT_LF);
- num_flush = k; // current octet not flushed
+
+ if (find_eol(buffer[k], k, infractions, events))
return SCAN_FOUND;
- }
- if (buffer[k] == '\r')
- {
- num_crlf = 1;
- }
}
octets_seen += length;
return SCAN_NOT_FOUND;
return false;
}
+uint8_t HttpZeroNineCutter::match[] = { 'H', 'T', 'T', 'P', '/' };
+
+HttpStartCutter::ValidationResult HttpZeroNineCutter::validate(uint8_t octet, HttpInfractions*, HttpEventGen*)
+{
+ if (octet != match[octets_checked])
+ return V_BAD;
+
+ if (++octets_checked >= match_size)
+ return V_GOOD;
+
+ return V_TBD;
+}
+
+// Lightweight version of the start cutter. Checking whether it is 0.9 response or more advanced
+// version's status line.
+ScanResult HttpZeroNineCutter::cut(const uint8_t* buffer, uint32_t length,
+ HttpInfractions* infractions, HttpEventGen* events, uint32_t, bool, HXBodyState)
+{
+ for (uint32_t k = 0; k < length; k++)
+ {
+ if (!validated)
+ {
+ // The purpose of validate() is to quickly and efficiently dispose of obviously wrong
+ // bindings. Passing is no guarantee that the connection is really HTTP, but failing
+ // makes it clear that it isn't.
+ switch (validate(buffer[k], infractions, events))
+ {
+ case V_GOOD:
+ validated = true;
+ break;
+ case V_BAD:
+ return SCAN_ABORT;
+ case V_TBD:
+ break;
+ }
+ }
+
+ if (find_eol(buffer[k], k, infractions, events))
+ return SCAN_FOUND;
+ }
+ octets_seen += length;
+ return SCAN_NOT_FOUND;
+}
case SEC_REQUEST:
return (HttpCutter*)new HttpRequestCutter;
case SEC_STATUS:
+ if (session_data->expected_trans_num[SRC_SERVER] == session_data->zero_nine_expected)
+ return (HttpCutter*)new HttpZeroNineCutter;
+
return (HttpCutter*)new HttpStatusCutter;
case SEC_HEADER:
case SEC_TRAILER:
return scan(pkt->flow, data, length, flush_offset);
}
-StreamSplitter::Status HttpStreamSplitter::scan(Flow* flow, const uint8_t* data, uint32_t length,
- uint32_t* flush_offset)
+StreamSplitter::Status HttpStreamSplitter::handle_zero_nine(Flow* flow, HttpFlowData* session_data, const uint8_t* data, uint32_t length,
+ uint32_t* flush_offset, SectionType& type, HttpCutter*& cutter)
{
- Profile profile(HttpModule::get_profile_stats());
-
- // This is the session state information we share with HttpInspect and store with stream. A
- // session is defined by a TCP connection. Since scan() is the first to see a new TCP
- // connection the new flow data object is created here.
- HttpFlowData* session_data = HttpInspect::http_get_flow_data(flow);
-
- if (session_data == nullptr)
- {
- HttpInspect::http_set_flow_data(flow, session_data = new HttpFlowData(flow, my_inspector->params));
- HttpModule::increment_peg_counts(PEG_FLOW);
- }
+ // Didn't match status line, this is a 0.9 response.
+ // Delete ZeroNineCutter, save the amount of bytes that should be resent to BodyOld
+ const uint32_t prev_scan_match = cutter->get_octets_seen();
+ delete cutter;
+ cutter = nullptr;
-#ifdef REG_TEST
- if (HttpTestManager::use_test_input(HttpTestManager::IN_HTTP))
+ // 0.9 response is a body that runs to connection end with no headers.
+ // Processing this imaginary empty headers allows
+ // us to overcome this limitation and reuse the entire HTTP infrastructure.
+ session_data->version_id[source_id] = VERS_0_9;
+ session_data->status_code_num = 200;
+ HttpModule::increment_peg_counts(PEG_RESPONSE);
+ prepare_flush(session_data, nullptr, SEC_HEADER, 0, 0, 0, false, 0, 0);
+ my_inspector->process((const uint8_t*)"", 0, flow, SRC_SERVER, false, nullptr);
+ session_data->transaction[SRC_SERVER]->clear_section();
+ HttpContextData* hcd = (HttpContextData*)DetectionEngine::get_data(HttpContextData::ips_id);
+ assert(hcd != nullptr);
+ if (hcd == nullptr)
{
- // This block substitutes a completely new data buffer supplied by the test tool in place
- // of the "real" data. It also rewrites the buffer length.
- *flush_offset = length;
- uint8_t* test_data = nullptr;
- HttpTestManager::get_test_input_source()->scan(test_data, length, source_id,
- session_data->seq_num);
- if (length == 0)
- return StreamSplitter::FLUSH;
- data = test_data;
- }
-#endif
-
- SectionType& type = session_data->type_expected[source_id];
- session_data->partial_flush[source_id] = false;
-
- if (type == SEC_ABORT)
- return status_value(StreamSplitter::ABORT);
-
- if (length > MAX_OCTETS)
- {
- assert(false);
type = SEC_ABORT;
return status_value(StreamSplitter::ABORT);
}
+ hcd->clear();
-#ifdef REG_TEST
- if (HttpTestManager::use_test_output(HttpTestManager::IN_HTTP) &&
- !HttpTestManager::use_test_input(HttpTestManager::IN_HTTP))
+ // Call BodyOldCutter
+ StreamSplitter::Status status;
+ if (prev_scan_match)
{
- fprintf(HttpTestManager::get_output_file(), "Scan from flow data %" PRIu64
- " direction %d length %u client port %hu server port %hu\n", session_data->seq_num,
- source_id, length, flow->client_port, flow->server_port);
- fflush(HttpTestManager::get_output_file());
- if (HttpTestManager::get_show_scan())
- {
- Field(length, data).print(HttpTestManager::get_output_file(), "Scan segment");
- }
- }
-#endif
-
- if (session_data->tcp_close[source_id])
- {
- // assert(false); // FIXIT-L This currently happens. Add assert back when problem resolved.
- type = SEC_ABORT;
- return status_value(StreamSplitter::ABORT);
- }
-
- // If the last request was a CONNECT and we have not yet seen the response, this is early C2S
- // traffic. If there has been a pipeline overflow or underflow we cannot match requests to
- // responses, so there is no attempt to track early C2S traffic.
- if ((source_id == SRC_CLIENT) && (type == SEC_REQUEST) && !session_data->for_httpx &&
- session_data->last_request_was_connect)
- {
- const uint64_t last_request_trans_num = session_data->expected_trans_num[SRC_CLIENT] - 1;
- const bool server_behind_connect =
- (session_data->expected_trans_num[SRC_SERVER] < last_request_trans_num);
- const bool server_expecting_connect_status =
- ((session_data->expected_trans_num[SRC_SERVER] == last_request_trans_num)
- && (session_data->type_expected[SRC_SERVER] == SEC_STATUS));
- const bool pipeline_valid = !session_data->pipeline_overflow &&
- !session_data->pipeline_underflow;
-
- if ((server_behind_connect || server_expecting_connect_status) && pipeline_valid)
- {
- *session_data->get_infractions(source_id) += INF_EARLY_C2S_TRAFFIC_AFTER_CONNECT;
- session_data->events[source_id]->create_event(EVENT_EARLY_C2S_TRAFFIC_AFTER_CONNECT);
- session_data->last_connect_trans_w_early_traffic =
- session_data->expected_trans_num[SRC_CLIENT] - 1;
- }
- session_data->last_request_was_connect = false;
+ assert(prev_scan_match < HttpZeroNineCutter::match_size);
+ uint8_t* buffer = new uint8_t[length + prev_scan_match];
+ memcpy (buffer, HttpZeroNineCutter::match, prev_scan_match);
+ memcpy (buffer + prev_scan_match, data, length);
+ status = call_cutter(flow, session_data, buffer, length + prev_scan_match, flush_offset, type);
+ delete[] buffer;
}
+ else
+ status = call_cutter(flow, session_data, data, length, flush_offset, type);
- HttpModule::increment_peg_counts(PEG_SCAN);
-
- // Check for 0.9 response message
- if ((type == SEC_STATUS) &&
- (session_data->expected_trans_num[SRC_SERVER] == session_data->zero_nine_expected))
- {
- // 0.9 response is a body that runs to connection end with no headers.
- // Processing this imaginary empty headers allows
- // us to overcome this limitation and reuse the entire HTTP infrastructure.
- session_data->version_id[source_id] = VERS_0_9;
- session_data->status_code_num = 200;
- HttpModule::increment_peg_counts(PEG_RESPONSE);
- prepare_flush(session_data, nullptr, SEC_HEADER, 0, 0, 0, false, 0, 0);
- my_inspector->process((const uint8_t*)"", 0, flow, SRC_SERVER, false, nullptr);
- session_data->transaction[SRC_SERVER]->clear_section();
- HttpContextData* hcd = (HttpContextData*)DetectionEngine::get_data(HttpContextData::ips_id);
- assert(hcd != nullptr);
- if (hcd != nullptr)
- hcd->clear();
- }
+ return status;
+}
+StreamSplitter::Status HttpStreamSplitter::call_cutter(Flow* flow, HttpFlowData* session_data, const uint8_t* data, uint32_t length,
+ uint32_t* flush_offset, SectionType& type)
+{
HttpCutter*& cutter = session_data->cutter[source_id];
if (cutter == nullptr)
{
{
*session_data->get_infractions(source_id) += INF_ENDLESS_HEADER;
auto event = HttpEnums::EVENT_HEADERS_TOO_LONG;
+
if (session_data ->type_expected[source_id] == HttpCommon::SEC_REQUEST)
event = HttpEnums::EVENT_REQ_TOO_LONG;
else if (session_data ->type_expected[source_id] == HttpCommon::SEC_STATUS)
event = HttpEnums::EVENT_STAT_TOO_LONG;
+
session_data->events[source_id]->create_event(event);
session_data->events[source_id]->create_event(HttpEnums::EVENT_LOSS_OF_SYNC);
type = SEC_ABORT;
// Wait patiently for more data
return status_value(StreamSplitter::SEARCH);
case SCAN_ABORT:
+ if (type == SEC_STATUS && session_data->expected_trans_num[SRC_SERVER] == session_data->zero_nine_expected)
+ return handle_zero_nine(flow, session_data, data, length, flush_offset, type, cutter);
type = SEC_ABORT;
delete cutter;
cutter = nullptr;
}
}
+StreamSplitter::Status HttpStreamSplitter::scan(Flow* flow, const uint8_t* data, uint32_t length,
+ uint32_t* flush_offset)
+{
+ Profile profile(HttpModule::get_profile_stats());
+
+ // This is the session state information we share with HttpInspect and store with stream. A
+ // session is defined by a TCP connection. Since scan() is the first to see a new TCP
+ // connection the new flow data object is created here.
+ HttpFlowData* session_data = HttpInspect::http_get_flow_data(flow);
+
+ if (session_data == nullptr)
+ {
+ HttpInspect::http_set_flow_data(flow, session_data = new HttpFlowData(flow, my_inspector->params));
+ HttpModule::increment_peg_counts(PEG_FLOW);
+ }
+
+#ifdef REG_TEST
+ if (HttpTestManager::use_test_input(HttpTestManager::IN_HTTP))
+ {
+ // This block substitutes a completely new data buffer supplied by the test tool in place
+ // of the "real" data. It also rewrites the buffer length.
+ *flush_offset = length;
+ uint8_t* test_data = nullptr;
+ HttpTestManager::get_test_input_source()->scan(test_data, length, source_id,
+ session_data->seq_num);
+ if (length == 0)
+ return StreamSplitter::FLUSH;
+ data = test_data;
+ }
+#endif
+
+ SectionType& type = session_data->type_expected[source_id];
+ session_data->partial_flush[source_id] = false;
+
+ if (type == SEC_ABORT)
+ return status_value(StreamSplitter::ABORT);
+
+ if (length > MAX_OCTETS)
+ {
+ assert(false);
+ type = SEC_ABORT;
+ return status_value(StreamSplitter::ABORT);
+ }
+
+#ifdef REG_TEST
+ if (HttpTestManager::use_test_output(HttpTestManager::IN_HTTP) &&
+ !HttpTestManager::use_test_input(HttpTestManager::IN_HTTP))
+ {
+ fprintf(HttpTestManager::get_output_file(), "Scan from flow data %" PRIu64
+ " direction %d length %u client port %hu server port %hu\n", session_data->seq_num,
+ source_id, length, flow->client_port, flow->server_port);
+ fflush(HttpTestManager::get_output_file());
+ if (HttpTestManager::get_show_scan())
+ {
+ Field(length, data).print(HttpTestManager::get_output_file(), "Scan segment");
+ }
+ }
+#endif
+
+ if (session_data->tcp_close[source_id])
+ {
+ // assert(false); // FIXIT-L This currently happens. Add assert back when problem resolved.
+ type = SEC_ABORT;
+ return status_value(StreamSplitter::ABORT);
+ }
+
+ // If the last request was a CONNECT and we have not yet seen the response, this is early C2S
+ // traffic. If there has been a pipeline overflow or underflow we cannot match requests to
+ // responses, so there is no attempt to track early C2S traffic.
+ if ((source_id == SRC_CLIENT) && (type == SEC_REQUEST) && !session_data->for_httpx &&
+ session_data->last_request_was_connect)
+ {
+ const uint64_t last_request_trans_num = session_data->expected_trans_num[SRC_CLIENT] - 1;
+ const bool server_behind_connect =
+ (session_data->expected_trans_num[SRC_SERVER] < last_request_trans_num);
+ const bool server_expecting_connect_status =
+ ((session_data->expected_trans_num[SRC_SERVER] == last_request_trans_num)
+ && (session_data->type_expected[SRC_SERVER] == SEC_STATUS));
+ const bool pipeline_valid = !session_data->pipeline_overflow &&
+ !session_data->pipeline_underflow;
+
+ if ((server_behind_connect || server_expecting_connect_status) && pipeline_valid)
+ {
+ *session_data->get_infractions(source_id) += INF_EARLY_C2S_TRAFFIC_AFTER_CONNECT;
+ session_data->events[source_id]->create_event(EVENT_EARLY_C2S_TRAFFIC_AFTER_CONNECT);
+ session_data->last_connect_trans_w_early_traffic =
+ session_data->expected_trans_num[SRC_CLIENT] - 1;
+ }
+ session_data->last_request_was_connect = false;
+ }
+
+ HttpModule::increment_peg_counts(PEG_SCAN);
+
+ return call_cutter(flow, session_data, data, length, flush_offset, type);
+}
+