From: Adrian Mamolea (admamole) Date: Tue, 3 Mar 2026 18:52:32 +0000 (+0000) Subject: Pull request #5181: payload_injector: add support for payload injection on s2c packet... X-Git-Tag: 3.12.1.0~31 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=89348264bd0da61f8bc30c591a1dbc8d45a8d391;p=thirdparty%2Fsnort3.git Pull request #5181: payload_injector: add support for payload injection on s2c packets for http and http2 traffic Merge in SNORT/snort3 from ~ADMAMOLE/snort3:test_s2c to master Squashed commit of the following: commit 5f0f358b3c2864c2a11d9697c8ce5046c2dfa7b6 Author: Adrian Mamolea Date: Thu Feb 26 13:58:35 2026 -0500 payload_injector: add support for payload injection on s2c packets for http and http2 traffic --- diff --git a/doc/user/active.txt b/doc/user/active.txt index 3061240ef..c3602a51e 100644 --- a/doc/user/active.txt +++ b/doc/user/active.txt @@ -122,7 +122,7 @@ translate the page to HTTP/2 format. Limitations for HTTP/2: -* Packet will be injected against the last received stream id. +* Packet will be injected against the last processed stream id. * Injection triggered while server-to-client flow of traffic is in a middle of a frame is not supported. The traffic will be blocked, but the page will diff --git a/src/actions/act_react.cc b/src/actions/act_react.cc index a3c20ac51..1daa1cb3b 100644 --- a/src/actions/act_react.cc +++ b/src/actions/act_react.cc @@ -180,7 +180,7 @@ void ReactActiveAction::send(Packet* p) if (p->flow && p->flow->gadget && (strcmp(p->flow->gadget->get_name(), "http2_inspect") == 0)) { - Http2FlowData* const session_data = + const Http2FlowData* const session_data = (Http2FlowData*)p->flow->get_flow_data(Http2FlowData::inspector_id); assert(session_data != nullptr); const SourceId source_id = p->is_from_client() ? SRC_CLIENT : SRC_SERVER; @@ -227,6 +227,9 @@ void ReactAction::exec(Packet* p, const ActInfo& ai) p->active->drop_packet(p); p->active->set_drop_reason("ips"); + if ( p->flow ) + p->flow->disable_inspection(); + if ( p->context->wire_packet and !p->active->is_reset_candidate(p->context->wire_packet) ) { diff --git a/src/actions/dev_notes.txt b/src/actions/dev_notes.txt index 97212d39e..fc7c43143 100644 --- a/src/actions/dev_notes.txt +++ b/src/actions/dev_notes.txt @@ -15,7 +15,11 @@ TCP resets (TCP connections) or ICMP unreachable packets. React sends an HTML page to the client, a RST to the server and blocks the flow. It is using payload_injector utility. payload_injector should be configured when -react is used. +react is used. In some cases, block page injection is not possible. For example, +if the client is expecting a response body rather than a status line, injecting +a block page would be invalid. For more details look at implementation of +payload_injector. In either case, even when injection of the block page is +not supported, react blocks the flow. Rewrite enables overwrite packet contents based on "replace" option in the rules. Note that using "rewrite" action without "replace" option will raise diff --git a/src/detection/event_trace.cc b/src/detection/event_trace.cc index 4fd7c8129..a96465f44 100644 --- a/src/detection/event_trace.cc +++ b/src/detection/event_trace.cc @@ -92,7 +92,7 @@ void EventTrace_Log(const Packet* p, const OptTreeNode* otn, IpsAction::Type act p->pkth->pktlen, p->pktlen); TextLog_Print(tlog, - "Pkt Bits: Flags=0x%X, Proto=0x%X, Err=0x%X\n", + "Pkt Bits: Flags=0x" STDx64 ", Proto=0x%X, Err=0x%X\n", p->packet_flags, (unsigned)p->proto_bits, (unsigned)p->ptrs.decode_flags); TextLog_Print(tlog, @@ -134,4 +134,3 @@ void EventTrace_Term() TextLog_Term(tlog); } } - diff --git a/src/framework/base_api.h b/src/framework/base_api.h index a0307995f..713f283df 100644 --- a/src/framework/base_api.h +++ b/src/framework/base_api.h @@ -38,7 +38,7 @@ // depends on includes installed in framework/snort_api.h // see framework/plugins.h -#define BASE_API_VERSION 24 +#define BASE_API_VERSION 25 #define PLUGIN_DEFAULT 0x0 #define PLUGIN_SO_RELOAD 0x1 // assumed for PT_SO_RULE diff --git a/src/payload_injector/dev_notes.txt b/src/payload_injector/dev_notes.txt index 99500cff7..a204fdeaf 100644 --- a/src/payload_injector/dev_notes.txt +++ b/src/payload_injector/dev_notes.txt @@ -9,6 +9,17 @@ injection triggered while server-to-client flow of traffic is in a middle of a frame is not supported. The traffic will be blocked, but the page will not be injected/displayed. +There are other cases when payload_injector doesn't inject a block page: +* payload_injector is not configured +* packet doesn't have a flow or the flow's session is not established +* packet is not TCP or stream_tcp marked the packet as one for which a block page + cannot be injected +* there is conflicting s2c traffic such as queued client segments +* http_inspect or http2_inspect marked the packet as one for which a block page + cannot be injected +* the service inspector for the flow is neither http_inspect nor http2_inspect +* for HTTP/2, the target stream ID is zero or even (server-initiated) + get_http2_payload supports translation of HTTP block/redirect page to HTTP2. Current implementation is limited, the constraints are specified in payload_injector_translate_page.cc. diff --git a/src/payload_injector/payload_injector.cc b/src/payload_injector/payload_injector.cc index 91b5ed809..0cb1f152e 100644 --- a/src/payload_injector/payload_injector.cc +++ b/src/payload_injector/payload_injector.cc @@ -50,8 +50,13 @@ static const std::map InjectionErrorToStrin { ERR_TRANSLATED_HDRS_SIZE, "HTTP/2 translated header size is bigger than expected. Update max size." }, { ERR_HTTP2_EVEN_STREAM_ID, "HTTP/2 - injection to server initiated stream" }, - { ERR_PKT_FROM_SERVER, "Packet is from server" }, - { ERR_CONFLICTING_S2C_TRAFFIC, "Conflicting S2C HTTP traffic in progress" } + { ERR_CONFLICTING_S2C_TRAFFIC, "Conflicting S2C HTTP traffic in progress" }, + { ERR_SESSION_NOT_TCP, "not a TCP stream" }, + { ERR_STALE_S2C_DATA, "S2C injection blocked: packet fills hole with pending out-of-order, " + "retransmitted, or overlapping segments" }, + { ERR_S2C_HTTP_PROTO, "HTTP/1 injection blocked on server response due to protocol state conflict" }, + { ERR_C2S_HTTP_PROTO, "HTTP/1 injection blocked on client request due to protocol state conflict" }, + { ERR_S2C_HTTP2_PROTO, "HTTP/2 injection blocked on server response due to protocol state conflict" } }; InjectionReturnStatus PayloadInjector::inject_http2_payload(Packet* p, @@ -60,10 +65,14 @@ InjectionReturnStatus PayloadInjector::inject_http2_payload(Packet* p, InjectionReturnStatus status; if (control.stream_id == 0) + { + payload_injector_stats.err_http2_stream_id_0++; status = ERR_HTTP2_STREAM_ID_0; + } else if (control.stream_id % 2 == 0) { // Don't inject against server initiated streams + payload_injector_stats.err_http2_even_stream++; status = ERR_HTTP2_EVEN_STREAM_ID; } else @@ -77,11 +86,18 @@ InjectionReturnStatus PayloadInjector::inject_http2_payload(Packet* p, // FIXIT-E mid-frame injection not supported status = ERR_HTTP2_MID_FRAME; } - else if (p->flow->session and + else if (p->flow->session and p->packet_flags & PKT_FROM_CLIENT and p->flow->session->are_client_segments_queued()) { + payload_injector_stats.err_conflicting_s2c_traffic++; status = ERR_CONFLICTING_S2C_TRAFFIC; } + else if ((p->packet_flags & PKT_FROM_SERVER) + && (p->packet_flags & PKT_HTTP_INJECT_BLOCKED)) + { + payload_injector_stats.err_s2c_http2_proto++; + status = ERR_S2C_HTTP2_PROTO; + } else { uint8_t* http2_payload; @@ -113,44 +129,82 @@ InjectionReturnStatus PayloadInjector::inject_http_payload(Packet* p, assert(p != nullptr); const PayloadInjectorConfig* conf = p->context->conf->payload_injector_config; - if (conf) + + if (!conf) { + payload_injector_stats.err_injector_not_configured++; + status = ERR_INJECTOR_NOT_CONFIGURED; + } + else if (!p->flow) + { + payload_injector_stats.err_unidentified_protocol++; + status = ERR_UNIDENTIFIED_PROTOCOL; + } + else if (!(p->flow->ssn_state.session_flags & SSNFLAG_ESTABLISHED)) + { + payload_injector_stats.err_stream_not_established++; + status = ERR_STREAM_NOT_ESTABLISHED; + } + else if (p->flow->pkt_type != PktType::TCP) + { + payload_injector_stats.err_session_not_tcp++; + status = ERR_SESSION_NOT_TCP; + } + else + { + EncodeFlags df = ENC_FLAG_RST_SRVR; // Send RST to server. + if (p->packet_flags & PKT_FROM_SERVER) - status = ERR_PKT_FROM_SERVER; - else - { - EncodeFlags df = ENC_FLAG_RST_SRVR; // Send RST to server. + df |= ENC_FLAG_FWD; - if (!p->flow) - status = ERR_UNIDENTIFIED_PROTOCOL; - else if (p->flow->ssn_state.session_flags & SSNFLAG_ESTABLISHED) + if ((p->packet_flags & PKT_FROM_SERVER) + && (p->packet_flags & PKT_TCP_INJECT_BLOCKED)) + { + payload_injector_stats.err_stale_s2c_data++; + status = ERR_STALE_S2C_DATA; + p->active->send_data(p, df, nullptr, 0); // To send reset + } + // FIXIT-M should we be supporting injection when there is no gadget on the flow? + else if (!p->flow->gadget || strcmp(p->flow->gadget->get_name(), "http_inspect") == 0) + { + if (p->flow->session and p->packet_flags & PKT_FROM_CLIENT and + p->flow->session->are_client_segments_queued()) { - // FIXIT-M should we be supporting injection when there is no gadget on the flow? - if (!p->flow->gadget || strcmp(p->flow->gadget->get_name(), "http_inspect") == 0) + payload_injector_stats.err_conflicting_s2c_traffic++; + status = ERR_CONFLICTING_S2C_TRAFFIC; + p->active->send_data(p, df, nullptr, 0); // To send reset + } + else if (p->packet_flags & PKT_HTTP_INJECT_BLOCKED) + { + if (p->packet_flags & PKT_FROM_SERVER) { - if (p->flow->session and - p->flow->session->are_client_segments_queued()) - { - status = ERR_CONFLICTING_S2C_TRAFFIC; - p->active->send_data(p, df, nullptr, 0); // To send reset - } - else - { - payload_injector_stats.http_injects++; - p->active->send_data(p, df, control.http_page, control.http_page_len); - } + payload_injector_stats.err_s2c_http_proto++; + status = ERR_S2C_HTTP_PROTO; } - else if (strcmp(p->flow->gadget->get_name(),"http2_inspect") == 0) - status = inject_http2_payload(p, control, df); else - status = ERR_UNIDENTIFIED_PROTOCOL; + { + payload_injector_stats.err_c2s_http_proto++; + status = ERR_C2S_HTTP_PROTO; + } + p->active->send_data(p, df, nullptr, 0); // To send reset } else - status = ERR_STREAM_NOT_ESTABLISHED; + { + payload_injector_stats.http_injects++; + p->active->send_data(p, df, control.http_page, control.http_page_len); + } + } + else if (strcmp(p->flow->gadget->get_name(),"http2_inspect") == 0) + status = inject_http2_payload(p, control, df); + else + { + payload_injector_stats.err_unidentified_protocol++; + status = ERR_UNIDENTIFIED_PROTOCOL; } } - else - status = ERR_INJECTOR_NOT_CONFIGURED; + + if (status != INJECTION_SUCCESS) + payload_injector_stats.failed_injects++; p->active->block_session(p, true); diff --git a/src/payload_injector/payload_injector.h b/src/payload_injector/payload_injector.h index f2ead41aa..5bb43a7aa 100644 --- a/src/payload_injector/payload_injector.h +++ b/src/payload_injector/payload_injector.h @@ -39,8 +39,12 @@ enum InjectionReturnStatus : int8_t ERR_HTTP2_MID_FRAME = -6, ERR_TRANSLATED_HDRS_SIZE = -7, ERR_HTTP2_EVEN_STREAM_ID = -8, - ERR_PKT_FROM_SERVER = -9, - ERR_CONFLICTING_S2C_TRAFFIC = -10, + ERR_CONFLICTING_S2C_TRAFFIC = -9, + ERR_SESSION_NOT_TCP = -10, + ERR_STALE_S2C_DATA = -11, + ERR_S2C_HTTP_PROTO= -12, + ERR_C2S_HTTP_PROTO= -13, + ERR_S2C_HTTP2_PROTO= -14 // Update InjectionErrorToString when adding/removing error codes }; diff --git a/src/payload_injector/payload_injector_module.cc b/src/payload_injector/payload_injector_module.cc index 914723c3e..9af17199d 100644 --- a/src/payload_injector/payload_injector_module.cc +++ b/src/payload_injector/payload_injector_module.cc @@ -38,10 +38,22 @@ THREAD_LOCAL PayloadInjectorCounts payload_injector_stats; const PegInfo payload_injector_pegs[] = { - { CountType::SUM, "http_injects", "total number of http injections" }, - { CountType::SUM, "http2_injects", "total number of http2 injections" }, - { CountType::SUM, "http2_translate_err", "total number of http2 page translation errors" }, + { CountType::SUM, "http_injects", "total number of HTTP injections" }, + { CountType::SUM, "http2_injects", "total number of HTTP/2 injections" }, + { CountType::SUM, "failed_injects", "total number of failed HTTP and HTTP/2 injections" }, + { CountType::SUM, "http2_translate_err", "total number of HTTP/2 page translation errors" }, { CountType::SUM, "http2_mid_frame", "total number of attempts to inject mid-frame" }, + { CountType::SUM, "err_unidentified_protocol", "total number of unidentified-protocol errors" }, + { CountType::SUM, "err_stream_not_established", "total number of stream-not-established errors" }, + { CountType::SUM, "err_injector_not_configured", "total number of injector-not-configured errors" }, + { CountType::SUM, "err_conflicting_s2c_traffic", "total number of conflicting s2c traffic errors" }, + { CountType::SUM, "err_http2_even_stream", "total number of HTTP/2 even-numbered stream errors" }, + { CountType::SUM, "err_http2_stream_id_0", "total number of HTTP/2 stream ID 0 errors" }, + { CountType::SUM, "err_session_not_tcp", "total number of session-not-tcp errors" }, + { CountType::SUM, "err_stale_s2c_data", "total number of stale s2c data errors" }, + { CountType::SUM, "err_s2c_http_proto", "total number of s2c HTTP protocol errors" }, + { CountType::SUM, "err_c2s_http_proto", "total number of c2s HTTP protocol errors" }, + { CountType::SUM, "err_s2c_http2_proto", "total number of s2c HTTP2 protocol errors" }, { CountType::END, nullptr, nullptr } }; diff --git a/src/payload_injector/payload_injector_module.h b/src/payload_injector/payload_injector_module.h index a9730676b..63946f524 100644 --- a/src/payload_injector/payload_injector_module.h +++ b/src/payload_injector/payload_injector_module.h @@ -27,8 +27,20 @@ struct PayloadInjectorCounts { PegCount http_injects; PegCount http2_injects; + PegCount failed_injects; PegCount http2_translate_err; PegCount http2_mid_frame; + PegCount err_unidentified_protocol; + PegCount err_stream_not_established; + PegCount err_injector_not_configured; + PegCount err_conflicting_s2c_traffic; + PegCount err_http2_even_stream; + PegCount err_http2_stream_id_0; + PegCount err_session_not_tcp; + PegCount err_stale_s2c_data; + PegCount err_s2c_http_proto; + PegCount err_c2s_http_proto; + PegCount err_s2c_http2_proto; }; extern THREAD_LOCAL PayloadInjectorCounts payload_injector_stats; diff --git a/src/payload_injector/test/payload_injector_test.cc b/src/payload_injector/test/payload_injector_test.cc index d24f99167..6fef68d37 100644 --- a/src/payload_injector/test/payload_injector_test.cc +++ b/src/payload_injector/test/payload_injector_test.cc @@ -28,6 +28,7 @@ #include "detection/detection_engine.h" #include "flow/flow.h" +#include "flow/session.h" #include "main/snort_config.h" #include "main/thread_config.h" #include "packet_io/active.h" @@ -119,6 +120,18 @@ public: bool configure(snort::SnortConfig*) override { return true; } }; +class MockSession : public Session +{ +public: + explicit MockSession(snort::Flow* f, bool has_queued) : Session(f), queued(has_queued) { } + + void clear() override { } + bool are_client_segments_queued() const override { return queued; } + +private: + bool queued; +}; + // Mocks for PayloadInjectorModule::get_http2_payload static InjectionReturnStatus translation_status = INJECTION_SUCCESS; @@ -171,12 +184,26 @@ TEST_GROUP(payload_injector_test) { counts->http_injects = 0; counts->http2_injects = 0; + counts->failed_injects = 0; counts->http2_translate_err = 0; counts->http2_mid_frame = 0; + counts->err_unidentified_protocol = 0; + counts->err_stream_not_established = 0; + counts->err_injector_not_configured = 0; + counts->err_conflicting_s2c_traffic = 0; + counts->err_http2_even_stream = 0; + counts->err_http2_stream_id_0 = 0; + counts->err_session_not_tcp = 0; + counts->err_stale_s2c_data = 0; + counts->err_s2c_http_proto = 0; + counts->err_c2s_http_proto = 0; + counts->err_s2c_http2_proto = 0; control.http_page = (const uint8_t*)"test"; control.http_page_len = 4; + control.stream_id = 0; flow.set_state(Flow::FlowState::INSPECT); flow.set_session_flags(SSNFLAG_ESTABLISHED); + flow.pkt_type = PktType::TCP; translation_status = INJECTION_SUCCESS; http2_flow_data.set_mid_frame(false); } @@ -189,10 +216,11 @@ TEST(payload_injector_test, not_configured_stream_not_established) p.flow = &flow; InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); CHECK(counts->http_injects == 0); + CHECK(counts->err_injector_not_configured == 1); CHECK(status == ERR_INJECTOR_NOT_CONFIGURED); CHECK(flow.flow_state == Flow::FlowState::BLOCK); const char* err_string = PayloadInjector::get_err_string(status); - CHECK(strcmp(err_string, "Payload injector is not configured") == 0); + STRCMP_EQUAL("Payload injector is not configured", err_string); } TEST(payload_injector_test, not_configured_stream_established) @@ -202,6 +230,8 @@ TEST(payload_injector_test, not_configured_stream_established) p.flow = &flow; InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); CHECK(counts->http_injects == 0); + CHECK(counts->failed_injects == 1); + CHECK(counts->err_injector_not_configured == 1); CHECK(status == ERR_INJECTOR_NOT_CONFIGURED); CHECK(flow.flow_state == Flow::FlowState::BLOCK); } @@ -214,9 +244,11 @@ TEST(payload_injector_test, configured_stream_not_established) flow.update_session_flags(0); InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); CHECK(counts->http_injects == 0); + CHECK(counts->failed_injects == 1); + CHECK(counts->err_stream_not_established == 1); CHECK(status == ERR_STREAM_NOT_ESTABLISHED); const char* err_string = PayloadInjector::get_err_string(status); - CHECK(strcmp(err_string, "TCP stream not established") == 0); + STRCMP_EQUAL("TCP stream not established", err_string); CHECK(flow.flow_state == Flow::FlowState::BLOCK); } @@ -244,10 +276,12 @@ TEST(payload_injector_test, http2_stream0) p.flow = &flow; InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); CHECK(counts->http2_injects == 0); + CHECK(counts->failed_injects == 1); + CHECK(counts->err_http2_stream_id_0 == 1); CHECK(status == ERR_HTTP2_STREAM_ID_0); CHECK(flow.flow_state == Flow::FlowState::BLOCK); const char* err_string = PayloadInjector::get_err_string(status); - CHECK(strcmp(err_string, "HTTP/2 - injection to stream 0") == 0); + STRCMP_EQUAL("HTTP/2 - injection to stream 0", err_string); delete flow.gadget; } @@ -261,10 +295,12 @@ TEST(payload_injector_test, http2_even_stream_id) control.stream_id = 2; InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); CHECK(counts->http2_injects == 0); + CHECK(counts->failed_injects == 1); + CHECK(counts->err_http2_even_stream == 1); CHECK(status == ERR_HTTP2_EVEN_STREAM_ID); CHECK(flow.flow_state == Flow::FlowState::BLOCK); const char* err_string = PayloadInjector::get_err_string(status); - CHECK(strcmp(err_string, "HTTP/2 - injection to server initiated stream") == 0); + STRCMP_EQUAL("HTTP/2 - injection to server initiated stream", err_string); delete flow.gadget; } @@ -304,6 +340,9 @@ TEST(payload_injector_test, unidentified_gadget_name) flow.gadget = new MockInspector(); p.flow = &flow; InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); + CHECK(counts->http_injects == 0); + CHECK(counts->failed_injects == 1); + CHECK(counts->err_unidentified_protocol == 1); CHECK(status == ERR_UNIDENTIFIED_PROTOCOL); CHECK(flow.flow_state == Flow::FlowState::BLOCK); delete flow.gadget; @@ -319,53 +358,180 @@ TEST(payload_injector_test, http2_mid_frame) control.stream_id = 1; http2_flow_data.set_mid_frame(true); InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); + CHECK(counts->http2_injects == 0); + CHECK(counts->failed_injects == 1); CHECK(counts->http2_mid_frame == 1); CHECK(status == ERR_HTTP2_MID_FRAME); CHECK(flow.flow_state == Flow::FlowState::BLOCK); const char* err_string = PayloadInjector::get_err_string(status); - CHECK(strcmp(err_string, "HTTP/2 - attempt to inject mid frame. Currently not supported.") - == 0); + STRCMP_EQUAL("HTTP/2 - attempt to inject mid frame. Currently not supported.", + err_string); delete flow.gadget; } -TEST(payload_injector_test, http2_continuation_expected) +TEST(payload_injector_test, http_pkt_from_srvr) { Packet p(false); set_configured(); - mock_api.base.name = "http2_inspect"; + mock_api.base.name = "http_inspect"; + p.packet_flags = PKT_FROM_SERVER; flow.gadget = new MockInspector(); p.flow = &flow; - control.stream_id = 1; - http2_flow_data.set_mid_frame(true); + p.active = &active; InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); - CHECK(counts->http2_mid_frame == 1); - CHECK(status == ERR_HTTP2_MID_FRAME); + CHECK(counts->http_injects == 1); + CHECK(status == INJECTION_SUCCESS); CHECK(flow.flow_state == Flow::FlowState::BLOCK); delete flow.gadget; } -TEST(payload_injector_test, http2_pkt_from_srvr) +TEST(payload_injector_test, flow_is_null) { Packet p(false); set_configured(); - p.packet_flags = PKT_FROM_SERVER; + InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); + CHECK(counts->http_injects == 0); + CHECK(counts->failed_injects == 1); + CHECK(counts->err_unidentified_protocol == 1); + CHECK(status == ERR_UNIDENTIFIED_PROTOCOL); + const char* err_string = PayloadInjector::get_err_string(status); + STRCMP_EQUAL("Unidentified protocol", err_string); +} + +TEST(payload_injector_test, session_not_tcp) +{ + Packet p(false); + set_configured(); + p.flow = &flow; + p.active = &active; + flow.pkt_type = PktType::UDP; + InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); + CHECK(counts->http_injects == 0); + CHECK(counts->failed_injects == 1); + CHECK(counts->err_session_not_tcp == 1); + CHECK(status == ERR_SESSION_NOT_TCP); + CHECK(flow.flow_state == Flow::FlowState::BLOCK); + const char* err_string = PayloadInjector::get_err_string(status); + STRCMP_EQUAL("not a TCP stream", err_string); +} + +TEST(payload_injector_test, stale_s2c_data) +{ + Packet p(false); + set_configured(); + p.flow = &flow; + p.active = &active; + p.packet_flags = PKT_FROM_SERVER | PKT_TCP_INJECT_BLOCKED; + InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); + CHECK(counts->http_injects == 0); + CHECK(counts->failed_injects == 1); + CHECK(counts->err_stale_s2c_data == 1); + CHECK(status == ERR_STALE_S2C_DATA); + CHECK(flow.flow_state == Flow::FlowState::BLOCK); + const char* err_string = PayloadInjector::get_err_string(status); + STRCMP_EQUAL("S2C injection blocked: packet fills hole with pending out-of-order, " + "retransmitted, or overlapping segments", err_string); +} + +TEST(payload_injector_test, http_conflicting_s2c_traffic) +{ + Packet p(false); + set_configured(); + p.flow = &flow; + p.active = &active; + p.packet_flags = PKT_FROM_CLIENT; + MockSession session(&flow, true); + flow.session = &session; + InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); + CHECK(counts->http_injects == 0); + CHECK(counts->failed_injects == 1); + CHECK(counts->err_conflicting_s2c_traffic == 1); + CHECK(status == ERR_CONFLICTING_S2C_TRAFFIC); + CHECK(flow.flow_state == Flow::FlowState::BLOCK); + const char* err_string = PayloadInjector::get_err_string(status); + STRCMP_EQUAL("Conflicting S2C HTTP traffic in progress", err_string); + flow.session = nullptr; +} + +TEST(payload_injector_test, http_s2c_proto_blocked) +{ + Packet p(false); + set_configured(); + p.flow = &flow; + p.active = &active; + p.packet_flags = PKT_FROM_SERVER | PKT_HTTP_INJECT_BLOCKED; + InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); + CHECK(counts->http_injects == 0); + CHECK(counts->failed_injects == 1); + CHECK(counts->err_s2c_http_proto == 1); + CHECK(status == ERR_S2C_HTTP_PROTO); + CHECK(flow.flow_state == Flow::FlowState::BLOCK); + const char* err_string = PayloadInjector::get_err_string(status); + STRCMP_EQUAL("HTTP/1 injection blocked on server response due to protocol state conflict", + err_string); +} + +TEST(payload_injector_test, http_c2s_proto_blocked) +{ + Packet p(false); + set_configured(); + p.flow = &flow; + p.active = &active; + p.packet_flags = PKT_HTTP_INJECT_BLOCKED; + InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); + CHECK(counts->http_injects == 0); + CHECK(counts->failed_injects == 1); + CHECK(counts->err_c2s_http_proto == 1); + CHECK(status == ERR_C2S_HTTP_PROTO); + CHECK(flow.flow_state == Flow::FlowState::BLOCK); + const char* err_string = PayloadInjector::get_err_string(status); + STRCMP_EQUAL("HTTP/1 injection blocked on client request due to protocol state conflict", + err_string); +} + +TEST(payload_injector_test, http2_conflicting_s2c_traffic) +{ + Packet p(false); + set_configured(); + mock_api.base.name = "http2_inspect"; flow.gadget = new MockInspector(); p.flow = &flow; + p.active = &active; + p.packet_flags = PKT_FROM_CLIENT; + control.stream_id = 1; + MockSession session(&flow, true); + flow.session = &session; InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); - CHECK(status == ERR_PKT_FROM_SERVER); + CHECK(counts->http_injects == 0); + CHECK(counts->failed_injects == 1); + CHECK(counts->err_conflicting_s2c_traffic == 1); + CHECK(status == ERR_CONFLICTING_S2C_TRAFFIC); CHECK(flow.flow_state == Flow::FlowState::BLOCK); + const char* err_string = PayloadInjector::get_err_string(status); + STRCMP_EQUAL("Conflicting S2C HTTP traffic in progress", err_string); delete flow.gadget; + flow.session = nullptr; } -TEST(payload_injector_test, flow_is_null) +TEST(payload_injector_test, http2_s2c_proto_blocked) { Packet p(false); set_configured(); + mock_api.base.name = "http2_inspect"; + flow.gadget = new MockInspector(); + p.flow = &flow; + p.active = &active; + p.packet_flags = PKT_FROM_SERVER | PKT_HTTP_INJECT_BLOCKED; + control.stream_id = 1; InjectionReturnStatus status = PayloadInjector::inject_http_payload(&p, control); CHECK(counts->http_injects == 0); - CHECK(status == ERR_UNIDENTIFIED_PROTOCOL); + CHECK(counts->failed_injects == 1); + CHECK(counts->err_s2c_http2_proto == 1); + CHECK(status == ERR_S2C_HTTP2_PROTO); const char* err_string = PayloadInjector::get_err_string(status); - CHECK(strcmp(err_string, "Unidentified protocol") == 0); + STRCMP_EQUAL("HTTP/2 injection blocked on server response due to protocol state conflict", + err_string); + delete flow.gadget; } TEST_GROUP(payload_injector_translate_err_test) @@ -386,6 +552,7 @@ TEST_GROUP(payload_injector_translate_err_test) control.http_page_len = 4; flow.set_state(Flow::FlowState::INSPECT); flow.set_session_flags(SSNFLAG_ESTABLISHED); + flow.pkt_type = PktType::TCP; http2_flow_data.set_mid_frame(false); mock_api.base.name = "http2_inspect"; flow.gadget = new MockInspector(); @@ -409,8 +576,8 @@ TEST(payload_injector_translate_err_test, http2_page_translation_err) translation_status = ERR_PAGE_TRANSLATION; status = PayloadInjector::inject_http_payload(&p, control); const char* err_string = PayloadInjector::get_err_string(status); - CHECK(strcmp(err_string, "Error in translating HTTP block page to HTTP/2. " - "Unsupported or bad format.") == 0); + STRCMP_EQUAL("Error in translating HTTP block page to HTTP/2. " + "Unsupported or bad format.", err_string); } TEST(payload_injector_translate_err_test, http2_hdrs_size) @@ -421,23 +588,11 @@ TEST(payload_injector_translate_err_test, http2_hdrs_size) translation_status = ERR_TRANSLATED_HDRS_SIZE; status = PayloadInjector::inject_http_payload(&p, control); const char* err_string = PayloadInjector::get_err_string(status); - CHECK(strcmp(err_string, - "HTTP/2 translated header size is bigger than expected. Update max size.") == 0); + STRCMP_EQUAL("HTTP/2 translated header size is bigger than expected. Update max size.", + err_string); } -TEST(payload_injector_translate_err_test, conflicting_s2c_traffic) -{ - Packet p(false); - set_configured(); - p.flow = &flow; - translation_status = ERR_CONFLICTING_S2C_TRAFFIC; - status = PayloadInjector::inject_http_payload(&p, control); - const char* err_string = PayloadInjector::get_err_string(status); - CHECK(strcmp(err_string, - "Conflicting S2C HTTP traffic in progress") == 0); -} int main(int argc, char** argv) { return CommandLineTestRunner::RunAllTests(argc, argv); } - diff --git a/src/protocols/packet.h b/src/protocols/packet.h index acc4d5f19..92d851033 100644 --- a/src/protocols/packet.h +++ b/src/protocols/packet.h @@ -90,6 +90,13 @@ class SFDAQInstance; #define PKT_TCP_PSEUDO_EST 0x80000000 // A one-sided or bidirectional without LWS TCP session was detected +#define PKT_TCP_INJECT_BLOCKED 0x0000000100000000ULL // cannot be injected on react due to tcp packet creates a hole, + // fills a hole, has overlaps or is retransmission + +// used by payload_injector +#define PKT_HTTP_INJECT_ALLOWED 0x0000000200000000ULL +#define PKT_HTTP_INJECT_BLOCKED 0x0000000400000000ULL + #define TS_PKT_OFFLOADED 0x01 #define TS_PKT_INJECT 0x02 @@ -125,7 +132,7 @@ struct SO_PUBLIC Packet Endianness* endianness = nullptr; Obfuscator* obfuscator = nullptr; - uint32_t packet_flags; /* special flags for the packet */ + uint64_t packet_flags; /* special flags for the packet */ uint32_t xtradata_mask; uint32_t proto_bits; /* protocols contained within this packet */ @@ -340,6 +347,12 @@ struct SO_PUBLIC Packet bool was_set() const { return (packet_flags & PKT_WAS_SET) != 0; } + bool is_http_inject_permission_unset() const + { + return !(packet_flags & PKT_HTTP_INJECT_BLOCKED) and + !(packet_flags & PKT_HTTP_INJECT_ALLOWED); + } + bool is_detection_enabled(bool to_server); bool is_inter_group_flow() const diff --git a/src/pub_sub/test/pub_sub_http_transaction_end_event_test.cc b/src/pub_sub/test/pub_sub_http_transaction_end_event_test.cc index 134d1ee71..8402cfbe1 100644 --- a/src/pub_sub/test/pub_sub_http_transaction_end_event_test.cc +++ b/src/pub_sub/test/pub_sub_http_transaction_end_event_test.cc @@ -97,7 +97,7 @@ bool HttpInspect::get_buf(snort::InspectionBuffer::Type, snort::Packet*, snort:: const uint8_t* HttpInspect::adjust_log_packet(snort::Packet*, uint16_t&) { return nullptr; } StreamSplitter::Status HttpStreamSplitter::scan(snort::Packet*, const uint8_t*, uint32_t, uint32_t, uint32_t*) { return StreamSplitter::FLUSH; } -StreamSplitter::Status HttpStreamSplitter::scan(snort::Flow*, const uint8_t*, uint32_t, uint32_t*) +StreamSplitter::Status HttpStreamSplitter::scan(snort::Flow*, const uint8_t*, uint32_t, uint32_t*, snort::Packet*) { return StreamSplitter::FLUSH; } const snort::StreamBuffer HttpStreamSplitter::reassemble(snort::Flow*, unsigned, unsigned, const uint8_t*, unsigned, uint32_t, unsigned&) diff --git a/src/service_inspectors/http2_inspect/dev_notes.txt b/src/service_inspectors/http2_inspect/dev_notes.txt index 6362509e0..d772d1b77 100644 --- a/src/service_inspectors/http2_inspect/dev_notes.txt +++ b/src/service_inspectors/http2_inspect/dev_notes.txt @@ -102,3 +102,40 @@ Dynamically allocated objects related to http_inspect are considered separate an included. Temporary objects (frame_data and frame_header) are ignored. The remaining dynamically allocated are Http2Infractions (8 bytes * 2) and Http2EventsGen(24 bytes * 2) Therefore, the memory required by http2 per flow: sizeof(Http2FlowData) + 1645 + 16 + 48 + +*** HTTP/2 Block Page Injection *** +During server-to-client scan(), H2I determines whether block page injection is structurally +possible for the current packet and sets PKT_HTTP_INJECT_BLOCKED or PKT_HTTP_INJECT_ALLOWED on the +packet flags. The injection status is tracked per stream in a thread-local cache that is reset at +the start of each scan call when no injection permission has been set yet (i.e., the first time +scan() is called for this packet). A global_injection_block flag short-circuits further checks +once the scan enters a state where injection is structurally impossible. + +PKT_HTTP_INJECT_BLOCKED is set in the following cases: + +Per-stream injection status (set via update_injection_status). Only the first frame for +each stream ID in a packet is checked: +1. Too many streams tracked - the per-thread injection status cache has reached + MAX_STREAM_INJECTION_STATUS entries. Further streams are blocked to cap resource usage. + This is a sanity check; usually one packet will have only 1-3 different HTTP/2 streams. +2. Frame type is not HEADERS - injection is only allowed when the server-to-client frame for the + stream is a HEADERS frame. Data, settings, window update, and other frame types block injection + because the client won't be able to interpret the block page. +3. Server stream state is not STREAM_EXPECT_HEADERS - even if the frame is HEADERS, the server + direction of the stream must be expecting headers (i.e., no prior server frames on this stream). + If the server has already sent headers or data (e.g., when processing trailers), injection is + blocked. +4. Stream not found and no matching client stream - if the stream object does not exist and there + is no unflushed client data for this stream ID, injection is blocked. + +Structural injection block (global_injection_block is set to true, skipping all further +per-stream checks for the remainder of the scan call): + +5. Packet starts with trailing frame padding - the scan is discarding padding that trails a + previous frame. +6. Packet starts with padding length byte - the scan is reading the padding length field of a + padded frame. +7. Packet starts with data frame body - the scan is processing frame data + +Cases 5-7 all indicate that the server-to-client flow is in the middle of a frame, making +injection structurally impossible. diff --git a/src/service_inspectors/http2_inspect/http2_enum.h b/src/service_inspectors/http2_inspect/http2_enum.h index 43213b2fb..f71a2ad02 100644 --- a/src/service_inspectors/http2_inspect/http2_enum.h +++ b/src/service_inspectors/http2_inspect/http2_enum.h @@ -32,6 +32,10 @@ static const uint32_t NO_STREAM_ID = 0xFFFFFFFF; // Perform memory allocation and deallocation tracking for Http2Stream objects in increments of 25 static const uint32_t STREAM_MEMORY_TRACKING_INCREMENT = 25; +// Maximum number of distinct stream IDs tracked per scan for injection status. +// If exceeded, injection is blocked to avoid expensive stream lookups. +static const uint32_t MAX_STREAM_INJECTION_STATUS = 20; + static const uint32_t HTTP2_GID = 121; // Frame type codes (fourth octet of frame header) @@ -193,6 +197,8 @@ enum SettingsFrameIds }; enum ScanState { SCAN_FRAME_HEADER, SCAN_PADDING_LENGTH, SCAN_DATA, SCAN_EMPTY_DATA }; + +enum InjectionStatus { INJECTION_BLOCKED = 0, INJECTION_ALLOWED = 1 }; } // end namespace Http2Enums #endif diff --git a/src/service_inspectors/http2_inspect/http2_flow_data.h b/src/service_inspectors/http2_inspect/http2_flow_data.h index 28dca5887..1da56552b 100644 --- a/src/service_inspectors/http2_inspect/http2_flow_data.h +++ b/src/service_inspectors/http2_inspect/http2_flow_data.h @@ -21,7 +21,6 @@ #define HTTP2_FLOW_DATA_H #include -#include #include "flow/flow.h" #include "flow/stream_flow.h" diff --git a/src/service_inspectors/http2_inspect/http2_stream_splitter.cc b/src/service_inspectors/http2_inspect/http2_stream_splitter.cc index 768a7bb42..7160639ab 100644 --- a/src/service_inspectors/http2_inspect/http2_stream_splitter.cc +++ b/src/service_inspectors/http2_inspect/http2_stream_splitter.cc @@ -101,7 +101,7 @@ StreamSplitter::Status Http2StreamSplitter::scan(Packet* pkt, const uint8_t* dat return HttpStreamSplitter::status_value(StreamSplitter::ABORT, true); StreamSplitter::Status ret_val = - implement_scan(session_data, data, length, flush_offset, source_id); + implement_scan(session_data, data, length, flush_offset, source_id, pkt); session_data->bytes_scanned[source_id] += (ret_val == StreamSplitter::FLUSH)? *flush_offset : length; diff --git a/src/service_inspectors/http2_inspect/http2_stream_splitter.h b/src/service_inspectors/http2_inspect/http2_stream_splitter.h index e9013868c..2f1ad449f 100644 --- a/src/service_inspectors/http2_inspect/http2_stream_splitter.h +++ b/src/service_inspectors/http2_inspect/http2_stream_splitter.h @@ -54,12 +54,16 @@ private: uint32_t length, uint32_t* flush_offset, HttpCommon::SourceId source_id, uint8_t type, uint8_t frame_flags, uint32_t& data_offset); static snort::StreamSplitter::Status implement_scan(Http2FlowData* session_data, const uint8_t* data, - uint32_t length, uint32_t* flush_offset, HttpCommon::SourceId source_id); + uint32_t length, uint32_t* flush_offset, HttpCommon::SourceId source_id, snort::Packet* pkt); static const snort::StreamBuffer implement_reassemble(Http2FlowData* session_data, unsigned total, unsigned offset, const uint8_t* data, unsigned len, uint32_t flags, HttpCommon::SourceId source_id); static bool read_frame_hdr(Http2FlowData* session_data, const uint8_t* data, uint32_t length, HttpCommon::SourceId source_id, uint32_t& data_offset); + static Http2Enums::InjectionStatus determine_injection_status(Http2FlowData* session_data, + uint8_t frame_type, uint32_t stream_id); + static void update_injection_status(Http2FlowData* session_data, snort::Packet* pkt, + uint8_t frame_type); }; diff --git a/src/service_inspectors/http2_inspect/http2_stream_splitter_impl.cc b/src/service_inspectors/http2_inspect/http2_stream_splitter_impl.cc index 5c9180176..58b8ddc7c 100644 --- a/src/service_inspectors/http2_inspect/http2_stream_splitter_impl.cc +++ b/src/service_inspectors/http2_inspect/http2_stream_splitter_impl.cc @@ -23,6 +23,8 @@ #include "http2_stream_splitter.h" +#include +#include #include #include "service_inspectors/http_inspect/http_common.h" @@ -39,6 +41,11 @@ using namespace snort; using namespace HttpCommon; using namespace Http2Enums; +static THREAD_LOCAL std::array, + Http2Enums::MAX_STREAM_INJECTION_STATUS> stream_injection_status; +static THREAD_LOCAL uint32_t stream_injection_status_count = 0; +static THREAD_LOCAL bool global_injection_block = false; + enum ValidationResult { V_GOOD, V_BAD, V_TBD }; static ValidationResult validate_preface(const uint8_t* data, const uint32_t length, @@ -63,6 +70,59 @@ static ValidationResult validate_preface(const uint8_t* data, const uint32_t len return V_GOOD; } +Http2Enums::InjectionStatus Http2StreamSplitter::determine_injection_status(Http2FlowData* session_data, + uint8_t frame_type, uint32_t stream_id) +{ + if (frame_type != FT_HEADERS) + return Http2Enums::INJECTION_BLOCKED; + + const Http2Stream* const stream = session_data->find_stream(stream_id); + + if (!stream) + { + // Check if we have some unflushed data from client + const uint32_t client_stream_id = session_data->current_stream[SRC_CLIENT]; + return (client_stream_id == stream_id) ? + Http2Enums::INJECTION_ALLOWED : Http2Enums::INJECTION_BLOCKED; + } + + return (stream->get_state(SRC_SERVER) == STREAM_EXPECT_HEADERS) ? + Http2Enums::INJECTION_ALLOWED : Http2Enums::INJECTION_BLOCKED; +} + +void Http2StreamSplitter::update_injection_status(Http2FlowData* session_data, Packet* pkt, + uint8_t frame_type) +{ + pkt->packet_flags &= ~(PKT_HTTP_INJECT_ALLOWED | PKT_HTTP_INJECT_BLOCKED); + + if (stream_injection_status_count >= MAX_STREAM_INJECTION_STATUS) + { + pkt->packet_flags |= PKT_HTTP_INJECT_BLOCKED; + return; + } + + const uint32_t current_stream_id = session_data->current_stream[SRC_SERVER]; + const auto begin = stream_injection_status.begin(); + const auto end = begin + stream_injection_status_count; + auto it = std::find_if(begin, end, + [current_stream_id](const std::pair& entry) + { return entry.first == current_stream_id; }); + + Http2Enums::InjectionStatus verdict; + if (it != end) + { + verdict = it->second; + } + else + { + verdict = determine_injection_status(session_data, frame_type, current_stream_id); + stream_injection_status[stream_injection_status_count++] = { current_stream_id, verdict }; + } + + pkt->packet_flags |= (verdict == Http2Enums::INJECTION_ALLOWED) ? + PKT_HTTP_INJECT_ALLOWED : PKT_HTTP_INJECT_BLOCKED; +} + void Http2StreamSplitter::data_frame_header_checks(Http2FlowData* session_data, HttpCommon::SourceId source_id) { @@ -135,7 +195,7 @@ bool Http2StreamSplitter::read_frame_hdr(Http2FlowData* session_data, const uint } StreamSplitter::Status Http2StreamSplitter::implement_scan(Http2FlowData* session_data, - const uint8_t* data, uint32_t length, uint32_t* flush_offset, HttpCommon::SourceId source_id) + const uint8_t* data, uint32_t length, uint32_t* flush_offset, HttpCommon::SourceId source_id, Packet* pkt) { if (session_data->preface[source_id]) { @@ -166,6 +226,12 @@ StreamSplitter::Status Http2StreamSplitter::implement_scan(Http2FlowData* sessio *flush_offset = 0; uint32_t data_offset = 0; + if (source_id == SRC_SERVER and pkt->is_http_inject_permission_unset()) + { + stream_injection_status_count = 0; + global_injection_block = false; + } + // Need to process multiple frames in a single scan() if a single TCP segment has multiple // header and continuation frames while ((status == StreamSplitter::SEARCH) && @@ -186,6 +252,11 @@ StreamSplitter::Status Http2StreamSplitter::implement_scan(Http2FlowData* sessio session_data->scan_remaining_frame_octets[source_id] -= avail; session_data->payload_discard[source_id] = true; *flush_offset = avail; + if (source_id == SRC_SERVER and pkt->is_http_inject_permission_unset()) + { + pkt->packet_flags |= PKT_HTTP_INJECT_BLOCKED; + global_injection_block = true; + } return StreamSplitter::FLUSH; } @@ -198,6 +269,9 @@ StreamSplitter::Status Http2StreamSplitter::implement_scan(Http2FlowData* sessio session_data->current_stream[source_id] = get_stream_id_from_header(session_data->scan_frame_header[source_id]); + if (source_id == SRC_SERVER and !global_injection_block) + update_injection_status(session_data, pkt, type); + if (session_data->continuation_expected[source_id] && ((type != FT_CONTINUATION) || (old_stream_id != session_data->current_stream[source_id]))) @@ -284,6 +358,12 @@ StreamSplitter::Status Http2StreamSplitter::implement_scan(Http2FlowData* sessio break; } case SCAN_PADDING_LENGTH: + if (source_id == SRC_SERVER and pkt->is_http_inject_permission_unset()) + { + pkt->packet_flags |= PKT_HTTP_INJECT_BLOCKED; + global_injection_block = true; + } + assert(session_data->scan_remaining_frame_octets[source_id] > 0); session_data->padding_length[source_id] = *(data + data_offset); if (session_data->frame_type[source_id] == FT_DATA) @@ -314,6 +394,12 @@ StreamSplitter::Status Http2StreamSplitter::implement_scan(Http2FlowData* sessio case SCAN_DATA: case SCAN_EMPTY_DATA: { + if (source_id == SRC_SERVER and pkt->is_http_inject_permission_unset()) + { + pkt->packet_flags |= PKT_HTTP_INJECT_BLOCKED; + global_injection_block = true; + } + const uint8_t type = get_frame_type(session_data->scan_frame_header[source_id]); const uint8_t frame_flags = get_frame_flags(session_data-> scan_frame_header[source_id]); diff --git a/src/service_inspectors/http_inspect/dev_notes.txt b/src/service_inspectors/http_inspect/dev_notes.txt index d6f0c0035..eaa1ffda1 100755 --- a/src/service_inspectors/http_inspect/dev_notes.txt +++ b/src/service_inspectors/http_inspect/dev_notes.txt @@ -65,3 +65,7 @@ include::dev_notes_mime_inspection.txt[] ==== HI test tool usage include::dev_notes_test_tool.txt[] + +==== HTTP/1 Block Page Injection + +include::dev_notes_block_page_injection.txt[] diff --git a/src/service_inspectors/http_inspect/dev_notes_block_page_injection.txt b/src/service_inspectors/http_inspect/dev_notes_block_page_injection.txt new file mode 100644 index 000000000..b23a4e5b2 --- /dev/null +++ b/src/service_inspectors/http_inspect/dev_notes_block_page_injection.txt @@ -0,0 +1,7 @@ +During scan(), HI determines whether block page injection is possible for the current packet and +sets PKT_HTTP_INJECT_ALLOWED or PKT_HTTP_INJECT_BLOCKED on the packet flags. This check is +performed once per packet when no injection permission has been set yet. + +PKT_HTTP_INJECT_ALLOWED is set when the server side is expecting a fresh response, i.e., +type_expected is SEC_STATUS and no response cutter has been created yet. In all other cases +PKT_HTTP_INJECT_BLOCKED is set. \ No newline at end of file diff --git a/src/service_inspectors/http_inspect/http_stream_splitter.h b/src/service_inspectors/http_inspect/http_stream_splitter.h index 14208ba46..0079cbf52 100644 --- a/src/service_inspectors/http_inspect/http_stream_splitter.h +++ b/src/service_inspectors/http_inspect/http_stream_splitter.h @@ -39,7 +39,8 @@ public: source_id(is_client_to_server ? HttpCommon::SRC_CLIENT : HttpCommon::SRC_SERVER) {} Status scan(snort::Packet* pkt, const uint8_t* data, uint32_t length, uint32_t not_used, uint32_t* flush_offset) override; - Status scan(snort::Flow* flow, const uint8_t* data, uint32_t length, uint32_t* flush_offset) override; + Status scan(snort::Flow* flow, const uint8_t* data, uint32_t length, uint32_t* flush_offset, + snort::Packet* pkt = nullptr) override; 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; diff --git a/src/service_inspectors/http_inspect/http_stream_splitter_base.h b/src/service_inspectors/http_inspect/http_stream_splitter_base.h index 3ba6c0c8a..b85434c35 100644 --- a/src/service_inspectors/http_inspect/http_stream_splitter_base.h +++ b/src/service_inspectors/http_inspect/http_stream_splitter_base.h @@ -30,7 +30,8 @@ public: virtual void prep_partial_flush(snort::Flow* flow, uint32_t num_flush) = 0; - virtual Status scan(snort::Flow* flow, const uint8_t* data, uint32_t length, uint32_t* flush_offset) = 0; + virtual Status scan(snort::Flow* flow, const uint8_t* data, uint32_t length, uint32_t* flush_offset, + snort::Packet* pkt = nullptr) = 0; protected: HttpStreamSplitterBase(bool c2s) : StreamSplitter(c2s) { } private: diff --git a/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc b/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc index 0d66da6c1..3bf24612a 100644 --- a/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc +++ b/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc @@ -133,7 +133,7 @@ StreamSplitter::Status HttpStreamSplitter::status_value(StreamSplitter::Status r StreamSplitter::Status HttpStreamSplitter::scan(Packet* pkt, const uint8_t* data, uint32_t length, uint32_t, uint32_t* flush_offset) { - return scan(pkt->flow, data, length, flush_offset); + return scan(pkt->flow, data, length, flush_offset, pkt); } StreamSplitter::Status HttpStreamSplitter::handle_zero_nine(Flow* flow, HttpFlowData* session_data, const uint8_t* data, uint32_t length, @@ -302,7 +302,7 @@ StreamSplitter::Status HttpStreamSplitter::call_cutter(Flow* flow, HttpFlowData* } StreamSplitter::Status HttpStreamSplitter::scan(Flow* flow, const uint8_t* data, uint32_t length, - uint32_t* flush_offset) + uint32_t* flush_offset, Packet* pkt) { Profile profile(HttpModule::get_profile_stats()); // cppcheck-suppress unreadVariable @@ -394,6 +394,15 @@ StreamSplitter::Status HttpStreamSplitter::scan(Flow* flow, const uint8_t* data, HttpModule::increment_peg_counts(PEG_SCAN); + if (pkt and pkt->is_http_inject_permission_unset()) + { + if (session_data->type_expected[SRC_SERVER] == SEC_STATUS + and session_data->cutter[SRC_SERVER] == nullptr) + pkt->packet_flags |= PKT_HTTP_INJECT_ALLOWED; + else + pkt->packet_flags |= PKT_HTTP_INJECT_BLOCKED; + } + return call_cutter(flow, session_data, data, length, flush_offset, type); } diff --git a/src/service_inspectors/http_inspect/test/http_transaction_test.cc b/src/service_inspectors/http_inspect/test/http_transaction_test.cc index bbff69882..959706d78 100644 --- a/src/service_inspectors/http_inspect/test/http_transaction_test.cc +++ b/src/service_inspectors/http_inspect/test/http_transaction_test.cc @@ -106,7 +106,7 @@ bool HttpInspect::get_buf(snort::InspectionBuffer::Type, snort::Packet*, snort:: const uint8_t* HttpInspect::adjust_log_packet(snort::Packet*, uint16_t&) { return nullptr; } StreamSplitter::Status HttpStreamSplitter::scan(snort::Packet*, const uint8_t*, uint32_t, uint32_t, uint32_t*) { return StreamSplitter::FLUSH; } -StreamSplitter::Status HttpStreamSplitter::scan(snort::Flow*, const uint8_t*, uint32_t, uint32_t*) +StreamSplitter::Status HttpStreamSplitter::scan(snort::Flow*, const uint8_t*, uint32_t, uint32_t*, Packet*) { return StreamSplitter::FLUSH; } const snort::StreamBuffer HttpStreamSplitter::reassemble(snort::Flow*, unsigned, unsigned, const uint8_t*, unsigned, uint32_t, unsigned&) diff --git a/src/stream/tcp/tcp_reassembly_segments.cc b/src/stream/tcp/tcp_reassembly_segments.cc index 80cd35a21..969d06d39 100644 --- a/src/stream/tcp/tcp_reassembly_segments.cc +++ b/src/stream/tcp/tcp_reassembly_segments.cc @@ -194,6 +194,9 @@ void TcpReassemblySegments::insert_segment_in_seglist(TcpSegmentDescriptor& tsd) return; } + // Packet is problematic, may have overlaps/retransmission, fill the hole, create a hole + tsd.set_packet_flags(PKT_TCP_INJECT_BLOCKED); + tos->init(tsd); overlap_resolver->eval_left(*tos); overlap_resolver->eval_right(*tos); diff --git a/src/stream/tcp/tcp_segment_descriptor.h b/src/stream/tcp/tcp_segment_descriptor.h index ee019a28b..4b0d5bea1 100644 --- a/src/stream/tcp/tcp_segment_descriptor.h +++ b/src/stream/tcp/tcp_segment_descriptor.h @@ -128,10 +128,10 @@ public: pkt->dsize -= offset; } - void set_packet_flags(uint32_t flags) const + void set_packet_flags(uint64_t flags) const { pkt->packet_flags |= flags; } - bool are_packet_flags_set(uint32_t flags) const + bool are_packet_flags_set(uint64_t flags) const { return (pkt->packet_flags & flags) == flags; } uint32_t get_packet_timestamp() const