This feature is off by default. script_detection = true will activate it.
-===== partial_depth_body
-
-Partial depth detection is a feature that enables Snort to more quickly detect
-and block malicious requests. It is configured by the partial_depth_body parameter
-which can take values in the range -1-16384 bytes. The feature is enabled by
-setting partial_depth_body to some non zero value. When the feature is enabled and
-either, the number of bytes received in the request body is below the value
-specified by partial_depth_body, or partial_depth_body is set to -1, unlimited; it
-immediately forwards the available part of the message body for early detection.
-This enables earlier threat detection but consumes somewhat more of the sensor's
-resources.
-
-This feature is turned off by default by setting partial_depth_body = 0. To activate
-it, set partial_depth_body to the desired value.
+===== partial_depth_body and partial_depth_header
+
+Partial depth detection enables faster threat detection by immediately forwarding
+partial message data to the detection engine before the complete message arrives.
+This feature can be configured independently for HTTP request bodies
+(partial_depth_body) and headers (partial_depth_header).
+
+ Configuration options:
+ 0 (default): Feature disabled
+ -1 (unlimited): Enable for all partial messages regardless of size
+ Positive value: Enable only when received bytes are below the specified threshold
+
+For HTTP request bodies only, the maximum configurable value is 16,384 bytes. Use
+partial_depth_body = -1 when early detection is needed beyond this limit. HTTP
+headers have no such limitation.
+
+This feature is turned off by default by setting partial_depth_body = 0
+and partial_depth_header = 0. To activate it, set the corresponding parameter to
+the desired value.
===== gzip
return buf;
}
bool HttpStreamSplitter::finish(snort::Flow*) { return false; }
-void HttpStreamSplitter::prep_partial_flush(snort::Flow*, uint32_t) { }
+void HttpStreamSplitter::prep_partial_flush(snort::Flow*, uint32_t, uint32_t, uint32_t) { }
HttpMsgHeader::HttpMsgHeader(const uint8_t* buffer, const uint16_t buf_size,
HttpFlowData* session_data_, SourceId source_id_, bool buf_owner, Flow* flow_,
will not miss anything. The benefits of partial inspection are in addition to the benefits of a
full inspection.
+Header inspections, partial or not, are done on the latest reassembled buffer. The result of
+a previous partial inspection is ignored by deleting the previous header from HttpTransaction.
+Partial header inspections ignore truncated fields at the end of the header fragment.
+
Partial inspection is used in multiple scenarios, described below, and in combinations of them.
Script detection uses partial inspection for message bodies containing Javascripts. The stream
private:
enum LineEndState { ZERO, HALF, ONE, THREEHALF };
LineEndState state = ONE;
- int32_t num_head_lines = 0;
+ uint32_t num_head_lines = 0;
};
class HttpBodyCutter : public HttpCutter
ConfigLogger::log_limit("request_depth", params->request_depth, -1);
ConfigLogger::log_limit("response_depth", params->response_depth, -1);
+ ConfigLogger::log_limit("partial_depth_header", params->partial_depth_header, -1, 0);
ConfigLogger::log_limit("partial_depth_body", params->partial_depth_body, -1, 0);
ConfigLogger::log_flag("unzip", params->unzip);
ConfigLogger::log_flag("normalize_utf", params->normalize_utf);
{ "response_depth", Parameter::PT_INT, "-1:max53", "-1",
"maximum response message body bytes to examine (-1 no limit)" },
+ { "partial_depth_header", Parameter::PT_INT, "-1:max53", "0",
+ "maximum request header to send to early detection (0 disabled, -1 no limit)" },
+
{ "partial_depth_body", Parameter::PT_INT, "-1:16384", "0",
"maximum request body to send to early detection (0 disabled, -1 no limit)" },
{
params->response_depth = val.get_int64();
}
+ else if (val.is("partial_depth_header"))
+ {
+ params->partial_depth_header = val.get_int64();
+ }
else if (val.is("partial_depth_body"))
{
params->partial_depth_body = val.get_int64();
~HttpParaList();
int64_t request_depth = -1;
int64_t response_depth = -1;
+ int64_t partial_depth_header = 0;
int64_t partial_depth_body = 0;
bool unzip = true;
// calculated correctly.
while (bytes_used < msg_text.length())
{
- assert(num_headers < session_data->num_head_lines[source_id]);
const int32_t header_length = find_next_header(msg_text.start() + bytes_used,
msg_text.length() - bytes_used, num_seps);
+ if (header_length == 0)
+ break;
+
+ assert(num_headers < session_data->num_head_lines[source_id]);
if (header_length > max_header_line)
{
max_header_line = header_length;
}
}
}
+ if (session_data->partial_flush[source_id] && session_data->num_excess[source_id] == 0)
+ return 0;
return length - num_seps;
}
void HttpMsgHeader::publish(unsigned pub_id)
{
+ if (session_data->partial_flush[source_id])
+ return;
+
const int64_t stream_id = session_data->get_hx_stream_id();
HttpEvent http_header_event(this, session_data->for_httpx, stream_id);
const snort::StreamBuffer reassemble(snort::Flow* flow, unsigned total, unsigned, const
uint8_t* data, unsigned len, uint32_t flags, unsigned& copied) override;
bool finish(snort::Flow* flow) override;
- void prep_partial_flush(snort::Flow* flow, uint32_t num_flush) override;
+ void prep_partial_flush(snort::Flow* flow, uint32_t num_flush) override
+ { prep_partial_flush(flow, num_flush, 0, 0); }
+ void prep_partial_flush(snort::Flow* flow, uint32_t num_flush, uint32_t num_excess,
+ uint32_t num_head_lines);
bool is_paf() override { return true; }
static StreamSplitter::Status status_value(StreamSplitter::Status ret_val, bool http2 = false);
return false;
}
-void HttpStreamSplitter::prep_partial_flush(Flow* flow, uint32_t num_flush)
+void HttpStreamSplitter::prep_partial_flush(Flow* flow, uint32_t num_flush,
+ uint32_t num_excess, uint32_t num_head_lines)
{
// cppcheck-suppress unreadVariable
Profile profile(HttpModule::get_profile_stats());
// Set up to process partial message section
uint32_t not_used;
- prepare_flush(session_data, ¬_used, session_data->type_expected[source_id], num_flush, 0, 0,
+ prepare_flush(session_data, ¬_used, session_data->type_expected[source_id], num_flush,
+ num_excess, num_head_lines,
session_data->cutter[source_id]->get_is_broken_chunk(),
session_data->cutter[source_id]->get_num_good_chunks(),
session_data->cutter[source_id]->get_octets_seen() - num_flush);
buffer = new uint8_t[MAX_OCTETS];
else
{
- const uint32_t buffer_size = (total > 0) ? total : 1;
+ uint32_t buffer_size = (total > 0) ? total : 1;
+ buffer_size += partial_buffer_length;
buffer = new uint8_t[buffer_size];
}
}
{
// It's possible we're doing a partial flush but there is no actual data to flush after
// decompression.
- if (buf_size > 0)
+ if (session_data->section_offset[source_id] > 0)
{
// Store the data from a partial flush for reuse
- partial_buffer = new uint8_t[buf_size];
- memcpy(partial_buffer, buffer, buf_size);
- partial_buffer_length = buf_size;
+ partial_buffer = new uint8_t[session_data->section_offset[source_id]];
+ memcpy(partial_buffer, buffer, session_data->section_offset[source_id]);
+ partial_buffer_length = session_data->section_offset[source_id];
}
partial_raw_bytes += total;
}
return status_value(StreamSplitter::ABORT);
}
- if (is_body(type) && source_id == SRC_CLIENT &&
- (my_inspector->params->partial_depth_body == -1 ||
- (cutter->get_octets_seen() < my_inspector->params->partial_depth_body && cutter->get_num_flush() == 0)))
+ if (source_id == SRC_CLIENT)
{
- static const uint64_t MAX_PARTIAL_FLUSH_COUNTER = 20;
- if (++session_data->partial_flush_counter == MAX_PARTIAL_FLUSH_COUNTER)
- session_data->events[source_id]->create_event(HttpEnums::EVENT_MAX_PARTIAL_FLUSH);
- cut_result = SCAN_NOT_FOUND_ACCELERATE;
+ int64_t partial_depth = 0;
+ auto params = my_inspector->params;
+
+ if (type == SEC_HEADER)
+ {
+ if (params->partial_depth_header != 0 && !session_data->for_httpx)
+ partial_depth = params->partial_depth_header;
+ }
+ else if (is_body(type))
+ {
+ if (params->partial_depth_body != 0 &&
+ (params->partial_depth_body == -1 || cutter->get_num_flush() == 0))
+ partial_depth = params->partial_depth_body;
+ }
+
+ if (partial_depth == -1 || cutter->get_octets_seen() < partial_depth)
+ {
+ static const uint64_t MAX_PARTIAL_FLUSH_COUNTER = 20;
+ if (++session_data->partial_flush_counter == MAX_PARTIAL_FLUSH_COUNTER)
+ session_data->events[source_id]->create_event(HttpEnums::EVENT_MAX_PARTIAL_FLUSH);
+ cut_result = SCAN_NOT_FOUND_ACCELERATE;
+ }
}
if (cut_result == SCAN_NOT_FOUND_ACCELERATE)
{
- prep_partial_flush(flow, length);
+ prep_partial_flush(flow, length, cutter->get_num_excess(), cutter->get_num_head_lines());
#ifdef REG_TEST
if (!HttpTestManager::use_test_input(HttpTestManager::IN_HTTP))
#endif
}
}
+void HttpTransaction::set_header(HttpMsgHeader* header_, HttpCommon::SourceId source_id)
+{
+ delete (header[source_id]);
+ header[source_id] = header_;
+}
+
void HttpTransaction::set_body(HttpMsgBody* latest_body)
{
latest_body->next = body_list;
void set_status(HttpMsgStatus* status_) { status = status_; }
HttpMsgHeader* get_header(HttpCommon::SourceId source_id) const { return header[source_id]; }
- void set_header(HttpMsgHeader* header_, HttpCommon::SourceId source_id)
- { header[source_id] = header_; }
+ void set_header(HttpMsgHeader* header_, HttpCommon::SourceId source_id);
HttpMsgTrailer* get_trailer(HttpCommon::SourceId source_id) const
{ return trailer[source_id]; }
return buf;
}
bool HttpStreamSplitter::finish(snort::Flow*) { return false; }
-void HttpStreamSplitter::prep_partial_flush(snort::Flow*, uint32_t) { }
+void HttpStreamSplitter::prep_partial_flush(snort::Flow*, uint32_t, uint32_t, uint32_t) { }
THREAD_LOCAL PegCount HttpModule::peg_counts[PEG_COUNT_MAX] = { };
const Field Field::FIELD_NULL { STAT_NO_SOURCE };