Merge remote-tracking branch 'origin/topic/awelzel/1705-http-pending-requests'

* origin/topic/awelzel/1705-http-pending-requests:
  http: Prevent request/response de-synchronization and unbounded state growth
This commit is contained in:
Arne Welzel 2023-09-01 11:53:55 +02:00
commit 14a2c02f9d
15 changed files with 137 additions and 3 deletions

View file

@ -144,7 +144,9 @@ export {
["FTP_password_too_long"] = ACTION_LOG_PER_CONN,
["HTTP_bad_chunk_size"] = ACTION_LOG,
["HTTP_chunked_transfer_for_multipart_message"] = ACTION_LOG,
["HTTP_excessive_pipelining"] = ACTION_LOG,
["HTTP_overlapping_messages"] = ACTION_LOG,
["HTTP_response_before_request"] = ACTION_LOG,
["unknown_HTTP_method"] = ACTION_LOG,
["HTTP_version_mismatch"] = ACTION_LOG,
["ident_request_addendum"] = ACTION_LOG,

View file

@ -133,6 +133,12 @@ export {
## HTTP finalization hook. Remaining HTTP info may get logged when it's called.
global finalize_http: Conn::RemovalHook;
## Only allow that many pending requests on a single connection.
## If this number is exceeded, all pending requests are flushed
## out and request/response tracking reset to prevent unbounded
## state growth.
option max_pending_requests = 100;
}
# Add the http state tracking fields to the connection record.
@ -205,6 +211,47 @@ event http_request(c: connection, method: string, original_URI: string,
Conn::register_removal_hook(c, finalize_http);
}
# Request/response tracking exists to account for HTTP pipelining.
# It fails if more responses have been seen than requests. If that
# happens, just fast-forward current_request such that the next
# response matches the in-flight request.
if ( c$http_state$current_request < c$http_state$current_response )
{
Reporter::conn_weird("HTTP_response_before_request", c);
c$http_state$current_request = c$http_state$current_response;
}
# Too many requests are pending for which we have not yet observed a
# reply. This might be due to excessive HTTP pipelining, one-sided
# traffic capture, or the responder side of the HTTP analyzer having
# been disabled. In any case, we simply log out all pending requests
# to make room for a new one. Any matching of pipelined requests and
# responses is most likely totally off anyhow.
if ( max_pending_requests > 0 && |c$http_state$pending| > max_pending_requests )
{
Reporter::conn_weird("HTTP_excessive_pipelining", c);
if ( c$http_state$current_response == 0 )
++c$http_state$current_response;
while ( c$http_state$current_response < c$http_state$current_request )
{
local cr = c$http_state$current_response;
if ( cr in c$http_state$pending )
{
Log::write(HTTP::LOG, c$http_state$pending[cr]);
delete c$http_state$pending[cr];
}
else
{
# The above should have been true...
# Reporter::error(fmt("Expected pending request at %d", cr));
}
++c$http_state$current_response;
}
}
++c$http_state$current_request;
set_state(c, T);