From: Davis McPherson -X (davmcphe - XORIANT CORPORATION at Cisco) Date: Wed, 6 Mar 2024 14:07:31 +0000 (+0000) Subject: Pull request #4227: stream_tcp: implement support for proxy mode normalization behavior X-Git-Tag: 3.1.82.0~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b4fcc3cc3401d0027caf4f145198815415bd6125;p=thirdparty%2Fsnort3.git Pull request #4227: stream_tcp: implement support for proxy mode normalization behavior Merge in SNORT/snort3 from ~DAVMCPHE/snort3:stream_tcp_proxy to master Squashed commit of the following: commit 82260056aa6c8e53a7d6fed23e77ebaf75d8c337 Author: davis mcpherson Date: Thu Feb 22 11:44:08 2024 -0500 stream_tcp: implement support for proxy mode normalization behavior --- diff --git a/src/stream/stream.cc b/src/stream/stream.cc index 4f1aba342..ccc6c527c 100644 --- a/src/stream/stream.cc +++ b/src/stream/stream.cc @@ -233,6 +233,14 @@ int Stream::ignore_flow( ctrlPkt, type, ip_proto, srcIP, srcPort, dstIP, dstPort, direction, fd); } +void Stream::start_proxy(Flow* flow) +{ + assert(flow and flow->session and flow->pkt_type == PktType::TCP); + + TcpStreamSession* tcp_session = (TcpStreamSession*)flow->session; + tcp_session->start_proxy(); +} + void Stream::stop_inspection( Flow* flow, Packet* p, char dir, int32_t /*bytes*/, int /*response*/) diff --git a/src/stream/stream.h b/src/stream/stream.h index b9656980e..2c03e8762 100644 --- a/src/stream/stream.h +++ b/src/stream/stream.h @@ -114,6 +114,10 @@ public: // packet_flags field of the Packet struct to indicate the direction determined. static uint32_t get_packet_direction(Packet*); + // Set the stream normalization mode to PROXY. In this mode wire packets go thru a proxy before snort + // sees them. All stream normalizations are turned off in this mode. + static void start_proxy(Flow*); + // Stop inspection on a flow for up to count bytes (-1 to ignore for life or until resume). // If response flag is set, automatically resume inspection up to count bytes when a data // packet in the other direction is seen. Also marks the packet to be ignored diff --git a/src/stream/tcp/tcp_module.cc b/src/stream/tcp/tcp_module.cc index 93e8fdb0f..49ece67d9 100644 --- a/src/stream/tcp/tcp_module.cc +++ b/src/stream/tcp/tcp_module.cc @@ -116,6 +116,7 @@ const PegInfo tcp_pegs[] = { CountType::MAX, "max_bytes", "maximum number of bytes queued in any flow" }, { CountType::SUM, "zero_len_tcp_opt", "number of zero length tcp options" }, { CountType::SUM, "zero_win_probes", "number of tcp zero window probes" }, + { CountType::SUM, "proxy_mode_flows", "number of flows set to proxy normalization policy" }, { CountType::END, nullptr, nullptr } }; diff --git a/src/stream/tcp/tcp_module.h b/src/stream/tcp/tcp_module.h index 07a6c1a0d..3188ffc39 100644 --- a/src/stream/tcp/tcp_module.h +++ b/src/stream/tcp/tcp_module.h @@ -116,6 +116,7 @@ struct TcpStats PegCount max_bytes; PegCount zero_len_tcp_opt; PegCount zero_win_probes; + PegCount proxy_mode_flows; }; extern THREAD_LOCAL struct TcpStats tcpStats; diff --git a/src/stream/tcp/tcp_normalizer.cc b/src/stream/tcp/tcp_normalizer.cc index 1f94bb8ab..586925ab4 100644 --- a/src/stream/tcp/tcp_normalizer.cc +++ b/src/stream/tcp/tcp_normalizer.cc @@ -25,6 +25,8 @@ #include "tcp_normalizer.h" +#include "stream/stream.h" + #include "tcp_module.h" #include "tcp_stream_session.h" #include "tcp_stream_tracker.h" @@ -32,6 +34,69 @@ using namespace snort; +TcpNormalizer::NormStatus TcpNormalizer::apply_normalizations( + TcpNormalizerState& tns, TcpSegmentDescriptor& tsd, uint32_t seq, bool stream_is_inorder) +{ + // if this is a midstream pickup then skip normalizations + if ( Stream::is_midstream(tsd.get_flow()) ) + return NORM_OK; + + // these normalizations can't be done if we missed setup. and + // window is zero in one direction until we've seen both sides. + if ( tsd.get_flow()->two_way_traffic() ) + { + // drop packet if sequence num is invalid + if ( !tns.tracker->is_segment_seq_valid(tsd) ) + { + tcpStats.invalid_seq_num++; + trim_win_payload(tns, tsd); + return NORM_BAD_SEQ; + } + + // trim to fit in listener's window and mss + trim_win_payload(tns, tsd, + (tns.tracker->r_win_base + tns.tracker->get_snd_wnd() - tns.tracker->rcv_nxt)); + + if ( tns.tracker->get_mss() ) + trim_mss_payload(tns, tsd, tns.tracker->get_mss()); + + ecn_stripper(tns, tsd); + } + + if ( stream_is_inorder ) + { + if ( get_stream_window(tns, tsd) == 0 ) + { + if ( !data_inside_window(tns, tsd) ) + { + trim_win_payload(tns, tsd, 0, tsd.is_nap_policy_inline()); + return NORM_TRIMMED; + } + + if ( tns.tracker->get_iss() ) + { + tcpStats.zero_win_probes++; + set_zwp_seq(tns, seq); + trim_win_payload(tns, tsd, MAX_ZERO_WIN_PROBE_LEN, tsd.is_nap_policy_inline()); + } + } + } + else if ( get_stream_window(tns, tsd) == 0 ) + { + if ( SEQ_EQ(seq, get_zwp_seq(tns)) ) + { + tcpStats.zero_win_probes++; + trim_win_payload(tns, tsd, MAX_ZERO_WIN_PROBE_LEN, tsd.is_nap_policy_inline()); + return NORM_TRIMMED; + } + + trim_win_payload(tns, tsd, 0, tsd.is_nap_policy_inline()); + return NORM_TRIMMED; + } + + return NORM_OK; +} + bool TcpNormalizer::trim_payload(TcpNormalizerState&, TcpSegmentDescriptor& tsd, uint32_t max, NormMode mode, PegCounts peg, bool force) { diff --git a/src/stream/tcp/tcp_normalizer.h b/src/stream/tcp/tcp_normalizer.h index de331344c..10b119af9 100644 --- a/src/stream/tcp/tcp_normalizer.h +++ b/src/stream/tcp/tcp_normalizer.h @@ -61,10 +61,14 @@ class TcpNormalizer { public: using State = TcpNormalizerState; + enum NormStatus { NORM_BAD_SEQ = -1, NORM_TRIMMED = 0, NORM_OK = 1 }; virtual ~TcpNormalizer() = default; virtual void init(State&) { } + + virtual NormStatus apply_normalizations( + State&, TcpSegmentDescriptor&, uint32_t seq, bool stream_is_inorder); virtual void session_blocker(State&, TcpSegmentDescriptor&); virtual bool packet_dropper(State&, TcpSegmentDescriptor&, NormFlags); virtual bool trim_syn_payload(State&, TcpSegmentDescriptor&, uint32_t max = 0); @@ -86,6 +90,9 @@ public: static void reset_stats(); + std::string& get_name() + { return my_name; } + protected: TcpNormalizer() = default; @@ -101,6 +108,8 @@ protected: virtual bool is_paws_ts_checked_required(State&, TcpSegmentDescriptor&); virtual int validate_paws(State&, TcpSegmentDescriptor&); virtual int handle_paws_no_timestamps(State&, TcpSegmentDescriptor&); + + std::string my_name; }; #endif diff --git a/src/stream/tcp/tcp_normalizers.cc b/src/stream/tcp/tcp_normalizers.cc index fb44a089c..4baa058db 100644 --- a/src/stream/tcp/tcp_normalizers.cc +++ b/src/stream/tcp/tcp_normalizers.cc @@ -35,7 +35,9 @@ using namespace snort; class TcpNormalizerFirst : public TcpNormalizer { public: - TcpNormalizerFirst() = default; + TcpNormalizerFirst() + { my_name = "OS_First"; } + int handle_repeated_syn(TcpNormalizerState&, TcpSegmentDescriptor&) override; }; @@ -43,7 +45,9 @@ public: class TcpNormalizerLast : public TcpNormalizer { public: - TcpNormalizerLast() = default; + TcpNormalizerLast() + { my_name = "OS_Last"; } + int handle_repeated_syn(TcpNormalizerState&, TcpSegmentDescriptor&) override; }; @@ -51,7 +55,9 @@ public: class TcpNormalizerLinux : public TcpNormalizer { public: - TcpNormalizerLinux() = default; + TcpNormalizerLinux() + { my_name = "OS_Linux"; } + void init(TcpNormalizerState& tns) override { @@ -67,7 +73,9 @@ public: class TcpNormalizerOldLinux : public TcpNormalizer { public: - TcpNormalizerOldLinux() = default; + TcpNormalizerOldLinux() + { my_name = "OS_OldLinux"; } + void init(TcpNormalizerState& tns) override { tns.paws_drop_zero_ts = false; } @@ -80,7 +88,9 @@ public: class TcpNormalizerBSD : public TcpNormalizer { public: - TcpNormalizerBSD() = default; + TcpNormalizerBSD() + { my_name = "OS_BSD"; } + bool validate_rst(TcpNormalizerState&, TcpSegmentDescriptor&) override; int handle_repeated_syn(TcpNormalizerState&, TcpSegmentDescriptor&) override; @@ -89,7 +99,9 @@ public: class TcpNormalizerMacOS : public TcpNormalizer { public: - TcpNormalizerMacOS() = default; + TcpNormalizerMacOS() + { my_name = "OS_MacOS"; } + int handle_repeated_syn(TcpNormalizerState&, TcpSegmentDescriptor&) override; }; @@ -97,7 +109,9 @@ public: class TcpNormalizerSolaris : public TcpNormalizer { public: - TcpNormalizerSolaris() = default; + TcpNormalizerSolaris() + { my_name = "OS_Solaris"; } + void init(TcpNormalizerState& tns) override { tns.paws_drop_zero_ts = false; } @@ -109,7 +123,9 @@ public: class TcpNormalizerIrix : public TcpNormalizer { public: - TcpNormalizerIrix() = default; + TcpNormalizerIrix() + { my_name = "OS_Irix"; } + int handle_repeated_syn(TcpNormalizerState&, TcpSegmentDescriptor&) override; }; @@ -117,7 +133,9 @@ public: class TcpNormalizerHpux11 : public TcpNormalizer { public: - TcpNormalizerHpux11() = default; + TcpNormalizerHpux11() + { my_name = "OS_Hpux11"; } + bool validate_rst(TcpNormalizerState&, TcpSegmentDescriptor&) override; bool is_paws_ts_checked_required(TcpNormalizerState&, TcpSegmentDescriptor&) override; @@ -127,7 +145,9 @@ public: class TcpNormalizerHpux10 : public TcpNormalizer { public: - TcpNormalizerHpux10() = default; + TcpNormalizerHpux10() + { my_name = "OS_Hpux10"; } + int handle_repeated_syn(TcpNormalizerState&, TcpSegmentDescriptor&) override; }; @@ -135,7 +155,9 @@ public: class TcpNormalizerWindows : public TcpNormalizer { public: - TcpNormalizerWindows() = default; + TcpNormalizerWindows() + { my_name = "OS_Windows"; } + void init(TcpNormalizerState& tns) override { tns.paws_drop_zero_ts = false; } @@ -147,7 +169,9 @@ public: class TcpNormalizerWindows2K3 : public TcpNormalizer { public: - TcpNormalizerWindows2K3() = default; + TcpNormalizerWindows2K3() + { my_name = "OS_Windows2K3"; } + void init(TcpNormalizerState& tns) override { tns.paws_drop_zero_ts = false; } @@ -159,7 +183,9 @@ public: class TcpNormalizerVista : public TcpNormalizer { public: - TcpNormalizerVista() = default; + TcpNormalizerVista() + { my_name = "OS_Vista"; } + void init(TcpNormalizerState& tns) override { tns.paws_drop_zero_ts = false; } @@ -171,8 +197,11 @@ public: class TcpNormalizerProxy : public TcpNormalizer { public: - TcpNormalizerProxy() = default; + TcpNormalizerProxy() + { my_name = "OS_Proxy"; } + TcpNormalizer::NormStatus apply_normalizations( + TcpNormalizerState&, TcpSegmentDescriptor&, uint32_t seq, bool stream_is_inorder) override; bool validate_rst(TcpNormalizerState&, TcpSegmentDescriptor&) override; int handle_paws(TcpNormalizerState&, TcpSegmentDescriptor&) override; int handle_repeated_syn(TcpNormalizerState&, TcpSegmentDescriptor&) override; @@ -362,8 +391,8 @@ bool TcpNormalizerHpux11::is_paws_ts_checked_required( TcpNormalizerState& tns, TcpSegmentDescriptor& tsd) { /* HPUX 11 ignores timestamps for out of order segments */ - if ( (tns.tracker->get_tf_flags() & TF_MISSING_PKT) || !SEQ_EQ(tns.tracker->rcv_nxt, - tsd.get_seq()) ) + if ( (tns.tracker->get_tf_flags() & TF_MISSING_PKT) + || !SEQ_EQ(tns.tracker->rcv_nxt, tsd.get_seq()) ) return false; else return true; @@ -417,11 +446,17 @@ int TcpNormalizerVista::handle_repeated_syn( return handle_repeated_syn_mswin(tns.peer_tracker, tns.tracker, tsd, tns.session); } +TcpNormalizer::NormStatus TcpNormalizerProxy::apply_normalizations( + TcpNormalizerState&, TcpSegmentDescriptor&, uint32_t, bool) +{ + // when Proxy policy is active packet normalizations are skipped + return NORM_OK; +} + bool TcpNormalizerProxy::validate_rst( TcpNormalizerState&, TcpSegmentDescriptor&) - { - return false; + return true; } int TcpNormalizerProxy::handle_paws( diff --git a/src/stream/tcp/tcp_normalizers.h b/src/stream/tcp/tcp_normalizers.h index 128319436..2715100d0 100644 --- a/src/stream/tcp/tcp_normalizers.h +++ b/src/stream/tcp/tcp_normalizers.h @@ -50,6 +50,9 @@ public: void reset() { init(StreamPolicy::OS_DEFAULT, nullptr, nullptr, nullptr); } + TcpNormalizer::NormStatus apply_normalizations(TcpSegmentDescriptor& tsd, uint32_t seq, bool stream_is_inorder) + { return norm->apply_normalizations(tns, tsd, seq, stream_is_inorder); } + void session_blocker(TcpSegmentDescriptor& tsd) { norm->session_blocker(tns, tsd); } diff --git a/src/stream/tcp/tcp_session.cc b/src/stream/tcp/tcp_session.cc index 9498414cc..b1773731b 100644 --- a/src/stream/tcp/tcp_session.cc +++ b/src/stream/tcp/tcp_session.cc @@ -410,29 +410,6 @@ bool TcpSession::flow_exceeds_config_thresholds(TcpSegmentDescriptor& tsd) return false; } -void TcpSession::process_tcp_stream(TcpSegmentDescriptor& tsd) -{ - if ( tsd.are_packet_flags_set(PKT_IGNORE) ) - return; - - set_packet_header_foo(tsd); - - if ( flow_exceeds_config_thresholds(tsd) ) - return; - - TcpStreamTracker* listener = tsd.get_listener(); - - listener->reassembler.queue_packet_for_reassembly(tsd); - - // Alert if overlap limit exceeded - if ( (tcp_config->overlap_limit) - && (listener->reassembler.get_overlap_count() > tcp_config->overlap_limit) ) - { - tel.set_tcp_event(EVENT_EXCESSIVE_OVERLAP); - listener->reassembler.set_overlap_count(0); - } -} - void TcpSession::update_stream_order(const TcpSegmentDescriptor& tsd, bool aligned) { TcpStreamTracker* listener = tsd.get_listener(); @@ -483,79 +460,6 @@ void TcpSession::update_stream_order(const TcpSegmentDescriptor& tsd, bool align } } -int TcpSession::process_tcp_data(TcpSegmentDescriptor& tsd) -{ - TcpStreamTracker* listener = tsd.get_listener(); - const tcp::TCPHdr* tcph = tsd.get_tcph(); - uint32_t seq = tsd.get_seq(); - - if ( tcph->is_syn() ) - seq++; - - /* we're aligned, so that's nice anyway */ - if (seq == listener->rcv_nxt) - { - /* check if we're in the window */ - if ( tcp_config->policy != StreamPolicy::OS_PROXY - and !Stream::is_midstream(flow) and listener->normalizer.get_stream_window(tsd) == 0 ) - { - if ( !listener->normalizer.data_inside_window(tsd) ) - { - listener->normalizer.trim_win_payload(tsd, 0, tsd.is_nap_policy_inline()); - return STREAM_UNALIGNED; - } - - if( listener->get_iss() ) - { - tcpStats.zero_win_probes++; - listener->normalizer.set_zwp_seq(seq); - listener->normalizer.trim_win_payload(tsd, MAX_ZERO_WIN_PROBE_LEN, tsd.is_nap_policy_inline()); - } - } - - /* move the ack boundary up, this is the only way we'll accept data */ - // FIXIT-L for ips, must move all the way to first hole or right end - listener->rcv_nxt = tsd.get_end_seq(); - - if ( tsd.is_data_segment() ) - { - update_stream_order(tsd, true); - process_tcp_stream(tsd); - return STREAM_ALIGNED; - } - } - else - { - // pkt is out of order, do some target-based shizzle here... - // NO, we don't want to simply bail. Some platforms favor unack'd dup data over the - // original data. Let the reassembly policy decide how to handle the overlapping data. - // See HP, Solaris, et al. for those that favor duplicate data over the original in - // some cases. - - /* check if we're in the window */ - if ( tcp_config->policy != StreamPolicy::OS_PROXY - and !Stream::is_midstream(flow) and listener->normalizer.get_stream_window(tsd) == 0 ) - { - if ( SEQ_EQ(seq, listener->normalizer.get_zwp_seq()) ) - { - tcpStats.zero_win_probes++; - listener->normalizer.trim_win_payload(tsd, MAX_ZERO_WIN_PROBE_LEN, tsd.is_nap_policy_inline()); - return STREAM_UNALIGNED; - } - - listener->normalizer.trim_win_payload(tsd, 0, tsd.is_nap_policy_inline()); - return STREAM_UNALIGNED; - } - if ( tsd.is_data_segment() ) - { - update_stream_order(tsd, false); - process_tcp_stream(tsd); - } - } - - return STREAM_UNALIGNED; -} - void TcpSession::set_os_policy() { StreamPolicy client_os_policy = flow->ssn_policy ? @@ -865,33 +769,45 @@ void TcpSession::handle_data_segment(TcpSegmentDescriptor& tsd, bool flush) else server.set_tcp_options_len(tcp_options_len); - // FIXIT-M move this to normalizer base class, handle OS_PROXY in derived class - if ( tcp_config->policy != StreamPolicy::OS_PROXY ) + uint32_t seq = tsd.get_tcph()->is_syn() ? tsd.get_seq() + 1 : tsd.get_seq(); + bool stream_is_inorder = ( seq == listener->rcv_nxt ); + + int rc = listener->normalizer.apply_normalizations(tsd, seq, stream_is_inorder); + switch ( rc ) { - // these normalizations can't be done if we missed setup. and - // window is zero in one direction until we've seen both sides. - if ( !(Stream::is_midstream(flow)) && flow->two_way_traffic() ) + case TcpNormalizer::NORM_OK: + if ( stream_is_inorder ) + listener->rcv_nxt = tsd.get_end_seq(); + + update_stream_order(tsd, stream_is_inorder); + + // don't queue data if we are ignoring or queue thresholds are exceeded + if ( !tsd.are_packet_flags_set(PKT_IGNORE) and !flow_exceeds_config_thresholds(tsd) ) { - // drop packet if sequence num is invalid - if ( !listener->is_segment_seq_valid(tsd) ) + set_packet_header_foo(tsd); + listener->reassembler.queue_packet_for_reassembly(tsd); + + // Alert if overlap limit exceeded + if ( (tcp_config->overlap_limit) + && (listener->reassembler.get_overlap_count() > tcp_config->overlap_limit) ) { - tcpStats.invalid_seq_num++; - listener->normalizer.trim_win_payload(tsd); - return; + tel.set_tcp_event(EVENT_EXCESSIVE_OVERLAP); + listener->reassembler.set_overlap_count(0); } + } + break; - // trim to fit in listener's window and mss - listener->normalizer.trim_win_payload - (tsd, (listener->r_win_base + listener->get_snd_wnd() - listener->rcv_nxt)); + case TcpNormalizer::NORM_TRIMMED: + break; - if ( listener->get_mss() ) - listener->normalizer.trim_mss_payload(tsd, listener->get_mss()); + case TcpNormalizer::NORM_BAD_SEQ: + return; - listener->normalizer.ecn_stripper(tsd); - } - } + default: + assert(false); + break; - process_tcp_data(tsd); + } } if ( flush ) diff --git a/src/stream/tcp/tcp_session.h b/src/stream/tcp/tcp_session.h index 47c70940d..aa4136a9d 100644 --- a/src/stream/tcp/tcp_session.h +++ b/src/stream/tcp/tcp_session.h @@ -75,8 +75,6 @@ public: private: int process_tcp_packet(TcpSegmentDescriptor&, const snort::Packet*); - void process_tcp_stream(TcpSegmentDescriptor&); - int process_tcp_data(TcpSegmentDescriptor&); void set_os_policy() override; bool flow_exceeds_config_thresholds(TcpSegmentDescriptor&); void update_stream_order(const TcpSegmentDescriptor&, bool aligned); diff --git a/src/stream/tcp/tcp_stream_session.cc b/src/stream/tcp/tcp_stream_session.cc index 078170cd6..d005ef5b4 100644 --- a/src/stream/tcp/tcp_stream_session.cc +++ b/src/stream/tcp/tcp_stream_session.cc @@ -26,6 +26,7 @@ #include "tcp_stream_session.h" #include "framework/data_bus.h" +#include "packet_tracer/packet_tracer.h" #include "pub_sub/stream_event_ids.h" #include "stream/stream.h" #include "stream/tcp/tcp_ha.h" @@ -338,7 +339,15 @@ StreamSplitter* TcpStreamSession::get_splitter(bool to_server) } void TcpStreamSession::start_proxy() -{ tcp_config->policy = StreamPolicy::OS_PROXY; } +{ + if ( PacketTracer::is_active() ) + PacketTracer::log("Stream TCP normalization policy set to Proxy mode. Normalizations will be skipped\n"); + + tcp_config->policy = StreamPolicy::OS_PROXY; + client.normalizer.init(tcp_config->policy, this, &client, &server); + server.normalizer.init(tcp_config->policy, this, &server, &client); + ++tcpStats.proxy_mode_flows; +} void TcpStreamSession::set_established(const TcpSegmentDescriptor& tsd) {