From: Bohdan Hryniv -X (bhryniv - SOFTSERVE INC at Cisco) Date: Thu, 10 Jul 2025 20:45:55 +0000 (+0000) Subject: Pull request #4758: cip: cip inspector fallback functionality X-Git-Tag: 3.9.2.0~6 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=858cb76e9649f5a2e30a317a34b0060e44b0c706;p=thirdparty%2Fsnort3.git Pull request #4758: cip: cip inspector fallback functionality Merge in SNORT/snort3 from ~BHRYNIV/snort3:cip_inspector_fallback_functionality to master Squashed commit of the following: commit ef51d9515e8b966ada31707535f7edeca3c7471a Author: Bohdan Hryniv Date: Wed May 21 11:53:40 2025 -0400 cip: cip inspector fallback functionality --- diff --git a/src/service_inspectors/cip/cip_paf.cc b/src/service_inspectors/cip/cip_paf.cc index a09a727de..503411b8f 100644 --- a/src/service_inspectors/cip/cip_paf.cc +++ b/src/service_inspectors/cip/cip_paf.cc @@ -32,6 +32,12 @@ static const uint16_t ENIP_PAF_FIELD_SIZE = 4; using namespace snort; +static inline void reset_states(cip_paf_data* pafdata) +{ + pafdata->paf_state = CIP_PAF_STATE__COMMAND_1; + pafdata->enip_length = 0; +} + /* Function: CIPPaf() Purpose: CIP PAF callback. @@ -44,6 +50,8 @@ static StreamSplitter::Status cip_paf(cip_paf_data* pafdata, const uint8_t* data uint32_t len, uint32_t* fp) { uint32_t bytes_processed = 0; + uint16_t command = 0; + uint32_t total_length = 0; /* Process this packet 1 byte at a time */ while (bytes_processed < len) @@ -51,13 +59,21 @@ static StreamSplitter::Status cip_paf(cip_paf_data* pafdata, const uint8_t* data switch (pafdata->paf_state) { case CIP_PAF_STATE__COMMAND_1: - // Skip ENIP command. + command = *(data + bytes_processed); pafdata->paf_state = CIP_PAF_STATE__COMMAND_2; break; case CIP_PAF_STATE__COMMAND_2: - // Skip ENIP command. - pafdata->paf_state = CIP_PAF_STATE__LENGTH_1; + command |= (*(data + bytes_processed) << 8); + if (!enip_command_valid(command)) + { + pafdata->bytes_seen += len; + pafdata->paf_state = CIP_PAF_STATE__INVALID; + } else + { + pafdata->bytes_seen = 0; + pafdata->paf_state = CIP_PAF_STATE__LENGTH_1; + } break; case CIP_PAF_STATE__LENGTH_1: @@ -70,13 +86,33 @@ static StreamSplitter::Status cip_paf(cip_paf_data* pafdata, const uint8_t* data pafdata->paf_state = CIP_PAF_STATE__SET_FLUSH; break; + case CIP_PAF_STATE__INVALID: + if (pafdata->bytes_seen > CIP_MAX_OCTETS) + { + pafdata->bytes_seen = 0; + reset_states(pafdata); + return StreamSplitter::ABORT; + } + pafdata->paf_state = CIP_PAF_STATE__COMMAND_1; + pafdata->enip_length = 0; + return StreamSplitter::SEARCH; + case CIP_PAF_STATE__SET_FLUSH: - *fp = bytes_processed + + total_length = bytes_processed + pafdata->enip_length + (ENIP_HEADER_SIZE - ENIP_PAF_FIELD_SIZE); - pafdata->paf_state = CIP_PAF_STATE__COMMAND_1; - return StreamSplitter::FLUSH; - + if(total_length > len) + { + pafdata->bytes_seen += len; + pafdata->paf_state = CIP_PAF_STATE__INVALID; + } else + { + pafdata->bytes_seen = 0; + *fp = total_length; + pafdata->paf_state = CIP_PAF_STATE__COMMAND_1; + return StreamSplitter::FLUSH; + } + break; default: // Will not happen. break; @@ -92,6 +128,7 @@ CipSplitter::CipSplitter(bool c2s) : StreamSplitter(c2s) { state.paf_state = CIP_PAF_STATE__COMMAND_1; state.enip_length = 0; + state.bytes_seen = 0; } StreamSplitter::Status CipSplitter::scan( @@ -101,4 +138,3 @@ StreamSplitter::Status CipSplitter::scan( cip_paf_data* pfdata = &state; return cip_paf(pfdata, data, len, fp); } - diff --git a/src/service_inspectors/cip/cip_paf.h b/src/service_inspectors/cip/cip_paf.h index e0afd082a..87a6e1ce3 100644 --- a/src/service_inspectors/cip/cip_paf.h +++ b/src/service_inspectors/cip/cip_paf.h @@ -27,6 +27,8 @@ #include "cip.h" +#define CIP_MAX_OCTETS 15000 // using standard max ethernet frame times 10 + /* State-tracking structs */ enum cip_paf_state { @@ -34,13 +36,15 @@ enum cip_paf_state CIP_PAF_STATE__COMMAND_2, CIP_PAF_STATE__LENGTH_1, CIP_PAF_STATE__LENGTH_2, - CIP_PAF_STATE__SET_FLUSH + CIP_PAF_STATE__SET_FLUSH, + CIP_PAF_STATE__INVALID }; struct cip_paf_data { cip_paf_state paf_state; uint16_t enip_length; + uint32_t bytes_seen; }; class CipSplitter : public snort::StreamSplitter diff --git a/src/service_inspectors/cip/cip_parsing.cc b/src/service_inspectors/cip/cip_parsing.cc index 0f7359756..59ac1e74f 100644 --- a/src/service_inspectors/cip/cip_parsing.cc +++ b/src/service_inspectors/cip/cip_parsing.cc @@ -139,7 +139,7 @@ static bool parse_message_router_request(const uint8_t* data, CipGlobalSessionData* global_data); /// Functions -static bool enip_command_valid(uint16_t command) +bool enip_command_valid(uint16_t command) { if ((ENIP_COMMAND_RESERVED1_START <= command && command <= ENIP_COMMAND_RESERVED1_END) || (ENIP_COMMAND_RESERVED2_START <= command)) diff --git a/src/service_inspectors/cip/cip_parsing.h b/src/service_inspectors/cip/cip_parsing.h index 005b5120c..88c8e6d2e 100644 --- a/src/service_inspectors/cip/cip_parsing.h +++ b/src/service_inspectors/cip/cip_parsing.h @@ -60,6 +60,7 @@ bool parse_enip_layer(const uint8_t* data, CipGlobalSessionData* global_data); void pack_cip_request_event(const CipRequest* request, CipEventData* cip_event_data); +bool enip_command_valid(uint16_t command); #endif // CIP_PARSING_H