From: Andres Avila Segura (aavilase) Date: Tue, 22 Jul 2025 15:45:46 +0000 (+0000) Subject: Pull request #4793: iec104: fallback functionality changes X-Git-Tag: 3.9.3.0~29 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=cf083f4d820b1948607e12ea694956dac9baf997;p=thirdparty%2Fsnort3.git Pull request #4793: iec104: fallback functionality changes Merge in SNORT/snort3 from ~AAVILASE/snort3:iec104_fallback_draft to master Squashed commit of the following: commit 1e29d06544ca82bc5b144ae80b0f65edb13be651 Author: Andres Avila Date: Tue Jun 17 13:09:07 2025 -0400 iec104: fallback functionality for abort scenario --- diff --git a/src/service_inspectors/iec104/iec104_paf.cc b/src/service_inspectors/iec104/iec104_paf.cc index 68a7abed8..54f8b94b7 100644 --- a/src/service_inspectors/iec104/iec104_paf.cc +++ b/src/service_inspectors/iec104/iec104_paf.cc @@ -37,13 +37,54 @@ using namespace snort; -#define IEC104_MIN_HDR_LEN 2 // Enough for the Start and Length fields +#define IEC104_MAX_OCTETS 2560 // using max length of iec104 packet (max size is a byte) times 10 +#define IEC104_LSBIT_MASK 0x01 +#define IEC104_FUNCTION_FIELDS_MASK 0xFC +#define IEC104_APCI_TYPE_MASK 0x03 +#define IEC104_TYPE_I 0 +#define IEC104_TYPE_S 1 +#define IEC104_TYPE_I_2 2 +#define IEC104_TYPE_U 3 Iec104Splitter::Iec104Splitter(bool b) : StreamSplitter(b) { state = IEC104_PAF_STATE__START; iec104_apci_length = 0; + bytes_seen = 0; + valid_bytes = true; +} + +//each u format frame can only have one function bit set +static inline bool onlyOneBitSet(uint8_t n) { + if (n == 0) + return false; + return (n & (n - 1)) == 0; // If non-zero after clearing LSB, more than one bit was set +} + +static inline bool validate_control_fields(const uint8_t* data) +{ + uint8_t apci_type = data[2] & IEC104_APCI_TYPE_MASK; + + switch(apci_type) + { + case IEC104_TYPE_U: + { + bool are_fields_unset = (data[3] | data[4] | data[5]) == 0; + uint8_t function_fields = data[2] & IEC104_FUNCTION_FIELDS_MASK; + return are_fields_unset and onlyOneBitSet(function_fields); + } + case IEC104_TYPE_S: + { + bool is_field3_lsb_unset = (data[4] & IEC104_LSBIT_MASK) == 0; + return data[2] == 1 and data[3] == 0 and is_field3_lsb_unset; + } + default: //IEC104_TYPE_I format + { + bool is_field3_lsb_unset = (data[4] & IEC104_LSBIT_MASK) == 0; + return is_field3_lsb_unset; + } + } } // IEC104/TCP PAF: @@ -57,6 +98,9 @@ StreamSplitter::Status Iec104Splitter::scan(Packet*, const uint8_t* data, uint32 uint32_t bytes_processed = 0; + if (len < IEC104_MIN_LEN or !validate_control_fields(data)) + valid_bytes = false; + /* Process this packet 1 byte at a time */ while (bytes_processed < len) { @@ -65,7 +109,12 @@ StreamSplitter::Status Iec104Splitter::scan(Packet*, const uint8_t* data, uint32 // skip the start state case IEC104_PAF_STATE__START: { + uint8_t iec104_byte = *data; state = IEC104_PAF_STATE__LEN; + if (iec104_byte != IEC104_START_ID) + { + valid_bytes = false; + } break; } @@ -74,6 +123,10 @@ StreamSplitter::Status Iec104Splitter::scan(Packet*, const uint8_t* data, uint32 { iec104_apci_length = *(data + bytes_processed); state = IEC104_PAF_STATE__SET_FLUSH; + if (iec104_apci_length > len) + { + valid_bytes = false; + } break; } @@ -82,6 +135,19 @@ StreamSplitter::Status Iec104Splitter::scan(Packet*, const uint8_t* data, uint32 *fp = iec104_apci_length + bytes_processed; // flush point at the end of payload state = IEC104_PAF_STATE__START; iec104_apci_length = 0; + if (!valid_bytes) + { + valid_bytes = true; + bytes_seen += len; + if (bytes_seen > IEC104_MAX_OCTETS) + { + return StreamSplitter::ABORT; + } + } + else + { + bytes_seen = 0; + } return StreamSplitter::FLUSH; } } diff --git a/src/service_inspectors/iec104/iec104_paf.h b/src/service_inspectors/iec104/iec104_paf.h index 09d266ce2..455d1cde2 100644 --- a/src/service_inspectors/iec104/iec104_paf.h +++ b/src/service_inspectors/iec104/iec104_paf.h @@ -47,6 +47,8 @@ public: private: iec104_paf_state_t state; uint16_t iec104_apci_length; + uint32_t bytes_seen; + bool valid_bytes; }; #endif