set (PUB_SUB_INCLUDES
appid_events.h
+ cip_events.h
daq_message_event.h
expect_events.h
finalize_packet_event.h
add_library( pub_sub OBJECT
${PUB_SUB_INCLUDES}
+ cip_events.cc
http_events.cc
sip_events.cc
)
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+// sip_events.cc author Jian Wu <jiawu2@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "cip_events.h"
+
+#include "service_inspectors/cip/cip.h"
+
+using namespace snort;
+using namespace std;
+
+CipEvent::CipEvent(const Packet* p, const CipEventData* EventData)
+{
+ this->p = p;
+ this->EventData = EventData;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+// cip_events.h author Jian Wu <jiawu2@cisco.com>
+
+#ifndef CIP_EVENTS_H
+#define CIP_EVENTS_H
+
+// This event conveys data published by the CIP service inspector to be consumed
+// by data bus subscribers
+
+#include <list>
+
+#include "framework/data_bus.h"
+
+#define CIP_EVENT_TYPE_CIP_DATA_KEY "cip_event_type_cip_data"
+
+enum CipEventType
+{
+ CIP_EVENT_TYPE_CIP_DATA
+};
+
+namespace snort
+{
+struct Packet;
+struct SfIp;
+}
+
+struct CipEventData;
+
+class CipEvent : public snort::DataEvent
+{
+public:
+ CipEvent(const snort::Packet*, const CipEventData*);
+
+ const snort::Packet* get_packet() override
+ { return p; }
+
+private:
+ const snort::Packet* p;
+ const CipEventData* EventData;
+};
+
+#endif
add_subdirectory(back_orifice)
+add_subdirectory(cip)
add_subdirectory(dce_rpc)
add_subdirectory(dnp3)
add_subdirectory(dns)
if (STATIC_INSPECTORS)
set (STATIC_INSPECTOR_OBJS
$<TARGET_OBJECTS:back_orifice>
+ $<TARGET_OBJECTS:cip>
$<TARGET_OBJECTS:dce_rpc>
$<TARGET_OBJECTS:dnp3>
$<TARGET_OBJECTS:dns>
--- /dev/null
+set( FILE_LIST
+ cip.cc
+ cip.h
+ cip_definitions.h
+ cip_module.cc
+ cip_module.h
+ cip_paf.cc
+ cip_paf.h
+ cip_parsing.cc
+ cip_parsing.h
+ cip_session.cc
+ cip_session.h
+ cip_util.h
+ ips_cip_attribute.cc
+ ips_cip_class.cc
+ ips_cip_connpathclass.cc
+ ips_cip_enipcommand.cc
+ ips_cip_enipreq.cc
+ ips_cip_eniprsp.cc
+ ips_cip_instance.cc
+ ips_cip_req.cc
+ ips_cip_rsp.cc
+ ips_cip_service.cc
+ ips_cip_status.cc
+)
+
+if (STATIC_INSPECTORS)
+ add_library(cip OBJECT ${FILE_LIST})
+
+else (STATIC_INSPECTORS)
+ add_dynamic_module(cip inspectors ${FILE_LIST})
+
+endif (STATIC_INSPECTORS)
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2014-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// cip.cc author Jian Wu <jiawu2@cisco.com>
+
+/* Description: service inspector for the CIP protocol. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "cip.h"
+
+#include "detection/detection_engine.h"
+#include "events/event_queue.h"
+#include "log/messages.h"
+#include "managers/inspector_manager.h"
+#include "profiler/profiler.h"
+#include "protocols/packet.h"
+#include "pub_sub/cip_events.h"
+#include "stream/stream_splitter.h"
+#include "utils/util.h" // for snort_calloc
+
+#include "cip_module.h"
+#include "cip_paf.h"
+#include "cip_parsing.h"
+
+using namespace snort;
+
+THREAD_LOCAL ProfileStats cip_perf_stats;
+
+unsigned CipFlowData::inspector_id = 0;
+
+static void free_cip_data(void* data);
+
+CipFlowData::CipFlowData() : FlowData(inspector_id)
+{
+ memset(&session, 0, sizeof(session));
+ cip_stats.sessions++;
+ cip_stats.concurrent_sessions++;
+ if (cip_stats.max_concurrent_sessions < cip_stats.concurrent_sessions)
+ cip_stats.max_concurrent_sessions = cip_stats.concurrent_sessions;
+}
+
+CipFlowData::~CipFlowData()
+{
+ free_cip_data(&session);
+ assert(cip_stats.concurrent_sessions > 0);
+ cip_stats.concurrent_sessions--;
+}
+
+CipSessionData* get_cip_session_data(const Flow* flow)
+{
+ CipFlowData* fd = static_cast<CipFlowData*>(flow->get_flow_data(CipFlowData::inspector_id));
+ return fd ? &fd->session : nullptr;
+}
+
+static CipSessionData* set_new_cip_session_data(CipProtoConf* config, Packet* p)
+{
+ CipFlowData* fd = new CipFlowData;
+ CipSessionData* css = &fd->session;
+
+ p->flow->set_flow_data(fd);
+
+ /* Only allocate global state data for TCP connections. */
+ if (p->has_tcp_data())
+ {
+ css->global_data.connection_list.list_size = config->max_cip_connections;
+ css->global_data.connection_list.list
+ = static_cast<CipConnection*>(snort_calloc(config->max_cip_connections,
+ sizeof(CipConnection)));
+
+ css->global_data.unconnected_list.list_size = config->max_unconnected_messages;
+ css->global_data.unconnected_list.list
+ = static_cast<CipUnconnectedMessage*>(snort_calloc(config->max_unconnected_messages,
+ sizeof(CipUnconnectedMessage)));
+ }
+
+ return &fd->session;
+}
+
+static void free_cip_data(void* data)
+{
+ CipSessionData* css = static_cast<CipSessionData*>(data);
+
+ if ( css->global_data.connection_list.list )
+ {
+ snort_free(css->global_data.connection_list.list);
+ css->global_data.connection_list.list = nullptr;
+ }
+
+ if ( css->global_data.unconnected_list.list )
+ {
+ snort_free(css->global_data.unconnected_list.list);
+ css->global_data.unconnected_list.list = nullptr;
+ }
+}
+
+static void print_cip_conf(CipProtoConf* config)
+{
+ if (config == nullptr)
+ return;
+ LogMessage("CIP config: \n");
+ LogMessage(" Embedded Enabled: %s\n",
+ config->embedded_cip_enabled ? "ENABLED" : "DISABLED");
+ if (config->embedded_cip_enabled)
+ {
+ LogMessage(" Embedded Class: 0x%x\n", config->embedded_cip_class_id);
+ LogMessage(" Embedded Service: 0x%x\n", config->embedded_cip_service_id);
+ }
+
+ LogMessage(" Unconnected Timeout: %d (seconds)\n", config->unconnected_timeout);
+ LogMessage(" Max CIP connections per TCP connection: %d\n",
+ static_cast<int>(config->max_cip_connections));
+ LogMessage(" Max unconnected messages per TCP connection: %d\n",
+ static_cast<int>(config->max_unconnected_messages));
+
+ LogMessage("\n");
+}
+
+static CipPacketDirection get_packet_direction(Packet* p)
+{
+ if (!p->has_tcp_data())
+ {
+ return CIP_FROM_UNKNOWN;
+ }
+ if (p->packet_flags & PKT_FROM_CLIENT)
+ {
+ return CIP_FROM_CLIENT;
+ }
+ return CIP_FROM_SERVER;
+}
+
+static void publish_data_to_appId(Packet* packet, CipCurrentData& current_data)
+{
+ CipEventData cip_event_data;
+ CipEvent cip_event(packet, &cip_event_data);
+
+ bool publish_appid = true;
+
+ // Set one specific matching type for this PDU, in order of priority.
+ if (current_data.invalid_fatal)
+ {
+ cip_event_data.type = CIP_DATA_TYPE_MALFORMED;
+ }
+ else if (current_data.cip_message_type == CipMessageTypeExplicit)
+ {
+ if (current_data.cip_msg.is_cip_request)
+ {
+ /* Just Cip implement this function in parsing.cc */
+ pack_cip_request_event(¤t_data.cip_msg.request, &cip_event_data);
+ }
+ else
+ {
+ // Do not attempt to set applications for CIP responses.
+ publish_appid = false;
+ }
+ }
+ else if (current_data.cip_message_type == CipMessageTypeImplicit)
+ {
+ cip_event_data.type = CIP_DATA_TYPE_IMPLICIT;
+ cip_event_data.class_id = current_data.enip_data.connection_class_id;
+ }
+ else if (current_data.enip_data.enip_decoded)
+ {
+ cip_event_data.type = CIP_DATA_TYPE_ENIP_COMMAND;
+ cip_event_data.enip_command_id = current_data.enip_data.enip_header.command;
+ }
+ else
+ {
+ cip_event_data.type = CIP_DATA_TYPE_OTHER;
+ }
+
+ if (publish_appid)
+ {
+ DataBus::publish(CIP_EVENT_TYPE_CIP_DATA_KEY, cip_event, packet->flow);
+ }
+}
+
+static void log_cip_validity_errors(const CipCurrentData& current_data,
+ CipGlobalSessionData& global_data)
+{
+ if (current_data.invalid_fatal)
+ {
+ /* what is engine */
+ DetectionEngine::queue_event(GID_CIP, CIP_MALFORMED);
+ }
+ else if (current_data.enip_data.enip_invalid_nonfatal != 0
+ || current_data.cip_msg.request.cip_req_invalid_nonfatal != 0)
+ {
+ DetectionEngine::queue_event(GID_CIP, CIP_NON_CONFORMING);
+ }
+
+ if (global_data.connection_list.connection_pruned)
+ {
+ DetectionEngine::queue_event(GID_CIP, CIP_CONNECTION_LIMIT);
+ global_data.connection_list.connection_pruned = false;
+ }
+
+ if (global_data.unconnected_list.request_pruned)
+ {
+ DetectionEngine::queue_event(GID_CIP, CIP_REQUEST_LIMIT);
+ global_data.unconnected_list.request_pruned = false;
+ }
+}
+
+static void cip_current_data_process(CipSessionData* css, CipCurrentData& current_data,
+ CipProtoConf* config, Packet* p)
+{
+ /* Current Data should be implemented as the same with c files */
+ memset(¤t_data, 0, sizeof(CipCurrentData));
+ current_data.direction = get_packet_direction(p);
+
+ css->global_data.config = config;
+ css->global_data.snort_packet = p;
+
+ /* parse_enip_layer should be implemented specifically */
+ current_data.invalid_fatal = !parse_enip_layer(p->data,
+ p->dsize,
+ p->has_tcp_data(),
+ ¤t_data,
+ &css->global_data);
+
+ if (!current_data.invalid_fatal
+ && (p->dsize != current_data.enip_data.enip_header.length + ENIP_HEADER_SIZE))
+ {
+ current_data.enip_data.enip_invalid_nonfatal |= ENIP_INVALID_PAYLOAD_SIZE;
+ }
+}
+
+static void snort_cip(CipProtoConf* config, Packet* p)
+{
+ Profile profile(cip_perf_stats);
+
+ if (p->has_tcp_data() && !p->is_full_pdu())
+ return;
+
+ p->packet_flags |= PKT_ALLOW_MULTIPLE_DETECT;
+ CipSessionData* css = get_cip_session_data(p->flow);
+
+ if (css == nullptr)
+ {
+ css = set_new_cip_session_data(config, p);
+ }
+
+ CipCurrentData& current_data = css->current_data;
+ cip_current_data_process(css, current_data, config, p);
+ publish_data_to_appId(p, current_data);
+ log_cip_validity_errors(current_data, css->global_data);
+}
+
+//-------------------------------------------------------------------------
+// class stuff
+//-------------------------------------------------------------------------
+
+class Cip : public Inspector
+{
+public:
+ Cip(CipProtoConf*);
+ ~Cip() override;
+
+ void show(SnortConfig*) override;
+ void eval(Packet*) override;
+
+ class StreamSplitter* get_splitter(bool c2s) override
+ {
+ return new CipSplitter(c2s);
+ }
+
+private:
+ CipProtoConf* config;
+};
+
+Cip::Cip(CipProtoConf* pc)
+{
+ config = pc;
+}
+
+Cip::~Cip()
+{
+ if (config)
+ {
+ delete config;
+ }
+}
+
+void Cip::show(SnortConfig*)
+{
+ /* defined in module.cc */
+ print_cip_conf(config);
+}
+
+void Cip::eval(Packet* p)
+{
+ assert(p->has_tcp_data() || p->has_udp_data());
+ assert(p->flow);
+ cip_stats.packets++;
+ snort_cip(config, p);
+}
+
+//-------------------------------------------------------------------------
+// api stuff
+//-------------------------------------------------------------------------
+
+static Module* mod_ctor()
+{
+ return new CipModule;
+}
+
+static void mod_dtor(Module* m)
+{
+ delete m;
+}
+
+static void cip_init()
+{
+ CipFlowData::init();
+}
+
+static Inspector* cip_ctor(Module* m)
+{
+ CipModule* mod = static_cast<CipModule*>(m);
+ return new Cip(mod->get_data());
+}
+
+static void cip_dtor(Inspector* p)
+{
+ delete p;
+}
+
+const InspectApi cip_api =
+{
+ {
+ PT_INSPECTOR,
+ sizeof(InspectApi),
+ INSAPI_VERSION,
+ 0,
+ API_RESERVED,
+ API_OPTIONS,
+ CIP_NAME,
+ CIP_HELP,
+ mod_ctor,
+ mod_dtor
+ },
+ IT_SERVICE,
+ PROTO_BIT__PDU,
+ nullptr, // buffers
+ "cip",
+ cip_init,
+ nullptr, // pterm
+ nullptr, // tinit
+ nullptr, // tterm
+ cip_ctor,
+ cip_dtor,
+ nullptr, // ssn
+ nullptr // reset
+};
+
+extern const BaseApi* ips_cip_attribute;
+extern const BaseApi* ips_cip_class;
+extern const BaseApi* ips_cip_connpathclass;
+extern const BaseApi* ips_cip_enipcommand;
+extern const BaseApi* ips_cip_enipreq;
+extern const BaseApi* ips_cip_eniprsp;
+extern const BaseApi* ips_cip_instance;
+extern const BaseApi* ips_cip_req;
+extern const BaseApi* ips_cip_rsp;
+extern const BaseApi* ips_cip_service;
+extern const BaseApi* ips_cip_status;
+
+#ifdef BUILDING_SO
+SO_PUBLIC const BaseApi* snort_plugins[] =
+#else
+const BaseApi* sin_cip[] =
+#endif
+{
+ &cip_api.base,
+ ips_cip_attribute,
+ ips_cip_class,
+ ips_cip_connpathclass,
+ ips_cip_enipcommand,
+ ips_cip_enipreq,
+ ips_cip_eniprsp,
+ ips_cip_instance,
+ ips_cip_req,
+ ips_cip_rsp,
+ ips_cip_service,
+ ips_cip_status,
+ nullptr
+};
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2014-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// cip.h author RA/Cisco Jian Wu <jiawu2@cisco.com>
+
+#ifndef CIP_H
+#define CIP_H
+// Implementation header with definitions, datatypes and flowdata class for CIP service inspector.
+
+#include "flow/flow.h"
+#include "framework/counts.h"
+#include "main/thread.h"
+#include "protocols/packet.h"
+
+#include "cip_definitions.h"
+
+namespace snort
+{
+struct Packet;
+}
+
+enum CipDataType
+{
+ CIP_DATA_TYPE_PATH_CLASS = 0,
+ CIP_DATA_TYPE_PATH_EXT_SYMBOL,
+ CIP_DATA_TYPE_SET_ATTRIBUTE,
+ CIP_DATA_TYPE_CONNECTION,
+ CIP_DATA_TYPE_IMPLICIT,
+ CIP_DATA_TYPE_OTHER,
+ CIP_DATA_TYPE_ENIP_COMMAND,
+ CIP_DATA_TYPE_MALFORMED
+};
+
+struct CipEventData
+{
+ // Specify the type of CIP data.
+ CipDataType type;
+
+ // Used for:
+ // CIP_DATA_TYPE_ENIP_COMMAND
+ uint16_t enip_command_id;
+
+ // Used for:
+ // CIP_DATA_TYPE_PATH_CLASS
+ // CIP_DATA_TYPE_PATH_EXT_SYMBOL
+ // CIP_DATA_TYPE_SET_ATTRIBUTE
+ uint8_t service_id;
+
+ // Used for:
+ // CIP_DATA_TYPE_PATH_CLASS: This represents the Request Path Class.
+ // CIP_DATA_TYPE_SET_ATTRIBUTE: This represents the Request Path Class.
+ // CIP_DATA_TYPE_CONNECTION: This represents the Connection Path Class.
+ // CIP_DATA_TYPE_IMPLICIT: This represents the Connection Path Class from
+ // the original connection request, for this connection.
+ uint32_t class_id;
+
+ // Used for:
+ // CIP_DATA_TYPE_SET_ATTRIBUTE: This represents the Request Path Instance.
+ uint32_t instance_id;
+
+ // Used for:
+ // CIP_DATA_TYPE_SET_ATTRIBUTE: This represents the Request Path Attribute.
+ uint32_t attribute_id;
+
+ // Pointer to snort::Packet
+ const snort::Packet* snort_packet;
+};
+
+class CipFlowData : public snort::FlowData
+{
+public:
+ CipFlowData();
+ ~CipFlowData() override;
+
+ static void init()
+ { inspector_id = snort::FlowData::create_flow_data_id(); }
+
+ size_t size_of() override
+ { return sizeof(*this); }
+
+public:
+ static unsigned inspector_id;
+ CipSessionData session;
+};
+
+CipSessionData* get_cip_session_data(const snort::Flow*);
+
+struct CipStats
+{
+ PegCount packets;
+ PegCount sessions;
+ PegCount concurrent_sessions;
+ PegCount max_concurrent_sessions;
+};
+
+extern THREAD_LOCAL CipStats cip_stats;
+
+#endif
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2014-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// cip_definitions.h author RA/Cisco
+
+/* Description: Common types for the CIP preprocessor. */
+
+#ifndef CIP_DEFINITIONS_H
+#define CIP_DEFINITIONS_H
+
+namespace snort
+{
+struct Packet;
+}
+
+#define MSEC_PER_SEC (1000)
+#define USEC_PER_SEC (1000000)
+
+// CIP preprocessor configuration
+struct CipProtoConf
+{
+ // Unconnected timeout, seconds.
+ uint32_t unconnected_timeout;
+
+ // Maximum number of unconnected requests per TCP connection.
+ size_t max_unconnected_messages;
+
+ // Maximum number of CIP connections per TCP connection.
+ size_t max_cip_connections;
+
+ // Custom embedded packet parameters.
+ bool embedded_cip_enabled;
+ uint32_t embedded_cip_class_id;
+ uint8_t embedded_cip_service_id;
+};
+
+/// CIP Request/Response Management
+enum CipRequestType
+{
+ CipRequestTypeOther = 0,
+ CipRequestTypeForwardOpen,
+ CipRequestTypeForwardClose,
+ CipRequestTypeUnconnectedSend,
+ CipRequestTypeMultipleServiceRequest,
+
+ // Special case to represent when no request is found for a given response.
+ CipRequestTypeNoMatchFound
+};
+
+struct CipStatus
+{
+ uint8_t general_status;
+ size_t extended_status_size;
+};
+
+enum CipPacketDirection
+{
+ CIP_FROM_CLIENT,
+ CIP_FROM_SERVER,
+ CIP_FROM_UNKNOWN
+};
+
+/// EtherNet/IP encapsulation layer definitions.
+
+// EtherNet/IP encapsulation header
+struct EnipHeader
+{
+ uint16_t command;
+ uint16_t length;
+ uint32_t session_handle;
+ uint32_t status;
+ uint64_t sender_context;
+ uint32_t options;
+};
+
+// This is an EtherNet/IP encapsulation layer common packet format item.
+struct EnipCpfItem
+{
+ uint16_t type;
+ uint16_t length;
+
+ // Used if length > 0. Data starts after the Length field.
+ const uint8_t* data;
+};
+
+// Largest number of allowed CPF items for standard EtherNet/IP commands.
+#define MAX_NUM_CPF_ITEMS 4
+
+// This is an EtherNet/IP encapsulation layer common packet format.
+struct EnipCpf
+{
+ uint16_t item_count;
+
+ // All CPF items in the list are valid up to and including array index item_count.
+ EnipCpfItem item_list[MAX_NUM_CPF_ITEMS];
+};
+
+/// CIP layer definitions.
+enum CipMessageType
+{
+ // Unknown CIP data type
+ CipMessageTypeUnknown,
+
+ // CIP Explicit Data
+ CipMessageTypeExplicit,
+
+ // CIP Implicit Data
+ CipMessageTypeImplicit
+};
+
+enum CipSegmentType
+{
+ CipSegment_Type_PORT_LINK_ADDRESS,
+ CipSegment_Type_PORT_LINK_ADDRESS_EXTENDED,
+
+ CipSegment_Type_LOGICAL_CLASS,
+ CipSegment_Type_LOGICAL_INSTANCE,
+ CipSegment_Type_LOGICAL_MEMBER,
+ CipSegment_Type_LOGICAL_CONN_POINT,
+ CipSegment_Type_LOGICAL_ATTRIBUTE,
+ CipSegment_Type_LOGICAL_ELECTRONIC_KEY,
+ CipSegment_Type_LOGICAL_EXTENDED,
+ CipSegment_Type_LOGICAL_SERVICE_ID,
+
+ CipSegment_Type_NETWORK,
+
+ CipSegment_Type_SYMBOLIC,
+
+ CipSegment_Type_DATA_SIMPLE,
+ CipSegment_Type_DATA_EXT_SYMBOL,
+
+ CipSegment_Type_UNKNOWN
+};
+
+#define CIP_STATUS_SUCCESS 0
+#define ENIP_STATUS_SUCCESS 0
+
+// CIP Classes
+#define MESSAGE_ROUTER_CLASS_ID 0x02
+#define CONNECTION_MANAGER_CLASS_ID 0x06
+
+// CIP Services
+#define SERVICE_SET_ATTRIBUTE_SINGLE 0x10
+#define SERVICE_MULTIPLE_SERVICE_PACKET 0x0A
+
+// CIP Connection Manager Services
+#define CONNECTION_MANAGER_UNCONNECTED_SEND 0x52
+#define CONNECTION_MANAGER_FORWARD_OPEN 0x54
+#define CONNECTION_MANAGER_LARGE_FORWARD_OPEN 0x5B
+#define CONNECTION_MANAGER_FORWARD_CLOSE 0x4E
+
+#define CIP_WORD_TO_BYTES 2
+
+struct CipSegment
+{
+ CipSegmentType type;
+
+ // Total size of this segment.
+ size_t size;
+
+ // When type = CipSegment_Type_PORT_LINK_ADDRESS
+ // When type = CipSegment_Type_PORT_LINK_ADDRESS_EXTENDED
+ uint16_t port_id;
+
+ // When type = CipSegment_Type_PORT_LINK_ADDRESS
+ uint8_t link_address;
+
+ // When type = CipSegment_Type_LOGICAL_CLASS
+ // When type = CipSegment_Type_LOGICAL_INSTANCE
+ // When type = CipSegment_Type_LOGICAL_MEMBER
+ // When type = CipSegment_Type_LOGICAL_CONN_POINT
+ // When type = CipSegment_Type_LOGICAL_ATTRIBUTE
+ uint32_t logical_value;
+
+ // When type = CipSegment_Type_PORT_LINK_ADDRESS_EXTENDED, this is the link address.
+ // When type = CipSegment_Type_DATA_EXT_SYMBOL, this is the symbol string.
+ // When type = CipSegment_Type_DATA_SIMPLE, this is the start of the data words.
+ // When type = CipSegment_Type_SYMBOLIC, this is the symbol string.
+ const uint8_t* data;
+ size_t data_size;
+};
+
+struct CipPath
+{
+ // Size of the entire path.
+ size_t full_path_size;
+
+ // True if path has been decoded successfully.
+ bool decoded;
+
+ // Main segment type for this path, which drives message target.
+ CipSegmentType primary_segment_type;
+
+ bool has_class_id;
+ uint32_t class_id;
+
+ bool has_instance_id;
+ uint32_t instance_id;
+
+ bool has_attribute_id;
+ uint32_t attribute_id;
+
+ bool has_unknown_segment;
+};
+
+// Matching pair of CIP Connection IDs.
+struct ConnectionIdPair
+{
+ uint32_t ot_connection_id;
+ uint32_t to_connection_id;
+};
+
+// RPI and Network Connection Parameters from a Forward Open Request.
+struct CipConnectionParameters
+{
+ uint32_t rpi;
+ uint32_t network_connection_parameters;
+ bool is_null_connection;
+};
+
+// Unique Connection Signature. This is unique to each CIP connection. This
+// tuple is unique on a given EtherNet/IP session.
+struct CipConnectionSignature
+{
+ uint16_t connection_serial_number;
+ uint16_t vendor_id;
+ uint32_t originator_serial_number;
+};
+
+#define TRANSPORT_CLASS_MASK 0x0F
+struct CipForwardOpenRequest
+{
+ // Unconnected request timeout, milliseconds.
+ uint32_t timeout_ms;
+
+ // Connection timeouts, microseconds.
+ uint64_t ot_connection_timeout_us;
+ uint64_t to_connection_timeout_us;
+
+ CipConnectionSignature connection_signature;
+
+ CipConnectionParameters ot_parameters;
+ CipConnectionParameters to_parameters;
+ uint8_t transport_class;
+
+ CipPath connection_path;
+
+ bool is_null_forward_open;
+
+ // Timestamp for message request.
+ struct timeval timestamp;
+};
+
+struct CipForwardOpenResponse
+{
+ // True if this was a successful Forward Open Response.
+ bool success;
+
+ // Properties for Success or Fail.
+ CipConnectionSignature connection_signature;
+
+ // Properties for a Forward Open Response Success.
+ ConnectionIdPair connection_pair;
+
+ size_t application_reply_size;
+
+ // Timestamp for message response.
+ struct timeval timestamp;
+};
+
+struct CipForwardCloseRequest
+{
+ // Unconnected request timeout, milliseconds.
+ uint32_t timeout_ms;
+
+ CipConnectionSignature connection_signature;
+
+ CipPath connection_path;
+};
+
+// Used to set error flags in enip_invalid_nonfatal.
+#define ENIP_INVALID_COMMAND (1 << 0)
+#define ENIP_INVALID_DUPLICATE_SESSION (1 << 1)
+#define ENIP_INVALID_SESSION_HANDLE (1 << 2)
+#define ENIP_INVALID_INTERFACE_HANDLE (1 << 3)
+#define ENIP_INVALID_CONNECTION_ID (1 << 4)
+#define ENIP_INVALID_PAYLOAD_SIZE (1 << 5)
+#define ENIP_INVALID_ENIP_COMMAND_CPF_MISMATCH (1 << 6)
+#define ENIP_INVALID_RESERVED_FUTURE_CPF_TYPE (1 << 7)
+#define ENIP_INVALID_STATUS (1 << 8)
+#define ENIP_INVALID_ENIP_TCP_ONLY (1 << 9)
+
+struct EnipSessionData
+{
+ // True if the ENIP Header was parsed and is valid.
+ bool enip_decoded;
+
+ // Full ENIP header.
+ EnipHeader enip_header;
+
+ // Error states for non-fatal ENIP errors. Error conditions that could trigger this:
+ // - Command code was not valid according to CIP Volume 2, Section 2-3.2.
+ // - RegisterSession attempted when a session was already active.
+ // - Session Handle did not match an active session.
+ // - Interface Handle != 0
+ // - Connection ID does not match an active connection.
+ // - Larger amount of ENIP data than specific in ENIP length.
+ // - Invalid CPF data item for a particular ENIP command.
+ // - CPF Item Type ID was found in the Reserved for future expansion range.
+ // - ENIP Status != 0, for a Request.
+ // - Attempting to send an ENIP command that is TCP only on a UDP connection.
+ uint32_t enip_invalid_nonfatal;
+
+ // True if the Common Packet Format was parsed and is valid.
+ bool cpf_decoded;
+
+ // True if the required CPF items are present for this EtherNet/IP command.
+ bool required_cpf_items_present;
+
+ // Common Packet Format data.
+ EnipCpf enip_cpf;
+
+ // Connection Class from original connection request, for connected messages.
+ uint32_t connection_class_id;
+};
+
+// Used to set error flags in cip_req_invalid_nonfatal.
+#define CIP_REQ_INVALID_CONNECTION_ADD_FAILED (1 << 0)
+#define CIP_REQ_INVALID_UNKNOWN_SEGMENT (1 << 1)
+#define CIP_REQ_INVALID_TIMEOUT_MULTIPLIER (1 << 2)
+struct CipRequest
+{
+ // CIP Service code.
+ uint8_t service;
+
+ CipPath request_path;
+
+ CipRequestType request_type;
+
+ // This is only valid for Unconnected Send messages.
+ CipPath route_path;
+
+ // CIP application payload data. This starts after the Request Path.
+ const uint8_t* cip_data;
+ size_t cip_data_size;
+
+ // Unconnected request timeout, milliseconds.
+ bool has_timeout;
+ uint32_t timeout_ms;
+
+ // True if this request was a Forward Open Request.
+ bool is_forward_open_request;
+
+ // Class ID in the Forward Open Request Connection Path.
+ // Used only when is_forward_open_request is true.
+ uint32_t connection_path_class_id;
+
+ // Error states for non-fatal CIP errors. Error conditions that could trigger this:
+ // - Forward Open Request received but couldn't add the connection to the list because a
+ // connection already existed with that signature.
+ // - Unknown segment type in request path.
+ // - Forward Open Request contained invalid Connection Timeout Multiplier.
+ uint32_t cip_req_invalid_nonfatal;
+};
+
+struct CipResponse
+{
+ // CIP Service code. This does not include the first bit set (0x80).
+ uint8_t service;
+
+ CipStatus status;
+};
+
+struct CipMessage
+{
+ // True if this is a CIP request (vs response).
+ bool is_cip_request;
+
+ // Used if is_cip_request is true.
+ CipRequest request;
+
+ // Used if is_cip_request is false.
+ CipResponse response;
+};
+
+struct CipCurrentData
+{
+ CipPacketDirection direction;
+
+ // ENIP layer data.
+ EnipSessionData enip_data;
+
+ // CIP layer data.
+ CipMessageType cip_message_type;
+
+ // Used if cip_message_type is CipMessageTypeExplicit
+ CipMessage cip_msg;
+
+ // True if the packet was not able to be fully parsed.
+ bool invalid_fatal;
+};
+
+struct EnipSession
+{
+ // ENIP session handle.
+ uint32_t session_handle;
+
+ // True if this session is active.
+ bool active;
+};
+
+// This represents an Unconnected message request.
+// Sender Context -> Request Type
+struct CipUnconnectedMessage
+{
+ uint64_t sender_context;
+
+ CipRequestType request_type;
+
+ // Unconnected request timeout, milliseconds.
+ uint32_t timeout_ms;
+
+ // Timestamp for message request.
+ struct timeval timestamp;
+
+ // True if this entry is in use.
+ bool slot_active;
+};
+
+struct CipUnconnectedMessageList
+{
+ CipUnconnectedMessage* list;
+ size_t list_size;
+
+ // True if an active request was forced to be pruned.
+ bool request_pruned;
+
+ size_t count;
+};
+
+// This represents a CIP connection.
+// This is used to:
+// a) Get the connection IDs, during a Forward Close.
+// b) Get Class ID from the Connection Path.
+struct CipConnection
+{
+ CipConnectionSignature signature;
+
+ ConnectionIdPair connection_id_pair;
+
+ // Class ID from the Connection Path
+ uint32_t class_id;
+
+ // True if the connection is fully established.
+ bool established;
+
+ // Connection timeouts, seconds.
+ uint32_t ot_connection_timeout_sec;
+ uint32_t to_connection_timeout_sec;
+
+ // Timestamp for last time connection was active.
+ struct timeval ot_timestamp;
+ struct timeval to_timestamp;
+
+ // True if this entry is in use.
+ bool slot_active;
+};
+
+struct CipConnectionList
+{
+ CipConnection* list;
+ size_t list_size;
+
+ // True if an active connection was forced to be pruned.
+ bool connection_pruned;
+
+ size_t count;
+};
+
+struct CipGlobalSessionData
+{
+ // ENIP Session for this TCP connection.
+ EnipSession enip_session;
+
+ // List of CIP connections.
+ CipConnectionList connection_list;
+
+ // List of outstanding unconnected messages (SendRRData).
+ CipUnconnectedMessageList unconnected_list;
+
+ // Current configuration for use in lower-level parsing functions.
+ const CipProtoConf* config;
+
+ snort::Packet* snort_packet;
+};
+
+// This is the overall structure used by Snort to store current and global data
+// for a particular stream.
+struct CipSessionData
+{
+ // Current data for this packet.
+ CipCurrentData current_data;
+
+ // Overall data for this session.
+ CipGlobalSessionData global_data;
+};
+
+#endif // CIP_DEFINITIONS_H
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// cip_module.cc author Jian Wu <jiawu2@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "cip_module.h"
+
+#include <cassert>
+
+#include "cip.h"
+
+using namespace snort;
+using namespace std;
+
+#define CIP_MALFORMED_STR "CIP data is malformed."
+#define CIP_NON_CONFORMING_STR "CIP data is non-conforming to ODVA standard."
+#define CIP_CONNECTION_LIMIT_STR \
+ "CIP connection limit exceeded. Least recently used connection removed."
+#define CIP_REQUEST_LIMIT_STR "CIP unconnected request limit exceeded. Oldest request removed."
+
+static const Parameter c_params[] =
+{
+ { "embedded_cip_path", Parameter::PT_STRING, nullptr, "false",
+ "check embedded CIP path" },
+ { "unconnected_timeout", Parameter::PT_INT, "0:360", "300",
+ "unconnected timeout in seconds" },
+ { "max_cip_connections", Parameter::PT_INT, "1:10000", "100",
+ "max cip connections" },
+ { "max_unconnected_messages", Parameter::PT_INT, "1:10000", "100",
+ "max unconnected cip messages" },
+ { nullptr, Parameter::PT_STRING, nullptr, nullptr, nullptr }
+};
+
+static const RuleMap cip_rules[] =
+{
+ { CIP_MALFORMED, CIP_MALFORMED_STR },
+ { CIP_NON_CONFORMING, CIP_NON_CONFORMING_STR },
+ { CIP_CONNECTION_LIMIT, CIP_CONNECTION_LIMIT_STR },
+ { CIP_REQUEST_LIMIT, CIP_REQUEST_LIMIT_STR },
+ { 0, nullptr }
+};
+
+THREAD_LOCAL CipStats cip_stats;
+
+static const PegInfo cip_pegs[] =
+{
+ { CountType::SUM, "packets", "total packets" },
+ { CountType::SUM, "session", "total sessions" },
+ { CountType::NOW, "concurrent_sessions", "total concurrent SIP sessions" },
+ { CountType::MAX, "max_concurrent_sessions", "maximum concurrent SIP sessions" },
+ { CountType::END, nullptr, nullptr },
+};
+
+//-------------------------------------------------------------------------
+// cip module
+//-------------------------------------------------------------------------
+
+CipModule::CipModule() : Module(CIP_NAME, CIP_HELP, c_params)
+{
+ conf = nullptr;
+}
+
+CipModule::~CipModule()
+{
+ if ( conf )
+ delete conf;
+}
+
+const RuleMap* CipModule::get_rules() const
+{ return cip_rules; }
+
+const PegInfo* CipModule::get_pegs() const
+{ return cip_pegs; }
+
+PegCount* CipModule::get_counts() const
+{ return (PegCount*)&cip_stats; }
+
+ProfileStats* CipModule::get_profile() const
+{ return &cip_perf_stats; }
+
+bool CipModule::set(const char*, Value& v, SnortConfig*)
+{
+ if ( v.is("embedded_cip_path") )
+ {
+ conf->embedded_cip_enabled = true;
+ embedded_path = v.get_string();
+ }
+ else if ( v.is("unconnected_timeout") )
+ conf->unconnected_timeout = v.get_uint32();
+
+ else if ( v.is("max_cip_connections") )
+ conf->max_cip_connections = v.get_uint32();
+
+ else if ( v.is("max_unconnected_messages") )
+ conf->max_unconnected_messages = v.get_uint32();
+
+ else
+ return false;
+
+ return true;
+}
+
+CipProtoConf* CipModule::get_data()
+{
+ CipProtoConf* tmp = conf;
+ conf = nullptr;
+ return tmp;
+}
+
+bool CipModule::begin(const char*, int, SnortConfig*)
+{
+ assert(!conf);
+ conf = new CipProtoConf;
+
+ conf->embedded_cip_enabled = false;
+
+ return true;
+}
+
+bool CipModule::end(const char*, int, SnortConfig*)
+{
+ Value v(embedded_path.c_str());
+ std::string tok;
+ v.set_first_token();
+
+ if ( v.get_next_token(tok) )
+ {
+ conf->embedded_cip_class_id = static_cast<uint32_t>(::strtol(tok.c_str(), nullptr, 0));
+ }
+
+ if (v.get_next_token(tok) )
+ {
+ conf->embedded_cip_service_id = static_cast<uint8_t>(::strtol(tok.c_str(), nullptr, 0));
+ }
+
+ return true;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// cip_module.h author Jian Wu <jiawu2@cisco.com>
+
+#ifndef CIP_MODULE_H
+#define CIP_MODULE_H
+
+// Interface to the CIP service inspector
+
+#include "framework/module.h"
+
+#include "cip_definitions.h"
+
+#define GID_CIP 148
+
+#define CIP_MALFORMED 1
+#define CIP_NON_CONFORMING 2
+#define CIP_CONNECTION_LIMIT 3
+#define CIP_REQUEST_LIMIT 4
+
+#define CIP_NAME "cip"
+#define CIP_HELP "cip inspection"
+
+extern THREAD_LOCAL snort::ProfileStats cip_perf_stats;
+
+class CipModule : public snort::Module
+{
+public:
+ CipModule();
+ ~CipModule() override;
+
+ bool set(const char*, snort::Value&, snort::SnortConfig*) override;
+ bool begin(const char*, int, snort::SnortConfig*) override;
+ bool end(const char*, int, snort::SnortConfig*) override;
+
+ unsigned get_gid() const override
+ { return GID_CIP; }
+
+ const snort::RuleMap* get_rules() const override;
+ const PegInfo* get_pegs() const override;
+ PegCount* get_counts() const override;
+ snort::ProfileStats* get_profile() const override;
+
+ Usage get_usage() const override
+ { return INSPECT; }
+
+ CipProtoConf* get_data();
+
+private:
+ CipProtoConf* conf;
+ std::string embedded_path;
+};
+
+#endif
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2014-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// cip_paf.cc author RA/Cisco
+
+/* Description: Protocol-Aware Flushing (PAF) code for the CIP preprocessor.*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "cip_paf.h"
+#include "cip_parsing.h" // For ENIP constants
+
+// PAF will skip over the ENIP Command and Length fields.
+static const uint16_t ENIP_PAF_FIELD_SIZE = 4;
+
+using namespace snort;
+
+/* Function: CIPPaf()
+
+ Purpose: CIP PAF callback.
+ Statefully inspects CIP traffic from the start of a session.
+ Reads up until the length octet is found, then sets a flush point.
+ The flushed PDU is a ENIP frame.
+*/
+
+static StreamSplitter::Status cip_paf(cip_paf_data* pafdata, const uint8_t* data,
+ uint32_t len, uint32_t* fp)
+{
+ uint32_t bytes_processed = 0;
+
+ /* Process this packet 1 byte at a time */
+ while (bytes_processed < len)
+ {
+ switch (pafdata->paf_state)
+ {
+ case CIP_PAF_STATE__COMMAND_1:
+ // Skip ENIP command.
+ 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;
+ break;
+
+ case CIP_PAF_STATE__LENGTH_1:
+ pafdata->enip_length = *(data + bytes_processed);
+ pafdata->paf_state = CIP_PAF_STATE__LENGTH_2;
+ break;
+
+ case CIP_PAF_STATE__LENGTH_2:
+ pafdata->enip_length |= (*(data + bytes_processed) << 8);
+ pafdata->paf_state = CIP_PAF_STATE__SET_FLUSH;
+ break;
+
+ case CIP_PAF_STATE__SET_FLUSH:
+ *fp = bytes_processed +
+ pafdata->enip_length + (ENIP_HEADER_SIZE - ENIP_PAF_FIELD_SIZE);
+
+ pafdata->paf_state = CIP_PAF_STATE__COMMAND_1;
+ return StreamSplitter::FLUSH;
+
+ default:
+ // Will not happen.
+ break;
+ }
+
+ bytes_processed++;
+ }
+
+ return StreamSplitter::SEARCH;
+}
+
+CipSplitter::CipSplitter(bool c2s) : StreamSplitter(c2s)
+{
+ state.paf_state = CIP_PAF_STATE__COMMAND_1;
+ state.enip_length = 0;
+}
+
+StreamSplitter::Status CipSplitter::scan(
+ Packet*, const uint8_t* data, uint32_t len,
+ uint32_t, uint32_t* fp)
+{
+ cip_paf_data* pfdata = &state;
+ return cip_paf(pfdata, data, len, fp);
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2014-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// cip_paf.h author RA/Cisco
+
+/* Description: Protocol-Aware Flushing (PAF) code for the CIP preprocessor. */
+
+#ifndef CIP_PAF_H
+#define CIP_PAF_H
+
+#include "stream/stream_splitter.h"
+
+#include "cip.h"
+
+/* State-tracking structs */
+enum cip_paf_state
+{
+ CIP_PAF_STATE__COMMAND_1 = 0,
+ CIP_PAF_STATE__COMMAND_2,
+ CIP_PAF_STATE__LENGTH_1,
+ CIP_PAF_STATE__LENGTH_2,
+ CIP_PAF_STATE__SET_FLUSH
+};
+
+struct cip_paf_data
+{
+ cip_paf_state paf_state;
+ uint16_t enip_length;
+};
+
+class CipSplitter : public snort::StreamSplitter
+{
+public:
+ CipSplitter(bool c2s);
+
+ Status scan(snort::Packet*, const uint8_t* data, uint32_t len, uint32_t flags,
+ uint32_t* fp) override;
+
+ bool is_paf() override
+ {
+ return true;
+ }
+
+public:
+ cip_paf_data state;
+};
+
+#endif /* CIP_PAF_H */
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2014-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// cip_parsing.cc author RA/Cisco
+
+/* Description: Data parsing for EtherNet/IP and CIP formats.
+ Note: No pointer parameters to these functions can be passed as NULL. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "cip_parsing.h"
+
+#include "framework/data_bus.h"
+
+#include "cip.h"
+#include "cip_session.h" // For CIP connection tracking
+#include "cip_util.h" // For GetLEUint16/32
+
+using namespace snort;
+
+/// EtherNet/IP Parsing Constants
+
+// Common Packet Format Item IDs.
+enum CpfItemId
+{
+ CPF_NULL_ADDRESS_ITEM_ID = 0x0000,
+ CPF_LIST_IDENTITY_ITEM_ID = 0x000C,
+ CPF_CONNECTED_ADDRESS_ITEM_ID = 0x00A1,
+ CPF_CONNECTED_DATA_ITEM_ID = 0x00B1,
+ CPF_UNCONNECTED_DATA_ITEM_ID = 0x00B2,
+ CPF_LIST_SERVICES_ITEM_ID = 0x0100,
+ CPF_SOCKADDR_INFO_OT_ITEM_ID = 0x8000,
+ CPF_SOCKADDR_INFO_TO_ITEM_ID = 0x8001,
+ CPF_SEQUENCED_ADDRESS_ITEM_ID = 0x8002
+};
+
+#define CPF_ADDRESS_ITEM_SLOT 0
+#define CPF_DATA_ITEM_SLOT 1
+#define CPF_LIST_REPLY_SLOT 0
+
+// Some ENIP command ranges are reserved for future range.
+#define ENIP_COMMAND_RESERVED1_START 0x0006
+#define ENIP_COMMAND_RESERVED1_END 0x0062
+#define ENIP_COMMAND_RESERVED2_START 0x00C8
+
+// Some CPF Item IDs are reserved for future range.
+#define ENIP_CPF_ITEM_RESERVED1_START 0x0086
+#define ENIP_CPF_ITEM_RESERVED1_END 0x0090
+#define ENIP_CPF_ITEM_RESERVED2_START 0x0092
+#define ENIP_CPF_ITEM_RESERVED2_END 0x00A0
+#define ENIP_CPF_ITEM_RESERVED3_START 0x00A5
+#define ENIP_CPF_ITEM_RESERVED3_END 0x00B0
+#define ENIP_CPF_ITEM_RESERVED4_START 0x00B3
+#define ENIP_CPF_ITEM_RESERVED4_END 0x00FF
+#define ENIP_CPF_ITEM_RESERVED5_START 0x0110
+#define ENIP_CPF_ITEM_RESERVED5_END 0x7FFF
+#define ENIP_CPF_ITEM_RESERVED6_START 0x8004
+
+#define REGISTER_SESSION_DATA_SIZE 4
+
+/// CIP Layer Parsing Constants
+const size_t CIP_PATH_SEGMENT_MIN_SIZE_BYTES = sizeof(uint16_t);
+
+// Typical payload size offset in CIP segment
+#define CIP_PATH_SEGMENT_PAYLOAD_SIZE_OFFSET 1
+
+// Offset to Segment Type/Format byte
+#define CIP_PATH_TYPE_OFFSET 0
+
+// Logical segment format mask and types.
+#define CIP_PATH_LOGICAL_FORMAT_MASK 0x03
+
+enum LogicalValueType
+{
+ CIP_PATH_LOGICAL_8_BIT = 0x00,
+ CIP_PATH_LOGICAL_16_BIT = 0x01,
+ CIP_PATH_LOGICAL_32_BIT = 0x02
+};
+
+// Logical segment types
+enum LogicalSegmentType
+{
+ CIP_PATH_LOGICAL_CLASS = 0x00,
+ CIP_PATH_LOGICAL_INSTANCE = 0x04,
+ CIP_PATH_LOGICAL_MEMBER = 0x08,
+ CIP_PATH_LOGICAL_CONN_POINT = 0x0c,
+ CIP_PATH_LOGICAL_ATTRIBUTE = 0x10,
+ CIP_PATH_LOGICAL_SPECIAL = 0x14,
+ CIP_PATH_LOGICAL_SERVICE_ID = 0x18,
+ CIP_PATH_LOGICAL_EXTENDED = 0x1C
+};
+
+enum SegmentType
+{
+ CIP_PATH_SEGMENT_PORT = 0x00,
+ CIP_PATH_SEGMENT_LOGICAL = 0x20,
+ CIP_PATH_SEGMENT_NETWORK = 0x40,
+ CIP_PATH_SEGMENT_SYMBOLIC = 0x60,
+ CIP_PATH_SEGMENT_DATA = 0x80
+};
+
+enum ExtendedStringType
+{
+ EXTENDED_STRING_DOUBLE = 0x20,
+ EXTENDED_STRING_TRIPLE = 0x40,
+ EXTENDED_STRING_NUMERIC = 0xC0
+};
+
+#define MESSAGE_ROUTER_RESPONSE_MASK 0x80
+
+#define CIP_STATUS_MIN_SIZE 2
+
+/// Prototypes
+static bool parse_logical_address_format(const uint8_t* data,
+ size_t data_length,
+ bool logical_extended,
+ CipSegment* segment);
+
+static bool parse_message_router_request(const uint8_t* data,
+ size_t data_length,
+ CipRequest* cip_request,
+ CipGlobalSessionData* global_data);
+
+/// Functions
+static bool enip_command_valid(uint16_t command)
+{
+ if ((ENIP_COMMAND_RESERVED1_START <= command && command <= ENIP_COMMAND_RESERVED1_END)
+ || (ENIP_COMMAND_RESERVED2_START <= command))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+static bool enip_command_tcp_only(uint16_t command)
+{
+ // Allocated command codes.
+ if (command == ENIP_COMMAND_NOP
+ || command == ENIP_COMMAND_REGISTER_SESSION
+ || command == ENIP_COMMAND_UNREGISTER_SESSION
+ || command == ENIP_COMMAND_SEND_RR_DATA
+ || command == ENIP_COMMAND_SEND_UNIT_DATA)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+static bool parse_enip_header(const uint8_t* data,
+ size_t data_length,
+ EnipSessionData* enip_session)
+{
+ EnipHeader* enip_header = &enip_session->enip_header;
+
+ if (data_length < ENIP_HEADER_SIZE)
+ {
+ return false;
+ }
+
+ #define ENIP_HEADER_OFFSET_COMMAND 0
+ #define ENIP_HEADER_OFFSET_LENGTH 2
+ #define ENIP_HEADER_OFFSET_HANDLE 4
+ #define ENIP_HEADER_OFFSET_STATUS 8
+ #define ENIP_HEADER_OFFSET_CONTEXT 12
+ #define ENIP_HEADER_OFFSET_OPTIONS 20
+
+ enip_header->command = GetLEUint16(&data[ENIP_HEADER_OFFSET_COMMAND]);
+ enip_header->length = GetLEUint16(&data[ENIP_HEADER_OFFSET_LENGTH]);
+ enip_header->session_handle = GetLEUint32(&data[ENIP_HEADER_OFFSET_HANDLE]);
+ enip_header->status = GetLEUint32(&data[ENIP_HEADER_OFFSET_STATUS]);
+ memcpy(&enip_header->sender_context,
+ &data[ENIP_HEADER_OFFSET_CONTEXT],
+ sizeof(enip_header->sender_context));
+ enip_header->options = GetLEUint32(&data[ENIP_HEADER_OFFSET_OPTIONS]);
+
+ if (!enip_command_valid(enip_header->command))
+ {
+ enip_session->enip_invalid_nonfatal |= ENIP_INVALID_COMMAND;
+ }
+
+ return true;
+}
+
+static bool cpf_item_id_valid(uint16_t item_id)
+{
+ if ((ENIP_CPF_ITEM_RESERVED1_START <= item_id && item_id <= ENIP_CPF_ITEM_RESERVED1_END)
+ || (ENIP_CPF_ITEM_RESERVED2_START <= item_id && item_id <= ENIP_CPF_ITEM_RESERVED2_END)
+ || (ENIP_CPF_ITEM_RESERVED3_START <= item_id && item_id <= ENIP_CPF_ITEM_RESERVED3_END)
+ || (ENIP_CPF_ITEM_RESERVED4_START <= item_id && item_id <= ENIP_CPF_ITEM_RESERVED4_END)
+ || (ENIP_CPF_ITEM_RESERVED5_START <= item_id && item_id <= ENIP_CPF_ITEM_RESERVED5_END)
+ || (ENIP_CPF_ITEM_RESERVED6_START <= item_id))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+static bool cpf_item_length_valid(uint16_t item_id, size_t item_length)
+{
+ #define NULL_ADDRESS_ITEM_DATA_SIZE 0
+ #define CONNECTED_ADDRESS_ITEM_DATA_SIZE 4
+ #define SOCKADDR_INFO_ITEM_DATA_SIZE 16
+ #define SEQUENCED_ADDRESS_ITEM_DATA_SIZE 8
+
+ // Minimum data size for Connected and Unconnected Data Items when used with
+ // CIP Class 3 / Explicit data.
+ #define MIN_CPF_CIP_DATA_SIZE 2
+
+ bool valid = true;
+
+ switch (item_id)
+ {
+ case CPF_NULL_ADDRESS_ITEM_ID:
+ if (item_length != NULL_ADDRESS_ITEM_DATA_SIZE)
+ {
+ valid = false;
+ }
+ break;
+ case CPF_CONNECTED_ADDRESS_ITEM_ID:
+ if (item_length != CONNECTED_ADDRESS_ITEM_DATA_SIZE)
+ {
+ valid = false;
+ }
+ break;
+ case CPF_CONNECTED_DATA_ITEM_ID:
+ if (item_length < MIN_CPF_CIP_DATA_SIZE)
+ {
+ valid = false;
+ }
+ break;
+ case CPF_UNCONNECTED_DATA_ITEM_ID:
+ if (item_length < MIN_CPF_CIP_DATA_SIZE)
+ {
+ valid = false;
+ }
+ break;
+ case CPF_SOCKADDR_INFO_OT_ITEM_ID:
+ case CPF_SOCKADDR_INFO_TO_ITEM_ID:
+ if (item_length != SOCKADDR_INFO_ITEM_DATA_SIZE)
+ {
+ valid = false;
+ }
+ break;
+ case CPF_SEQUENCED_ADDRESS_ITEM_ID:
+ if (item_length != SEQUENCED_ADDRESS_ITEM_DATA_SIZE)
+ {
+ valid = false;
+ }
+ break;
+ case CPF_LIST_IDENTITY_ITEM_ID:
+ case CPF_LIST_SERVICES_ITEM_ID:
+ default:
+ // No length checks for anything else.
+ break;
+ }
+
+ return valid;
+}
+
+static bool enip_command_cpf_valid(uint16_t command, const EnipCpf* enip_cpf)
+{
+ #define MIN_CPF_ITEMS_CIP_MESSAGE 2
+ #define MIN_CPF_ITEMS_LIST_REPLY 1
+
+ bool valid = true;
+
+ switch (command)
+ {
+ case ENIP_COMMAND_SEND_RR_DATA:
+ if (enip_cpf->item_count < MIN_CPF_ITEMS_CIP_MESSAGE
+ || enip_cpf->item_list[CPF_ADDRESS_ITEM_SLOT].type != CPF_NULL_ADDRESS_ITEM_ID
+ || enip_cpf->item_list[CPF_DATA_ITEM_SLOT].type != CPF_UNCONNECTED_DATA_ITEM_ID)
+ {
+ valid = false;
+ }
+ break;
+ case ENIP_COMMAND_SEND_UNIT_DATA:
+ if (enip_cpf->item_count != MIN_CPF_ITEMS_CIP_MESSAGE
+ || enip_cpf->item_list[CPF_ADDRESS_ITEM_SLOT].type != CPF_CONNECTED_ADDRESS_ITEM_ID
+ || enip_cpf->item_list[CPF_DATA_ITEM_SLOT].type != CPF_CONNECTED_DATA_ITEM_ID)
+ {
+ valid = false;
+ }
+ break;
+ case ENIP_COMMAND_LIST_SERVICES:
+ // Used in Reply only.
+ if (enip_cpf->item_count < MIN_CPF_ITEMS_LIST_REPLY
+ || enip_cpf->item_list[CPF_LIST_REPLY_SLOT].type != CPF_LIST_SERVICES_ITEM_ID)
+ {
+ valid = false;
+ }
+ break;
+ case ENIP_COMMAND_LIST_IDENTITY:
+ // Used in Reply only.
+ if (enip_cpf->item_count < MIN_CPF_ITEMS_LIST_REPLY
+ || enip_cpf->item_list[CPF_LIST_REPLY_SLOT].type != CPF_LIST_IDENTITY_ITEM_ID)
+ {
+ valid = false;
+ }
+ break;
+ default:
+ // Ignore commands without defined CPF items.
+ break;
+ }
+
+ return valid;
+}
+
+// Returns the CIP message type based on packet and session data. The data must already:
+// 1. Be ENIP_COMMAND_SEND_UNIT_DATA or ENIP_COMMAND_SEND_RR_DATA
+// 2. Have the required CPF items for that ENIP command.
+// This also saves connection related data for the given packet and updates connection timestamps.
+static CipMessageType get_cip_message_type(CipCurrentData* current_data,
+ CipGlobalSessionData* global_data)
+{
+ CipMessageType cip_message_type = CipMessageTypeUnknown;
+
+ if (current_data->enip_data.enip_header.command == ENIP_COMMAND_SEND_RR_DATA)
+ {
+ cip_message_type = CipMessageTypeExplicit;
+ }
+ else // ENIP_COMMAND_SEND_UNIT_DATA
+ {
+ const EnipCpf* enip_cpf = ¤t_data->enip_data.enip_cpf;
+
+ if (enip_cpf->item_list[CPF_ADDRESS_ITEM_SLOT].length > 0)
+ {
+ uint32_t connection_id = GetLEUint32(enip_cpf->item_list[CPF_ADDRESS_ITEM_SLOT].data);
+
+ // Validate connected messages against CIP Connection List.
+ CipConnection* connection = cip_find_connection_by_id(
+ &global_data->connection_list,
+ current_data->direction,
+ connection_id,
+ true);
+ if (connection)
+ {
+ if (current_data->direction == CIP_FROM_CLIENT)
+ {
+ connection->ot_timestamp = global_data->snort_packet->pkth->ts;
+ }
+ else
+ {
+ connection->to_timestamp = global_data->snort_packet->pkth->ts;
+ }
+
+ current_data->enip_data.connection_class_id = connection->class_id;
+
+ if (connection->class_id == MESSAGE_ROUTER_CLASS_ID)
+ {
+ cip_message_type = CipMessageTypeExplicit;
+ }
+ else
+ {
+ cip_message_type = CipMessageTypeImplicit;
+ }
+ }
+ else
+ {
+ current_data->enip_data.enip_invalid_nonfatal |= ENIP_INVALID_CONNECTION_ID;
+ cip_message_type = CipMessageTypeUnknown;
+ }
+ }
+ }
+
+ return cip_message_type;
+}
+
+static bool parse_common_packet_format(const uint8_t* data,
+ size_t data_length,
+ EnipCpf* enip_cpf,
+ CipCurrentData* current_data)
+{
+ // The total item count is always first.
+ #define CPF_ITEM_COUNT_SIZE 2
+ if (data_length < CPF_ITEM_COUNT_SIZE)
+ {
+ return false;
+ }
+
+ #define CPF_OFFSET_ITEM_COUNT 0
+ #define CPF_ITEM_OFFSET_TYPE 0
+ #define CPF_ITEM_OFFSET_LENGTH 2
+ #define CPF_ITEM_OFFSET_DATA 4
+
+ enip_cpf->item_count = GetLEUint16(&data[CPF_OFFSET_ITEM_COUNT]);
+ data_length -= CPF_ITEM_COUNT_SIZE;
+
+ bool valid = true;
+
+ size_t current_item_offset = CPF_ITEM_COUNT_SIZE;
+
+ int i;
+ for (i = 0; i < enip_cpf->item_count; ++i)
+ {
+ uint16_t item_type;
+ uint16_t item_length;
+ const uint8_t* item_data;
+ /* This contains Type ID and Length. */
+ #define CPF_ITEM_HEADER_SIZE 4
+ if (data_length < CPF_ITEM_HEADER_SIZE)
+ {
+ valid = false;
+ break;
+ }
+
+ item_type = GetLEUint16(&data[current_item_offset + CPF_ITEM_OFFSET_TYPE]);
+ item_length = GetLEUint16(&data[current_item_offset + CPF_ITEM_OFFSET_LENGTH]);
+ item_data = nullptr;
+ if (item_length > 0)
+ {
+ item_data = &data[current_item_offset + CPF_ITEM_OFFSET_DATA];
+ }
+
+ data_length -= CPF_ITEM_HEADER_SIZE;
+
+ if (!cpf_item_id_valid(item_type))
+ {
+ current_data->enip_data.enip_invalid_nonfatal |= ENIP_INVALID_RESERVED_FUTURE_CPF_TYPE;
+ }
+
+ if (!cpf_item_length_valid(item_type, item_length))
+ {
+ valid = false;
+ break;
+ }
+
+ // Check that there is enough data left for the Item Length.
+ if (data_length < item_length)
+ {
+ valid = false;
+ break;
+ }
+
+ // Validate every CPF item, but only store data for a set amount.
+ if (i < MAX_NUM_CPF_ITEMS)
+ {
+ enip_cpf->item_list[i].type = item_type;
+ enip_cpf->item_list[i].length = item_length;
+ enip_cpf->item_list[i].data = item_data;
+ }
+
+ // Get data for the next item.
+ current_item_offset = current_item_offset + CPF_ITEM_HEADER_SIZE + item_length;
+ data_length -= item_length;
+ }
+
+ if (valid)
+ {
+ current_data->enip_data.required_cpf_items_present
+ = enip_command_cpf_valid(current_data->enip_data.enip_header.command, enip_cpf);
+ if (!current_data->enip_data.required_cpf_items_present)
+ {
+ current_data->enip_data.enip_invalid_nonfatal |=
+ ENIP_INVALID_ENIP_COMMAND_CPF_MISMATCH;
+ }
+ }
+
+ return valid;
+}
+
+// If there in an unknown segment type, then just set this segment to include
+// all of the data left.
+static void set_unknown_segment_type(size_t data_length,
+ CipSegment* segment)
+{
+ segment->type = CipSegment_Type_UNKNOWN;
+ segment->size = data_length;
+}
+
+// Return the timeout, in milliseconds, based on the priority/time_tick and time-out_ticks fields
+// that are common to: Unconnected Send, Forward Open, Forward Close.
+// This requires that enough data is available to read 2 bytes.
+static uint32_t get_unconnected_timeout(const uint8_t* data)
+{
+ #define UNCONNECTED_OFFSET_PRIORITY_TIME_TICK 0
+ #define UNCONNECTED_OFFSET_TIMEOUT_TICKS 1
+ #define TICK_TIME_MASK 0xF
+
+ uint8_t tick_time = data[UNCONNECTED_OFFSET_PRIORITY_TIME_TICK] & TICK_TIME_MASK;
+ uint8_t timeout_ticks = data[UNCONNECTED_OFFSET_TIMEOUT_TICKS];
+
+ return (1 << tick_time) * timeout_ticks;
+}
+
+// Parses RPI and Network Connection Parameters from a Forward Open Request.
+// Note: Assumes there is enough data to parse the RPI and Network Connection Parameters.
+static void parse_connection_parameters(const uint8_t* data,
+ bool large_forward_open,
+ CipConnectionParameters* connection_parameters)
+{
+ #define NULL_CONNECTION_TYPE_MASK 0x6000
+ static const uint32_t LARGE_NULL_CONNECTION_TYPE_MASK = 0x60000000;
+
+ // Offsets to the RPI and Network Connection Parameters data, from the RPI data.
+ #define OFFSET_RPI 0
+ #define OFFSET_NETWORK_PARAMETERS 4
+
+ connection_parameters->rpi = GetLEUint32(&data[OFFSET_RPI]);
+
+ if (!large_forward_open)
+ {
+ uint16_t network_connection_parameters = GetLEUint16(&data[OFFSET_NETWORK_PARAMETERS]);
+ connection_parameters->network_connection_parameters = network_connection_parameters;
+
+ if ((network_connection_parameters & NULL_CONNECTION_TYPE_MASK) == 0)
+ {
+ connection_parameters->is_null_connection = true;
+ }
+ }
+ else // SERVICE_LARGE_FORWARD_OPEN
+ {
+ uint32_t network_connection_parameters = GetLEUint32(&data[OFFSET_NETWORK_PARAMETERS]);
+ connection_parameters->network_connection_parameters = network_connection_parameters;
+
+ if ((network_connection_parameters & LARGE_NULL_CONNECTION_TYPE_MASK) == 0)
+ {
+ connection_parameters->is_null_connection = true;
+ }
+ }
+}
+
+// Note: Assumes there is enough data for a full Connection Signature.
+static void parse_connection_signature(const uint8_t* data,
+ CipConnectionSignature* connection_signature)
+{
+ #define OFFSET_CONNECTION_SERIAL 0
+ #define OFFSET_VENDOR 2
+ #define OFFSET_ORIGINATOR_SERIAL 4
+
+ connection_signature->connection_serial_number = GetLEUint16(&data[OFFSET_CONNECTION_SERIAL]);
+ connection_signature->vendor_id = GetLEUint16(&data[OFFSET_VENDOR]);
+ connection_signature->originator_serial_number = GetLEUint32(&data[OFFSET_ORIGINATOR_SERIAL]);
+}
+
+static bool parse_segment_electronic_key(const uint8_t* data,
+ size_t data_length,
+ CipSegment* segment)
+{
+ #define ELECTRONIC_KEY_FORMAT_TABLE 0x04
+ #define ELECTRONIC_KEY_FORMAT_TABLE_SIZE 10
+
+ // Check that there is enough size for the Key Format Table.
+ if (ELECTRONIC_KEY_FORMAT_TABLE_SIZE > data_length)
+ {
+ return false;
+ }
+
+ // Currently, the only supported Key Format is the Key Format Table.
+ #define ELECTRONIC_KEY_OFFSET_FORMAT_TABLE 1
+ if (data[ELECTRONIC_KEY_OFFSET_FORMAT_TABLE] != ELECTRONIC_KEY_FORMAT_TABLE)
+ {
+ return false;
+ }
+
+ segment->type = CipSegment_Type_LOGICAL_ELECTRONIC_KEY;
+ segment->size = ELECTRONIC_KEY_FORMAT_TABLE_SIZE;
+
+ return true;
+}
+
+static bool parse_segment_extended_symbol(const uint8_t* data,
+ size_t data_length,
+ CipSegment* segment)
+{
+ size_t symbol_size_bytes;
+ size_t segment_size_bytes;
+ symbol_size_bytes = data[CIP_PATH_SEGMENT_PAYLOAD_SIZE_OFFSET];
+
+ // calculate expected size
+ segment_size_bytes = CIP_PATH_SEGMENT_MIN_SIZE_BYTES + symbol_size_bytes;
+
+ // add padding
+ segment_size_bytes += segment_size_bytes % 2;
+
+ // Exit early, if we know we won't fit.
+ if (segment_size_bytes > data_length)
+ {
+ return false;
+ }
+
+ segment->type = CipSegment_Type_DATA_EXT_SYMBOL;
+ segment->data = &data[CIP_PATH_SEGMENT_MIN_SIZE_BYTES];
+ segment->data_size = symbol_size_bytes;
+ segment->size = segment_size_bytes;
+
+ return true;
+}
+
+static bool parse_segment_logical(const uint8_t* data,
+ size_t data_length,
+ CipSegment* segment)
+{
+ uint8_t segment_type = data[CIP_PATH_TYPE_OFFSET];
+
+ // parse particular logical type
+ bool valid = true;
+ #define CIP_PATH_LOGICAL_TYPE_MASK 0x1C
+ switch (segment_type & CIP_PATH_LOGICAL_TYPE_MASK)
+ {
+ case CIP_PATH_LOGICAL_CLASS:
+ segment->type = CipSegment_Type_LOGICAL_CLASS;
+ valid = parse_logical_address_format(data, data_length, false, segment);
+ break;
+ case CIP_PATH_LOGICAL_INSTANCE:
+ segment->type = CipSegment_Type_LOGICAL_INSTANCE;
+ valid = parse_logical_address_format(data, data_length, false, segment);
+ break;
+ case CIP_PATH_LOGICAL_MEMBER:
+ segment->type = CipSegment_Type_LOGICAL_MEMBER;
+ valid = parse_logical_address_format(data, data_length, false, segment);
+ break;
+ case CIP_PATH_LOGICAL_CONN_POINT:
+ segment->type = CipSegment_Type_LOGICAL_CONN_POINT;
+ valid = parse_logical_address_format(data, data_length, false, segment);
+ break;
+ case CIP_PATH_LOGICAL_ATTRIBUTE:
+ segment->type = CipSegment_Type_LOGICAL_ATTRIBUTE;
+ valid = parse_logical_address_format(data, data_length, false, segment);
+ break;
+ case CIP_PATH_LOGICAL_EXTENDED:
+ segment->type = CipSegment_Type_LOGICAL_EXTENDED;
+ valid = parse_logical_address_format(data, data_length, true, segment);
+ break;
+ case CIP_PATH_LOGICAL_SPECIAL:
+ {
+ // Logical Segment Electronic Key Logical Format.
+ #define CIP_PATH_SEGMENT_ELECTRONIC_KEY 0x34
+
+ if (segment_type == CIP_PATH_SEGMENT_ELECTRONIC_KEY)
+ {
+ valid = parse_segment_electronic_key(data,
+ data_length,
+ segment);
+ }
+ else
+ {
+ set_unknown_segment_type(data_length, segment);
+ }
+
+ break;
+ }
+ case CIP_PATH_LOGICAL_SERVICE_ID:
+ {
+ #define CIP_PATH_SEGMENT_SERVICE_ID 0x38
+ if (segment_type == CIP_PATH_SEGMENT_SERVICE_ID)
+ {
+ segment->type = CipSegment_Type_LOGICAL_SERVICE_ID;
+ valid = parse_logical_address_format(data, data_length, false, segment);
+ }
+ else
+ {
+ set_unknown_segment_type(data_length, segment);
+ }
+
+ break;
+ }
+ default:
+ // Can't happen.
+ set_unknown_segment_type(data_length, segment);
+ break;
+ }
+
+ return valid;
+}
+
+static bool parse_segment_network(const uint8_t* data,
+ size_t data_length,
+ CipSegment* segment)
+{
+ #define CIP_PATH_NETWORK_FORMAT_MASK 0xF0
+ #define CIP_PATH_NETWORK_ONE_BYTE 0x40
+
+ size_t segment_size_bytes = 0;
+
+ uint8_t segment_type = data[CIP_PATH_TYPE_OFFSET];
+ uint8_t network_segment_type = segment_type & CIP_PATH_NETWORK_FORMAT_MASK;
+ if (network_segment_type == CIP_PATH_NETWORK_ONE_BYTE)
+ {
+ segment_size_bytes = CIP_PATH_SEGMENT_MIN_SIZE_BYTES;
+ }
+ else // Variable length network segment (0x50)
+ {
+ size_t data_size_bytes = data[CIP_PATH_SEGMENT_PAYLOAD_SIZE_OFFSET] * CIP_WORD_TO_BYTES;
+ segment_size_bytes = CIP_PATH_SEGMENT_MIN_SIZE_BYTES + data_size_bytes;
+ if (segment_size_bytes > data_length)
+ {
+ return false;
+ }
+ }
+
+ segment->type = CipSegment_Type_NETWORK;
+ segment->size = segment_size_bytes;
+
+ return true;
+}
+
+static bool parse_segment_port(const uint8_t* data,
+ size_t data_length,
+ CipSegment* segment)
+{
+ uint8_t segment_type = data[CIP_PATH_TYPE_OFFSET];
+
+ // set minimal expected segment size
+ size_t segment_size_bytes = CIP_PATH_SEGMENT_MIN_SIZE_BYTES;
+
+ // port segment extended port threshold
+ #define CIP_PATH_PORT_EXTENDED 0x0F
+
+ // calculate simple port (extended port is also a mask)
+ uint16_t port_number = segment_type & CIP_PATH_PORT_EXTENDED;
+
+ bool is_port_extended = port_number == CIP_PATH_PORT_EXTENDED;
+
+ #define CIP_PATH_PORT_EXTENDED_LINK_ADDRESS_MASK 0x10
+ bool is_long_address = (segment_type & CIP_PATH_PORT_EXTENDED_LINK_ADDRESS_MASK) != 0;
+
+ if (is_long_address)
+ {
+ // add length of address
+ segment_size_bytes += data[CIP_PATH_SEGMENT_PAYLOAD_SIZE_OFFSET];
+
+ // add padding
+ segment_size_bytes += segment_size_bytes % 2;
+ }
+
+ if (is_port_extended)
+ {
+ // add length of extended port
+ segment_size_bytes += sizeof(uint16_t);
+ }
+
+ // Exit early, if we know we won't fit.
+ if (segment_size_bytes > data_length)
+ {
+ return false;
+ }
+
+ if (is_port_extended)
+ {
+ size_t extended_port_offset = CIP_PATH_SEGMENT_PAYLOAD_SIZE_OFFSET;
+
+ if (is_long_address)
+ {
+ extended_port_offset += sizeof(uint8_t);
+ }
+
+ port_number = GetLEUint16(&data[extended_port_offset]);
+ }
+
+ segment->port_id = port_number;
+
+ if (!is_long_address)
+ {
+ size_t link_address_offset = CIP_PATH_SEGMENT_PAYLOAD_SIZE_OFFSET;
+
+ if (is_port_extended)
+ {
+ link_address_offset += sizeof(uint16_t);
+ }
+
+ segment->type = CipSegment_Type_PORT_LINK_ADDRESS;
+ segment->link_address = data[link_address_offset];
+ }
+ else
+ {
+ size_t link_address_offset = CIP_PATH_SEGMENT_PAYLOAD_SIZE_OFFSET + sizeof(uint8_t);
+
+ if (is_port_extended)
+ {
+ link_address_offset += sizeof(uint16_t);
+ }
+
+ segment->type = CipSegment_Type_PORT_LINK_ADDRESS_EXTENDED;
+ segment->data = &data[link_address_offset];
+ segment->data_size = data[CIP_PATH_SEGMENT_PAYLOAD_SIZE_OFFSET];
+ }
+
+ segment->size = segment_size_bytes;
+
+ return true;
+}
+
+static bool parse_segment_simple_data(const uint8_t* data,
+ size_t data_length,
+ CipSegment* segment)
+{
+ size_t data_size_bytes
+ = data[CIP_PATH_SEGMENT_PAYLOAD_SIZE_OFFSET] * CIP_WORD_TO_BYTES;
+
+ // calculate expected size
+ size_t segment_size_bytes = CIP_PATH_SEGMENT_MIN_SIZE_BYTES + data_size_bytes;
+
+ // Exit early, if we know we won't fit.
+ if (segment_size_bytes > data_length)
+ {
+ return false;
+ }
+
+ segment->type = CipSegment_Type_DATA_SIMPLE;
+ segment->data = &data[CIP_PATH_SEGMENT_MIN_SIZE_BYTES];
+ segment->data_size = data_size_bytes;
+ segment->size = segment_size_bytes;
+
+ return true;
+}
+
+static bool parse_segment_symbolic_extended_string(const uint8_t* data,
+ size_t data_length,
+ CipSegment* segment)
+{
+ #define EXTENDED_STRING_SIZE_MASK 0x1F
+ #define EXTENDED_STRING_FORMAT_MASK 0xE0
+
+ #define NUMERIC_SYMBOL_USINT 6
+ #define NUMERIC_SYMBOL_UINT 7
+ #define NUMERIC_SYMBOL_UDINT 8
+
+ #define DOUBLE_BYTE 2
+ #define TRIPLE_BYTE 3
+
+ uint8_t extended_format_byte = data[CIP_PATH_SEGMENT_PAYLOAD_SIZE_OFFSET];
+ uint8_t extended_format_size = extended_format_byte & EXTENDED_STRING_SIZE_MASK;
+
+ bool valid = true;
+ size_t data_size = 0;
+ switch (extended_format_byte & EXTENDED_STRING_FORMAT_MASK)
+ {
+ case EXTENDED_STRING_DOUBLE:
+ data_size = extended_format_size * DOUBLE_BYTE;
+ break;
+ case EXTENDED_STRING_TRIPLE:
+ data_size = extended_format_size * TRIPLE_BYTE;
+ break;
+ case EXTENDED_STRING_NUMERIC:
+ if (extended_format_size == NUMERIC_SYMBOL_USINT)
+ {
+ data_size = sizeof(uint8_t);
+ }
+ else if (extended_format_size == NUMERIC_SYMBOL_UINT)
+ {
+ data_size = sizeof(uint16_t);
+ }
+ else if (extended_format_size == NUMERIC_SYMBOL_UDINT)
+ {
+ data_size = sizeof(uint32_t);
+ }
+ else
+ {
+ valid = false;
+ }
+ break;
+ default:
+ valid = false;
+ break;
+ }
+
+ size_t segment_size_bytes = CIP_PATH_SEGMENT_MIN_SIZE_BYTES + data_size;
+
+ // Add padding.
+ segment_size_bytes += segment_size_bytes % 2;
+
+ if (data_length < segment_size_bytes)
+ {
+ return false;
+ }
+
+ segment->type = CipSegment_Type_SYMBOLIC;
+ segment->data = &data[CIP_PATH_SEGMENT_MIN_SIZE_BYTES];
+ segment->data_size = data_size;
+ segment->size = segment_size_bytes;
+
+ return valid;
+}
+
+static bool parse_segment_symbolic(const uint8_t* data,
+ size_t data_length,
+ CipSegment* segment)
+{
+ #define CIP_PATH_SYMBOLIC_SIZE_MASK 0x1F
+
+ bool valid = true;
+
+ uint8_t symbol_size_bytes = data[CIP_PATH_TYPE_OFFSET] & CIP_PATH_SYMBOLIC_SIZE_MASK;
+ if (symbol_size_bytes == 0)
+ {
+ valid = parse_segment_symbolic_extended_string(data, data_length, segment);
+ }
+ else // Size 1 - 31.
+ {
+ size_t expected_segment_size = CIP_PATH_SEGMENT_PAYLOAD_SIZE_OFFSET + symbol_size_bytes;
+
+ // Add padding
+ expected_segment_size += expected_segment_size % 2;
+
+ if (expected_segment_size > data_length)
+ {
+ valid = false;
+ }
+ else
+ {
+ segment->type = CipSegment_Type_SYMBOLIC;
+ segment->data = &data[CIP_PATH_SEGMENT_PAYLOAD_SIZE_OFFSET];
+ segment->data_size = symbol_size_bytes;
+ segment->size = expected_segment_size;
+ }
+ }
+
+ return valid;
+}
+
+static bool parse_cip_segment(const uint8_t* data,
+ size_t data_length,
+ CipSegment* segment)
+{
+ bool valid = true;
+
+ #define CIP_PATH_SEGMENT_TYPE_MASK 0xE0
+
+ uint8_t segment_type = data[CIP_PATH_TYPE_OFFSET];
+ switch (segment_type & CIP_PATH_SEGMENT_TYPE_MASK)
+ {
+ case CIP_PATH_SEGMENT_PORT:
+ valid = parse_segment_port(
+ data,
+ data_length,
+ segment);
+ break;
+ case CIP_PATH_SEGMENT_LOGICAL:
+ valid = parse_segment_logical(
+ data,
+ data_length,
+ segment);
+ break;
+ case CIP_PATH_SEGMENT_DATA:
+ {
+ #define CIP_PATH_SEGMENT_SIMPLE_DATA 0x80
+ #define CIP_PATH_SEGMENT_EXT_SYMBOL 0x91
+
+ if (segment_type == CIP_PATH_SEGMENT_EXT_SYMBOL)
+ {
+ valid = parse_segment_extended_symbol(
+ data,
+ data_length,
+ segment);
+ }
+ else if (segment_type == CIP_PATH_SEGMENT_SIMPLE_DATA)
+ {
+ valid = parse_segment_simple_data(
+ data,
+ data_length,
+ segment);
+ }
+ else
+ {
+ set_unknown_segment_type(data_length, segment);
+ }
+ break;
+ }
+ case CIP_PATH_SEGMENT_NETWORK:
+ valid = parse_segment_network(data, data_length, segment);
+ break;
+ case CIP_PATH_SEGMENT_SYMBOLIC:
+ valid = parse_segment_symbolic(data, data_length, segment);
+ break;
+ default:
+ set_unknown_segment_type(data_length, segment);
+ break;
+ }
+
+ return valid;
+}
+
+static bool parse_cip_segments(const uint8_t* data,
+ size_t data_length,
+ CipPath* path)
+{
+ bool valid = true;
+
+ // Parse all CIP segments.
+ while (data_length > 0)
+ {
+ CipSegment segment;
+ /* Check that there is enough data to start. */
+ if (data_length < CIP_PATH_SEGMENT_MIN_SIZE_BYTES)
+ {
+ valid = false;
+ break;
+ }
+
+ memset(&segment, 0, sizeof(segment));
+ if (!parse_cip_segment(data, data_length, &segment))
+ {
+ valid = false;
+ break;
+ }
+
+ // Save off key data in this segment for later use.
+ if (segment.type == CipSegment_Type_LOGICAL_CLASS)
+ {
+ path->has_class_id = true;
+ path->class_id = segment.logical_value;
+
+ path->primary_segment_type = CipSegment_Type_LOGICAL_CLASS;
+ }
+ else if (segment.type == CipSegment_Type_DATA_EXT_SYMBOL)
+ {
+ path->primary_segment_type = CipSegment_Type_DATA_EXT_SYMBOL;
+ }
+ else if (segment.type == CipSegment_Type_LOGICAL_INSTANCE)
+ {
+ path->has_instance_id = true;
+ path->instance_id = segment.logical_value;
+ }
+ else if (segment.type == CipSegment_Type_LOGICAL_ATTRIBUTE)
+ {
+ path->has_attribute_id = true;
+ path->attribute_id = segment.logical_value;
+ }
+ else if (segment.type == CipSegment_Type_UNKNOWN)
+ {
+ path->has_unknown_segment = true;
+ }
+
+ // Move to the next segment.
+ data_length -= segment.size;
+ data += segment.size;
+ }
+
+ return valid;
+}
+
+static bool parse_cip_epath(const uint8_t* data,
+ size_t data_length,
+ bool path_contains_reserved_byte,
+ CipPath* path)
+{
+ #define PATH_SIZE_FIELD_BYTES 1
+ #define PATH_SIZE_OFFSET 0
+ size_t path_size_bytes;
+ size_t path_header_size;
+
+ // There is a size byte and optionally a padding byte before the actual path data.
+ path_header_size = PATH_SIZE_FIELD_BYTES;
+ if (path_contains_reserved_byte)
+ {
+ path_header_size++;
+ }
+
+ // Validate/Get the Path Size.
+ if (data_length < path_header_size)
+ {
+ return false;
+ }
+
+ path_size_bytes = data[PATH_SIZE_OFFSET] * CIP_WORD_TO_BYTES;
+ if (data_length - path_header_size < path_size_bytes)
+ {
+ return false;
+ }
+
+ if (!parse_cip_segments(data + path_header_size, path_size_bytes, path))
+ {
+ return false;
+ }
+
+ path->full_path_size = path_header_size + path_size_bytes;
+ path->decoded = true;
+
+ return true;
+}
+
+// Parse the logical addressing format which is common to all logical segment
+// types, except Special and Service ID.
+static bool parse_logical_address_format(const uint8_t* data,
+ size_t data_length,
+ bool logical_extended,
+ CipSegment* segment)
+{
+ #define LOGICAL_8_BIT_SIZE 2
+ #define LOGICAL_8_BIT_EXTENDED_SIZE 4
+ #define LOGICAL_16_BIT_SIZE 4
+ #define LOGICAL_32_BIT_SIZE 6
+ #define LOGICAL_DEFAULT_DATA_OFFSET 2
+ #define LOGICAL_8_BIT_DATA_OFFSET 1
+
+ uint32_t logical_value;
+ bool valid = true;
+
+ uint8_t segment_type = data[CIP_PATH_TYPE_OFFSET];
+
+ // Get the expected segment size and data offset.
+ size_t segment_size = 0;
+ size_t data_offset = LOGICAL_DEFAULT_DATA_OFFSET;
+ switch (segment_type & CIP_PATH_LOGICAL_FORMAT_MASK)
+ {
+ case CIP_PATH_LOGICAL_32_BIT:
+ segment_size = LOGICAL_32_BIT_SIZE;
+ break;
+ case CIP_PATH_LOGICAL_16_BIT:
+ segment_size = LOGICAL_16_BIT_SIZE;
+ break;
+ case CIP_PATH_LOGICAL_8_BIT:
+ if (logical_extended)
+ {
+ segment_size = LOGICAL_8_BIT_EXTENDED_SIZE;
+ }
+ else
+ {
+ segment_size = LOGICAL_8_BIT_SIZE;
+ data_offset = LOGICAL_8_BIT_DATA_OFFSET;
+ }
+ break;
+ default:
+ valid = false;
+ break;
+ }
+
+ // Exit early, if we know we won't fit.
+ if (segment_size > data_length)
+ {
+ return false;
+ }
+
+ // Get the logical value.
+ logical_value = 0;
+ switch (segment_type & CIP_PATH_LOGICAL_FORMAT_MASK)
+ {
+ case CIP_PATH_LOGICAL_32_BIT:
+ logical_value = GetLEUint32(&data[data_offset]);
+ break;
+ case CIP_PATH_LOGICAL_16_BIT:
+ logical_value = GetLEUint16(&data[data_offset]);
+ break;
+ case CIP_PATH_LOGICAL_8_BIT:
+ logical_value = data[data_offset];
+ break;
+ default:
+ valid = false;
+ break;
+ }
+
+ segment->logical_value = logical_value;
+ segment->size = segment_size;
+
+ return valid;
+}
+
+static bool parse_cip_status(const uint8_t* data,
+ size_t data_length,
+ CipStatus* status)
+{
+ if (data_length < CIP_STATUS_MIN_SIZE)
+ {
+ return false;
+ }
+
+ #define CIP_STATUS_OFFSET_GEN_STATUS 0
+ status->general_status = data[CIP_STATUS_OFFSET_GEN_STATUS];
+
+ #define CIP_STATUS_OFFSET_EXT_STATUS_SIZE 1
+ status->extended_status_size = data[CIP_STATUS_OFFSET_EXT_STATUS_SIZE] * CIP_WORD_TO_BYTES;
+
+ // extended status size does not fit the response
+ if (data_length < (CIP_STATUS_MIN_SIZE + status->extended_status_size))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+/// Forward Open/Close parsing.
+static bool parse_forward_open_request(const uint8_t* data,
+ size_t data_length,
+ bool large_forward_open,
+ CipForwardOpenRequest* forward_open_request,
+ CipRequest* cip_request)
+{
+ // This includes all data up to, but not including, the Connection Path Size.
+ #define CIP_FORWARD_OPEN_PREFIX_SIZE 35
+ #define CIP_LARGE_FORWARD_OPEN_PREFIX_SIZE 39
+ #define FWD_OPEN_OFFSET_CONN_SIGNATURE 10
+ #define DEFAULT_CONNECTION_TIMEOUT (10 * USEC_PER_SEC)
+ size_t forward_open_prefix_size;
+
+ // Size of the common connection-related parameters fields. This includes
+ // the RPI and the Network Connection Parameters.
+ size_t connection_parameters_size;
+ size_t offset_to_parameters;
+ size_t offset_transport_type_trigger;
+ size_t offset_connection_path_size;
+ uint8_t connection_timeout_multiplier;
+ const bool NO_PATH_RESERVED_BYTE = false;
+
+ if (large_forward_open)
+ {
+ connection_parameters_size = sizeof(uint32_t) + sizeof(uint32_t);
+ forward_open_prefix_size = CIP_LARGE_FORWARD_OPEN_PREFIX_SIZE;
+ }
+ else
+ {
+ connection_parameters_size = sizeof(uint32_t) + sizeof(uint16_t);
+ forward_open_prefix_size = CIP_FORWARD_OPEN_PREFIX_SIZE;
+ }
+
+ // Ensure that there is enough data for the common part of a Forward Open.
+ if (data_length < forward_open_prefix_size)
+ {
+ return false;
+ }
+ data_length -= forward_open_prefix_size;
+
+ #define FWD_OPEN_OFFSET_TIMEOUT_MULTIPLIER 18
+ #define FWD_OPEN_OFFSET_OT_RPI 22
+ offset_to_parameters = FWD_OPEN_OFFSET_OT_RPI + connection_parameters_size;
+ offset_transport_type_trigger = offset_to_parameters + connection_parameters_size;
+ offset_connection_path_size = offset_transport_type_trigger + 1;
+
+ forward_open_request->timeout_ms = get_unconnected_timeout(data);
+ parse_connection_signature(&data[FWD_OPEN_OFFSET_CONN_SIGNATURE],
+ &forward_open_request->connection_signature);
+ parse_connection_parameters(&data[FWD_OPEN_OFFSET_OT_RPI],
+ large_forward_open,
+ &forward_open_request->ot_parameters);
+ parse_connection_parameters(&data[offset_to_parameters],
+ large_forward_open,
+ &forward_open_request->to_parameters);
+
+ // Get the overall connection timeouts.
+ connection_timeout_multiplier = data[FWD_OPEN_OFFSET_TIMEOUT_MULTIPLIER];
+ #define MULTIPLIER_DEFAULT 2
+ #define MAX_TIMEOUT_MULTIPLIER 7
+ if (connection_timeout_multiplier <= MAX_TIMEOUT_MULTIPLIER)
+ {
+ uint16_t actual_multiplier = 1 << (connection_timeout_multiplier + MULTIPLIER_DEFAULT);
+ forward_open_request->ot_connection_timeout_us
+ = forward_open_request->ot_parameters.rpi * actual_multiplier;
+ forward_open_request->to_connection_timeout_us
+ = forward_open_request->to_parameters.rpi * actual_multiplier;
+ }
+ else
+ {
+ cip_request->cip_req_invalid_nonfatal |= CIP_REQ_INVALID_TIMEOUT_MULTIPLIER;
+ forward_open_request->ot_connection_timeout_us = DEFAULT_CONNECTION_TIMEOUT;
+ forward_open_request->to_connection_timeout_us = DEFAULT_CONNECTION_TIMEOUT;
+ }
+
+ if (forward_open_request->ot_parameters.is_null_connection
+ && forward_open_request->to_parameters.is_null_connection)
+ {
+ forward_open_request->is_null_forward_open = true;
+ }
+
+ uint8_t transport_type_trigger = data[offset_transport_type_trigger];
+ forward_open_request->transport_class = transport_type_trigger & TRANSPORT_CLASS_MASK;
+
+ // Parse out the Connection Path. This is a variable length section.
+ bool valid = parse_cip_epath(&data[offset_connection_path_size],
+ data_length,
+ NO_PATH_RESERVED_BYTE,
+ &forward_open_request->connection_path);
+
+ return valid;
+}
+
+static bool parse_forward_open_response_success(const uint8_t* data,
+ size_t data_length,
+ CipForwardOpenResponse* forward_open_response)
+{
+ #define FWD_OPEN_OFFSET_CON_SIGNATURE 8
+ #define CIP_FORWARD_OPEN_RESPONSE_PREFIX_SIZE 26
+ if (data_length < CIP_FORWARD_OPEN_RESPONSE_PREFIX_SIZE)
+ {
+ return false;
+ }
+
+ #define FWD_OPEN_OFFSET_OT_CONNECTION 0
+ #define FWD_OPEN_OFFSET_TO_CONNECTION 4
+ #define FWD_OPEN_OFFSET_REPLY_SIZE 24
+
+ forward_open_response->connection_pair.ot_connection_id
+ = GetLEUint32(&data[FWD_OPEN_OFFSET_OT_CONNECTION]);
+ forward_open_response->connection_pair.to_connection_id
+ = GetLEUint32(&data[FWD_OPEN_OFFSET_TO_CONNECTION]);
+ parse_connection_signature(&data[FWD_OPEN_OFFSET_CON_SIGNATURE],
+ &forward_open_response->connection_signature);
+ forward_open_response->application_reply_size
+ = data[FWD_OPEN_OFFSET_REPLY_SIZE] * CIP_WORD_TO_BYTES;
+
+ data_length -= CIP_FORWARD_OPEN_RESPONSE_PREFIX_SIZE;
+ if (data_length < forward_open_response->application_reply_size)
+ {
+ return false;
+ }
+
+ forward_open_response->success = true;
+
+ return true;
+}
+
+static bool parse_forward_open_response_fail(const uint8_t* data,
+ size_t data_length,
+ CipForwardOpenResponse* forward_open_response)
+{
+ #define CIP_FORWARD_OPEN_RESPONSE_FAIL_SIZE 10
+ if (data_length < CIP_FORWARD_OPEN_RESPONSE_FAIL_SIZE)
+ {
+ return false;
+ }
+
+ parse_connection_signature(data, &forward_open_response->connection_signature);
+
+ forward_open_response->success = false;
+
+ return true;
+}
+
+static bool parse_forward_open_response(const uint8_t* data,
+ size_t data_length,
+ uint8_t response_status,
+ CipForwardOpenResponse* forward_open_response)
+{
+ bool valid = true;
+
+ // Forward Open Success and Failure cases have different formats.
+ if (response_status == CIP_STATUS_SUCCESS)
+ {
+ valid = parse_forward_open_response_success(data, data_length, forward_open_response);
+ }
+ else
+ {
+ valid = parse_forward_open_response_fail(data, data_length, forward_open_response);
+ }
+
+ return valid;
+}
+
+static bool parse_forward_close_request(const uint8_t* data,
+ size_t data_length,
+ CipForwardCloseRequest* forward_close_request)
+{
+ bool valid;
+ const bool PATH_RESERVED_BYTE = true;
+ #define CIP_FORWARD_CLOSE_PREFIX_SIZE 10
+ if (data_length < CIP_FORWARD_CLOSE_PREFIX_SIZE)
+ {
+ return false;
+ }
+
+ #define FWD_CLOSE_OFFSET_CONNECTION_SIGNATURE 2
+
+ forward_close_request->timeout_ms = get_unconnected_timeout(data);
+ parse_connection_signature(&data[FWD_CLOSE_OFFSET_CONNECTION_SIGNATURE],
+ &forward_close_request->connection_signature);
+
+ // Parse out the Connection Path. This is a variable length section.
+ valid = parse_cip_epath(data + CIP_FORWARD_CLOSE_PREFIX_SIZE,
+ data_length - CIP_FORWARD_CLOSE_PREFIX_SIZE,
+ PATH_RESERVED_BYTE,
+ &forward_close_request->connection_path);
+
+ return valid;
+}
+
+// Returns size of the CIP Request Header that was parsed.
+static bool parse_cip_request_header(const uint8_t* data,
+ size_t data_length,
+ size_t* header_size,
+ CipRequest* cip_request)
+{
+ bool valid;
+ CipPath* path;
+ const bool NO_PATH_RESERVED_BYTE = false;
+ #define CIP_SERVICE_SIZE 1
+ if (data_length < CIP_SERVICE_SIZE)
+ {
+ return false;
+ }
+
+ #define CIP_SERVICE_OFFSET 0
+ cip_request->service = data[CIP_SERVICE_OFFSET];
+
+ // Reset all path information.
+ memset(&cip_request->request_path, 0, sizeof(cip_request->request_path));
+ path = &cip_request->request_path;
+
+ valid = parse_cip_epath(data + CIP_SERVICE_SIZE,
+ data_length - CIP_SERVICE_SIZE,
+ NO_PATH_RESERVED_BYTE,
+ path);
+ if (!valid)
+ {
+ return false;
+ }
+
+ if (path->has_unknown_segment)
+ {
+ cip_request->cip_req_invalid_nonfatal |= CIP_REQ_INVALID_UNKNOWN_SEGMENT;
+ }
+
+ *header_size = CIP_SERVICE_SIZE + path->full_path_size;
+
+ return true;
+}
+
+static size_t cip_status_size(const CipStatus* status)
+{
+ return CIP_STATUS_MIN_SIZE + status->extended_status_size;
+}
+
+static bool parse_multiple_service_packet(const uint8_t* data,
+ size_t data_length,
+ CipRequest* cip_request,
+ CipGlobalSessionData* global_data)
+{
+ // Save the original data length for use in handling the offsets of embedded services.
+ uint16_t number_services;
+ size_t total_offset_size;
+ size_t data_offset;
+ size_t first_offset;
+ bool valid;
+ uint16_t i;
+ size_t original_data_length = data_length;
+
+ // Check that the number of services will fit.
+ #define CIP_MSP_NUMBER_SERVICES_FIELD_SIZE 2
+ if (data_length < CIP_MSP_NUMBER_SERVICES_FIELD_SIZE)
+ {
+ return false;
+ }
+
+ #define CIP_MSP_OFFSET_NUMBER_SERVICES 0
+ number_services = data[CIP_MSP_OFFSET_NUMBER_SERVICES];
+ data_length -= CIP_MSP_NUMBER_SERVICES_FIELD_SIZE;
+
+ // Check that the offsets will fit.
+ #define CIP_MSP_OFFSET_FIELD_SIZE 2
+ total_offset_size = number_services * CIP_MSP_OFFSET_FIELD_SIZE;
+ if (data_length < total_offset_size)
+ {
+ return false;
+ }
+
+ // Length of actual data left after the offsets.
+ data_length -= total_offset_size;
+
+ // Check that offset data starts after the last offset.
+ data_offset = CIP_MSP_NUMBER_SERVICES_FIELD_SIZE + total_offset_size;
+ first_offset = GetLEUint16(&data[CIP_MSP_NUMBER_SERVICES_FIELD_SIZE]);
+ if (first_offset < data_offset)
+ {
+ return false;
+ }
+
+ valid = true;
+
+ // Process each embedded service.
+ for (i = 1; i <= number_services; ++i)
+ {
+ size_t msp_length;
+ CipRequest embedded_request;
+ CipEventData cip_event_data;
+ CipEvent cip_event(global_data->snort_packet, &cip_event_data);
+
+ /* This if the offset from the Number of Services field, to the Offset field. */
+ uint16_t buffer_offset = i * CIP_MSP_OFFSET_FIELD_SIZE;
+
+ /* This if the offset from the Number of Services field to the data. */
+ size_t msp_offset = GetLEUint16(&data[buffer_offset]);
+
+ /* There is no end offset specified, so the next offset needs checked
+ to find the length of the current service. For the last packet,
+ this needs to use the total length of the Multiple Service Packet. */
+ size_t msp_offset_end = 0;
+ if (i == number_services)
+ {
+ msp_offset_end = original_data_length;
+ }
+ else
+ {
+ uint16_t next_buffer_offset = buffer_offset + CIP_MSP_OFFSET_FIELD_SIZE;
+ msp_offset_end = GetLEUint16(&data[next_buffer_offset]);
+ }
+
+ // Check that offsets are increasing.
+ if (msp_offset >= msp_offset_end)
+ {
+ valid = false;
+ break;
+ }
+
+ // Check embedded length against the data size left.
+ msp_length = msp_offset_end - msp_offset;
+ if (data_length < msp_length)
+ {
+ valid = false;
+ break;
+ }
+
+ data_length -= msp_length;
+
+ memset(&embedded_request, 0, sizeof(embedded_request));
+ if (!parse_message_router_request(data + msp_offset,
+ msp_length,
+ &embedded_request,
+ global_data))
+ {
+ valid = false;
+ break;
+ }
+
+ // Store embedded packet errors in the parent request.
+ cip_request->cip_req_invalid_nonfatal |= embedded_request.cip_req_invalid_nonfatal;
+
+ // Publish embedded CIP data to appid.
+ memset(&cip_event_data, 0, sizeof(cip_event_data));
+
+ pack_cip_request_event(&embedded_request, &cip_event_data);
+
+ DataBus::publish(CIP_EVENT_TYPE_CIP_DATA_KEY, cip_event, global_data->snort_packet->flow);
+ }
+
+ return valid;
+}
+
+static bool parse_unconnected_send_request(const uint8_t* data,
+ size_t data_length,
+ CipRequest* cip_request,
+ CipGlobalSessionData* global_data)
+{
+ bool valid;
+ uint16_t message_request_size;
+ const bool PATH_RESERVED_BYTE = true;
+ // This includes: Timeout data, embedded message size.
+ #define UNCONNECTED_SEND_HEADER_SIZE 4
+ if (data_length < UNCONNECTED_SEND_HEADER_SIZE)
+ {
+ return false;
+ }
+
+ cip_request->timeout_ms = get_unconnected_timeout(data);
+ cip_request->has_timeout = true;
+
+ #define UNCONNECTED_SEND_OFFSET_MESSAGE_SIZE 2
+ message_request_size = GetLEUint16(&data[UNCONNECTED_SEND_OFFSET_MESSAGE_SIZE]);
+
+ data += UNCONNECTED_SEND_HEADER_SIZE;
+ data_length -= UNCONNECTED_SEND_HEADER_SIZE;
+
+ // Verify that expected length of embedded request will fit in actual data.
+ if (message_request_size > data_length)
+ {
+ return false;
+ }
+
+ if (!parse_message_router_request(data, message_request_size, cip_request, global_data))
+ {
+ return false;
+ }
+
+ // Parse the Route Path.
+ valid = parse_cip_epath(data + message_request_size,
+ data_length - message_request_size,
+ PATH_RESERVED_BYTE,
+ &cip_request->route_path);
+
+ return valid;
+}
+
+static bool parse_cip_command_specific_data_request(const uint8_t* data,
+ size_t data_length,
+ CipRequest* cip_request,
+ CipGlobalSessionData* global_data)
+{
+ const CipProtoConf* config;
+ /* If the request path doesn't have a Class ID, then we don't know how to
+ parse the response.*/
+ if (!cip_request->request_path.has_class_id)
+ {
+ cip_request->request_type = CipRequestTypeOther;
+ return true;
+ }
+
+ bool valid = true;
+
+ uint8_t service = cip_request->service;
+ uint32_t class_id = cip_request->request_path.class_id;
+ if (service == SERVICE_MULTIPLE_SERVICE_PACKET)
+ {
+ valid = parse_multiple_service_packet(data, data_length, cip_request, global_data);
+ cip_request->request_type = CipRequestTypeMultipleServiceRequest;
+ }
+ else if (class_id == CONNECTION_MANAGER_CLASS_ID
+ && service == CONNECTION_MANAGER_UNCONNECTED_SEND)
+ {
+ valid = parse_unconnected_send_request(data,
+ data_length,
+ cip_request,
+ global_data);
+ cip_request->request_type = CipRequestTypeUnconnectedSend;
+ }
+ else if (class_id == CONNECTION_MANAGER_CLASS_ID
+ && (service == CONNECTION_MANAGER_FORWARD_OPEN
+ || service == CONNECTION_MANAGER_LARGE_FORWARD_OPEN))
+ {
+ CipForwardOpenRequest forward_open_request;
+ memset(&forward_open_request, 0, sizeof(forward_open_request));
+
+ bool large_forward_open = (service == CONNECTION_MANAGER_LARGE_FORWARD_OPEN);
+ valid = parse_forward_open_request(data,
+ data_length,
+ large_forward_open,
+ &forward_open_request,
+ cip_request);
+ forward_open_request.timestamp = global_data->snort_packet->pkth->ts;
+
+ if (valid)
+ {
+ // Only store connection information for Class 3, Non-Null connections.
+ if (!forward_open_request.is_null_forward_open
+ && forward_open_request.transport_class == 3)
+ {
+ if (!cip_add_connection_to_pending(&global_data->connection_list,
+ &forward_open_request))
+ {
+ // Error if the connection couldn't be added to the list.
+ cip_request->cip_req_invalid_nonfatal |= CIP_REQ_INVALID_CONNECTION_ADD_FAILED;
+ }
+ }
+
+ cip_request->is_forward_open_request = true;
+ cip_request->connection_path_class_id = forward_open_request.connection_path.class_id;
+ cip_request->timeout_ms = forward_open_request.timeout_ms;
+ cip_request->has_timeout = true;
+ }
+
+ cip_request->request_type = CipRequestTypeForwardOpen;
+ }
+ else if (class_id == CONNECTION_MANAGER_CLASS_ID
+ && service == CONNECTION_MANAGER_FORWARD_CLOSE)
+ {
+ CipForwardCloseRequest forward_close_request;
+ memset(&forward_close_request, 0, sizeof(forward_close_request));
+
+ valid = parse_forward_close_request(data,
+ data_length,
+ &forward_close_request);
+
+ if (valid)
+ {
+ const bool connection_established = true;
+ cip_remove_connection(&global_data->connection_list,
+ &forward_close_request.connection_signature,
+ connection_established);
+
+ cip_request->timeout_ms = forward_close_request.timeout_ms;
+ cip_request->has_timeout = true;
+ }
+
+ cip_request->request_type = CipRequestTypeForwardClose;
+ }
+ else
+ {
+ // This is a regular CIP request. No need to parse data.
+ cip_request->request_type = CipRequestTypeOther;
+ }
+
+ // Parse any embedded CIP packet that is configured.
+ config = global_data->config;
+ if (config->embedded_cip_enabled
+ && class_id == config->embedded_cip_class_id
+ && service == config->embedded_cip_service_id)
+ {
+ valid = parse_message_router_request(data, data_length, cip_request, global_data);
+ }
+
+ return valid;
+}
+
+static bool parse_cip_command_specific_data_response(const CipStatus* status,
+ const uint8_t* data,
+ size_t data_length,
+ CipRequestType request_type,
+ CipGlobalSessionData* global_data)
+{
+ bool valid = true;
+
+ if (request_type == CipRequestTypeForwardOpen)
+ {
+ CipForwardOpenResponse forward_open_response;
+ memset(&forward_open_response, 0, sizeof(forward_open_response));
+
+ valid = parse_forward_open_response(data,
+ data_length,
+ status->general_status,
+ &forward_open_response);
+ forward_open_response.timestamp = global_data->snort_packet->pkth->ts;
+
+ if (forward_open_response.success)
+ {
+ cip_add_connection_to_active(&global_data->connection_list, &forward_open_response);
+ }
+ else
+ {
+ const bool connection_established = false;
+ cip_remove_connection(&global_data->connection_list,
+ &forward_open_response.connection_signature,
+ connection_established);
+ }
+ }
+
+ return valid;
+}
+
+
+static bool parse_message_router_request(const uint8_t* data,
+ size_t data_length,
+ CipRequest* cip_request,
+ CipGlobalSessionData* global_data)
+{
+ size_t header_size = 0;
+ if (!parse_cip_request_header(data,
+ data_length,
+ &header_size,
+ cip_request))
+ {
+ return false;
+ }
+
+ cip_request->cip_data = data + header_size;
+ cip_request->cip_data_size = data_length - header_size;
+
+ bool valid = parse_cip_command_specific_data_request(data + header_size,
+ data_length - header_size,
+ cip_request,
+ global_data);
+
+ return valid;
+}
+
+static bool parse_message_router_response(const uint8_t* data,
+ size_t data_length,
+ CipRequestType request_type,
+ CipResponse* cip_response,
+ CipGlobalSessionData* global_data)
+{
+ bool valid;
+ size_t response_header_size;
+ size_t status_size;
+ #define MESSAGE_ROUTER_RESPONSE_MIN_SIZE 4
+ if (data_length < MESSAGE_ROUTER_RESPONSE_MIN_SIZE)
+ {
+ return false;
+ }
+
+ #define CIP_SERVICE_OFFSET 0
+ #define CIP_STATUS_OFFSET 2
+
+ cip_response->service = data[CIP_SERVICE_OFFSET] & ~MESSAGE_ROUTER_RESPONSE_MASK;
+
+ if (!parse_cip_status(data + CIP_STATUS_OFFSET,
+ data_length - CIP_STATUS_OFFSET,
+ &cip_response->status))
+ {
+ return false;
+ }
+
+ status_size = cip_status_size(&cip_response->status);
+
+ // This includes: Service, reserved field, total status data.
+ response_header_size = CIP_STATUS_OFFSET + status_size;
+
+ valid = true;
+ // Don't attempt to decode the command specific response if there wasn't a
+ // match to an existing request.
+ if (request_type != CipRequestTypeNoMatchFound)
+ {
+ valid = parse_cip_command_specific_data_response(&cip_response->status,
+ data + response_header_size,
+ data_length - response_header_size,
+ request_type,
+ global_data);
+ }
+
+ return valid;
+}
+
+// Returns true if the serviceId was a request service.
+static bool is_service_request(uint8_t service_id)
+{
+ return (service_id & MESSAGE_ROUTER_RESPONSE_MASK) == 0;
+}
+
+static bool parse_message_router(const uint8_t* data,
+ size_t data_length,
+ CipCurrentData* current_data,
+ CipGlobalSessionData* global_data)
+{
+ bool valid = true;
+
+ CipMessage* cip_msg = ¤t_data->cip_msg;
+
+ cip_msg->is_cip_request = is_service_request(*data);
+ if (cip_msg->is_cip_request)
+ {
+ cip_msg->request.request_type = CipRequestTypeOther;
+ valid = parse_message_router_request(data,
+ data_length,
+ &cip_msg->request,
+ global_data);
+
+ cip_request_add(&global_data->unconnected_list,
+ ¤t_data->enip_data,
+ &cip_msg->request,
+ &global_data->snort_packet->pkth->ts);
+ }
+ else
+ {
+ CipRequestType request_type = CipRequestTypeNoMatchFound;
+ cip_request_remove(&global_data->unconnected_list,
+ ¤t_data->enip_data,
+ &request_type);
+
+ valid = parse_message_router_response(data,
+ data_length,
+ request_type,
+ &cip_msg->response,
+ global_data);
+ }
+
+ return valid;
+}
+
+// Returns true if this data contains valid CIP Explicit data. The data must already:
+// 1. Be ENIP_COMMAND_SEND_UNIT_DATA or ENIP_COMMAND_SEND_RR_DATA
+// 2. Have the required CPF items for that ENIP command.
+static bool parse_cip_explicit_data(CipCurrentData* current_data, CipGlobalSessionData* global_data)
+{
+ // Assume that all data values/length inside the EnipCpf are valid.
+ const EnipCpf* enip_cpf = ¤t_data->enip_data.enip_cpf;
+ const EnipCpfItem* cpf_item = &enip_cpf->item_list[CPF_DATA_ITEM_SLOT];
+
+ // Get the offset of the CIP Message Router data.
+ size_t cpf_data_offset = 0;
+ if (cpf_item->type == CPF_CONNECTED_DATA_ITEM_ID)
+ {
+ // For CIP Class 3 data, Connected Data contains: Sequence Count, then CIP Data.
+ const size_t CPF_CONNECTED_DATA_SEQUENCE_COUNT_SIZE = sizeof(uint16_t);
+ cpf_data_offset = CPF_CONNECTED_DATA_SEQUENCE_COUNT_SIZE;
+ }
+ else // CPF_UNCONNECTED_DATA_ITEM_ID
+ {
+ cpf_data_offset = 0;
+ }
+
+ bool valid = false;
+ if (cpf_item->length > cpf_data_offset)
+ {
+ const uint8_t* message_router_data = cpf_item->data + cpf_data_offset;
+ size_t message_router_data_length = cpf_item->length - cpf_data_offset;
+
+ valid = parse_message_router(message_router_data,
+ message_router_data_length,
+ current_data,
+ global_data);
+ }
+
+ return valid;
+}
+
+// Used for parsing SendRRData and SendUnitData Command Specific Data.
+static bool parse_enip_command_data(const uint8_t* data,
+ size_t data_length,
+ CipCurrentData* current_data,
+ CipGlobalSessionData* global_data)
+{
+ uint32_t interface_handle;
+
+ // This should always contain: Interface Handle, Timeout.
+#define ENIP_COMMAND_HEADER_SIZE 6
+ if (data_length < ENIP_COMMAND_HEADER_SIZE)
+ {
+ return false;
+ }
+
+ // Interface Handle
+ #define ENIP_OFFSET_INTERFACE_HANDLE 0
+ interface_handle = GetLEUint32(&data[ENIP_OFFSET_INTERFACE_HANDLE]);
+
+#define CIP_INTERFACE_HANDLE 0
+ if (interface_handle != CIP_INTERFACE_HANDLE)
+ {
+ current_data->enip_data.enip_invalid_nonfatal |= ENIP_INVALID_INTERFACE_HANDLE;
+ }
+
+ // Parse the Encapsulated Data as Common Packet Format.
+ current_data->enip_data.cpf_decoded = parse_common_packet_format(data + ENIP_COMMAND_HEADER_SIZE,
+ data_length - ENIP_COMMAND_HEADER_SIZE,
+ ¤t_data->enip_data.enip_cpf,
+ current_data);
+ if (!current_data->enip_data.cpf_decoded)
+ {
+ return false;
+ }
+
+ // Exit early if CIP Explicit Data cannot be processed.
+ if (!current_data->enip_data.required_cpf_items_present)
+ {
+ return true;
+ }
+
+ current_data->cip_message_type = get_cip_message_type(current_data, global_data);
+
+ bool valid = true;
+ if (current_data->cip_message_type == CipMessageTypeExplicit)
+ {
+ valid = parse_cip_explicit_data(current_data, global_data);
+ }
+
+ return valid;
+}
+
+bool parse_enip_layer(const uint8_t* data,
+ size_t data_length,
+ bool is_TCP,
+ CipCurrentData* current_data,
+ CipGlobalSessionData* global_data)
+{
+ const EnipHeader* enip_header;
+ current_data->enip_data.enip_decoded = parse_enip_header(data,
+ data_length,
+ ¤t_data->enip_data);
+ if (!current_data->enip_data.enip_decoded)
+ {
+ return false;
+ }
+
+ // Command Specific Data
+ data += ENIP_HEADER_SIZE;
+ data_length -= ENIP_HEADER_SIZE;
+
+ // Verify that actual data matches data length field.
+ enip_header = ¤t_data->enip_data.enip_header;
+ if (data_length < enip_header->length)
+ {
+ return false;
+ }
+
+ if (enip_command_tcp_only(enip_header->command) && !is_TCP)
+ {
+ // Flag as an error and exit early because there would be no way to tie this data
+ // to a particular TCP session.
+ current_data->enip_data.enip_invalid_nonfatal |= ENIP_INVALID_ENIP_TCP_ONLY;
+
+ return true;
+ }
+
+ if (enip_header->status != ENIP_STATUS_SUCCESS)
+ {
+ if (current_data->direction == CIP_FROM_CLIENT)
+ {
+ current_data->enip_data.enip_invalid_nonfatal |= ENIP_INVALID_STATUS;
+ }
+ else if (current_data->direction == CIP_FROM_SERVER)
+ {
+ // Remove any outstanding request.
+ CipRequestType request_type = CipRequestTypeNoMatchFound;
+ cip_request_remove(&global_data->unconnected_list,
+ ¤t_data->enip_data,
+ &request_type);
+ }
+
+ // No more processing after a non-success status.
+ return true;
+ }
+
+ bool valid = true;
+
+ switch (enip_header->command)
+ {
+ case ENIP_COMMAND_REGISTER_SESSION:
+ {
+ if (data_length < REGISTER_SESSION_DATA_SIZE)
+ {
+ valid = false;
+ break;
+ }
+
+ // Check that there is no active ENIP session.
+ if (current_data->direction == CIP_FROM_CLIENT)
+ {
+ if (global_data->enip_session.active)
+ {
+ current_data->enip_data.enip_invalid_nonfatal |= ENIP_INVALID_DUPLICATE_SESSION;
+ }
+ }
+
+ // Add ENIP session to the current TCP session.
+ if (current_data->direction == CIP_FROM_SERVER)
+ {
+ if (!enip_session_add(&global_data->enip_session,
+ enip_header->session_handle))
+ {
+ current_data->enip_data.enip_invalid_nonfatal |= ENIP_INVALID_DUPLICATE_SESSION;
+ }
+ }
+ }
+ break;
+
+ case ENIP_COMMAND_SEND_RR_DATA:
+ case ENIP_COMMAND_SEND_UNIT_DATA:
+ valid = parse_enip_command_data(data, data_length, current_data, global_data);
+
+ // Check that the Session Handle matches the active ENIP session.
+ if (!enip_session_handle_valid(&global_data->enip_session, enip_header->session_handle))
+ {
+ current_data->enip_data.enip_invalid_nonfatal |= ENIP_INVALID_SESSION_HANDLE;
+ }
+ break;
+ case ENIP_COMMAND_NOP:
+ break;
+ case ENIP_COMMAND_LIST_SERVICES:
+ case ENIP_COMMAND_LIST_IDENTITY:
+ case ENIP_COMMAND_LIST_INTERFACES:
+ if (current_data->direction == CIP_FROM_SERVER)
+ {
+ current_data->enip_data.cpf_decoded = parse_common_packet_format(data,
+ data_length,
+ ¤t_data->enip_data.enip_cpf,
+ current_data);
+ if (!current_data->enip_data.cpf_decoded)
+ {
+ valid = false;
+ }
+ }
+ break;
+ case ENIP_COMMAND_UNREGISTER_SESSION:
+ // Remove ENIP session from the current TCP session.
+ enip_session_remove(&global_data->enip_session, enip_header->session_handle);
+ break;
+ default:
+ // Ignore legacy cases.
+ break;
+ }
+
+ return valid;
+}
+
+void pack_cip_request_event(const CipRequest* request, CipEventData* cip_event_data)
+{
+ cip_event_data->service_id = request->service;
+
+ if (request->is_forward_open_request)
+ {
+ cip_event_data->type = CIP_DATA_TYPE_CONNECTION;
+ cip_event_data->class_id = request->connection_path_class_id;
+ }
+ else if (request->request_path.primary_segment_type == CipSegment_Type_LOGICAL_CLASS)
+ {
+ // Publish Set Attribute Single services separately than other requests.
+ if (cip_event_data->service_id == SERVICE_SET_ATTRIBUTE_SINGLE
+ && request->request_path.has_instance_id
+ && request->request_path.has_attribute_id)
+ {
+ cip_event_data->instance_id = request->request_path.instance_id;
+ cip_event_data->attribute_id = request->request_path.attribute_id;
+
+ cip_event_data->type = CIP_DATA_TYPE_SET_ATTRIBUTE;
+ }
+ else
+ {
+ cip_event_data->type = CIP_DATA_TYPE_PATH_CLASS;
+ }
+
+ cip_event_data->class_id = request->request_path.class_id;
+ }
+ else if (request->request_path.primary_segment_type == CipSegment_Type_DATA_EXT_SYMBOL)
+ {
+ cip_event_data->type = CIP_DATA_TYPE_PATH_EXT_SYMBOL;
+ }
+ else
+ {
+ cip_event_data->type = CIP_DATA_TYPE_OTHER;
+ }
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2014-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// cip_parsing.h author RA/Cisco
+
+/* Description: Data parsing for EtherNet/IP and CIP formats. */
+
+#ifndef CIP_PARSING_H
+#define CIP_PARSING_H
+
+#include <cstdint>
+#include <cstring>
+#include "pub_sub/cip_events.h" // For CipEventData
+
+#include "cip_definitions.h" // For CIP structs
+
+namespace snort
+{
+struct Packet;
+}
+
+//// EtherNet/IP Parsing
+
+// Constants - EtherNet/IP encapsulation layer
+#define ENIP_HEADER_SIZE 24u
+
+// EtherNet/IP commands.
+enum EnipCommand
+{
+ ENIP_COMMAND_NOP = 0x0000,
+ ENIP_COMMAND_LIST_SERVICES = 0x0004,
+ ENIP_COMMAND_LIST_IDENTITY = 0x0063,
+ ENIP_COMMAND_LIST_INTERFACES = 0x0064,
+ ENIP_COMMAND_REGISTER_SESSION = 0x0065,
+ ENIP_COMMAND_UNREGISTER_SESSION = 0x0066,
+ ENIP_COMMAND_SEND_RR_DATA = 0x006F,
+ ENIP_COMMAND_SEND_UNIT_DATA = 0x0070
+};
+
+/// EtherNet/IP data parsing functions.
+bool parse_enip_layer(const uint8_t* data,
+ size_t data_length,
+ bool is_TCP,
+ CipCurrentData* current_data,
+ CipGlobalSessionData* global_data);
+
+void pack_cip_request_event(const CipRequest* request, CipEventData* cip_event_data);
+
+#endif // CIP_PARSING_H
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2014-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// cip_session.cc author RA/Cisco
+
+/* Description: Functions for managing CIP state data across multiple packets and TCP connections.
+ Note: Performance of all lookup functions is O(n). */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "cip_session.h"
+
+#include <math.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+#include <sys/time.h>
+#include "time/timersub.h" // For TIMERSUB
+
+#include "cip_parsing.h" // For CIP constants
+
+static uint32_t f_unconnected_timeout_ms = DEFAULT_UNCONNECTED_REQUEST_TIMEOUT;
+
+bool enip_session_add(EnipSession* enip_session, uint32_t session_handle)
+{
+ // Only 1 ENIP session per TCP connection is allowed.
+ if (enip_session->active)
+ {
+ return false;
+ }
+
+ enip_session->session_handle = session_handle;
+ enip_session->active = true;
+
+ return true;
+}
+
+bool enip_session_remove(EnipSession* enip_session, uint32_t session_handle)
+{
+ if (!enip_session->active)
+ {
+ return false;
+ }
+
+ if (enip_session->session_handle != session_handle)
+ {
+ return false;
+ }
+
+ enip_session->active = false;
+
+ return true;
+}
+
+bool enip_session_handle_valid(const EnipSession* enip_session, uint32_t session_handle)
+{
+ return (enip_session->active && enip_session->session_handle == session_handle);
+}
+
+/// CIP Request and Connection Management.
+static void prune_cip_unconnected_list(CipUnconnectedMessageList* unconnected_list,
+ const struct timeval* timestamp)
+{
+ struct timeval timestamp_diff;
+
+ bool pruned = false;
+ size_t oldest_slot = 0;
+
+ // Prune any message that has exceeded the CIP timeout.
+ size_t i;
+ for (i = 0; i < unconnected_list->list_size; ++i)
+ {
+ if (unconnected_list->list[i].slot_active)
+ {
+ TIMERSUB(timestamp, &unconnected_list->list[i].timestamp, ×tamp_diff);
+
+ // Round up to the nearest whole second.
+ uint32_t timeout_sec
+ = ceil(unconnected_list->list[i].timeout_ms / (double)MSEC_PER_SEC);
+
+ // If the message timeout has been exceeded, remove the request from the list.
+ if (timestamp_diff.tv_sec > timeout_sec)
+ {
+ unconnected_list->list[i].slot_active = false;
+ unconnected_list->count--;
+ pruned = true;
+ }
+
+ // Check if the current item's timestamp is older than the previous oldest.
+ if (timercmp(&unconnected_list->list[i].timestamp,
+ &unconnected_list->list[oldest_slot].timestamp,
+ <) != 0)
+ {
+ oldest_slot = i;
+ }
+ }
+ }
+
+ // If no timeout was exceeded, prune the oldest one.
+ if (!pruned)
+ {
+ unconnected_list->list[oldest_slot].slot_active = false;
+ unconnected_list->count--;
+ unconnected_list->request_pruned = true;
+ }
+}
+
+static void prune_cip_connection_list(CipConnectionList* connection_list,
+ const struct timeval* timestamp)
+{
+ struct timeval ot_timestamp_diff;
+ struct timeval to_timestamp_diff;
+
+ bool pruned = false;
+
+ size_t stale_slot = 0;
+ struct timeval stale_timestamp_diff;
+ memset(&stale_timestamp_diff, 0, sizeof(stale_timestamp_diff));
+
+ // Prune any connection that has exceeded the CIP timeout.
+ size_t i;
+ for (i = 0; i < connection_list->list_size; ++i)
+ {
+ if (connection_list->list[i].slot_active)
+ {
+ TIMERSUB(timestamp, &connection_list->list[i].ot_timestamp, &ot_timestamp_diff);
+ TIMERSUB(timestamp, &connection_list->list[i].to_timestamp, &to_timestamp_diff);
+
+ // If either OT or TO connection timeouts have been exceeded, remove the connection
+ // from the list.
+ if (ot_timestamp_diff.tv_sec > connection_list->list[i].ot_connection_timeout_sec
+ || to_timestamp_diff.tv_sec > connection_list->list[i].to_connection_timeout_sec)
+ {
+ connection_list->list[i].slot_active = false;
+ connection_list->count--;
+ pruned = true;
+ }
+
+ // Pick the most recent timestamp for this connection.
+ struct timeval connection_timestamp;
+ if (timercmp(&connection_list->list[i].ot_timestamp,
+ &connection_list->list[i].to_timestamp,
+ >) != 0)
+ {
+ connection_timestamp = connection_list->list[i].ot_timestamp;
+ }
+ else
+ {
+ connection_timestamp = connection_list->list[i].to_timestamp;
+ }
+
+ struct timeval timestamp_diff;
+ TIMERSUB(timestamp, &connection_timestamp, ×tamp_diff);
+
+ // Check if the current connection is more stale than the previously found one.
+ if (timercmp(×tamp_diff, &stale_timestamp_diff, >) != 0)
+ {
+ stale_slot = i;
+ stale_timestamp_diff = timestamp_diff;
+ }
+ }
+ }
+
+ // If no timeout was exceeded, prune the least recently used one.
+ if (!pruned)
+ {
+ connection_list->list[stale_slot].slot_active = false;
+ connection_list->count--;
+ connection_list->connection_pruned = true;
+ }
+}
+
+static bool cip_connection_signature_match(const CipConnectionSignature* left,
+ const CipConnectionSignature* right)
+{
+ if (left->connection_serial_number == right->connection_serial_number
+ && left->originator_serial_number == right->originator_serial_number
+ && left->vendor_id == right->vendor_id)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+static CipConnection* cip_find_connection_slot(CipConnectionList* connection_list,
+ const struct timeval* timestamp)
+{
+ CipConnection* connection = NULL;
+
+ // Prune old connections if the list is at max capacity.
+ if (connection_list->count == connection_list->list_size)
+ {
+ prune_cip_connection_list(connection_list, timestamp);
+ }
+
+ size_t i;
+ for (i = 0; i < connection_list->list_size; ++i)
+ {
+ if (!connection_list->list[i].slot_active)
+ {
+ connection = &connection_list->list[i];
+ break;
+ }
+ }
+
+ return connection;
+}
+
+CipConnection* cip_find_connection_by_id(
+ CipConnectionList* connection_list,
+ CipPacketDirection direction,
+ uint32_t connection_id,
+ bool established)
+{
+ CipConnection* connection = NULL;
+
+ size_t i;
+ for (i = 0; i < connection_list->list_size; ++i)
+ {
+ if (connection_list->list[i].slot_active
+ && (connection_list->list[i].established == established))
+ {
+ if (direction == CIP_FROM_CLIENT
+ && connection_list->list[i].connection_id_pair.ot_connection_id == connection_id)
+ {
+ connection = &connection_list->list[i];
+ break;
+ }
+
+ if (direction == CIP_FROM_SERVER
+ && connection_list->list[i].connection_id_pair.to_connection_id == connection_id)
+ {
+ connection = &connection_list->list[i];
+ break;
+ }
+ }
+ }
+
+ return connection;
+}
+
+static const CipConnection* cip_find_connection_by_id_any(
+ const CipConnectionList* connection_list,
+ uint32_t ot_connection_id,
+ uint32_t to_connection_id)
+{
+ const CipConnection* connection = NULL;
+
+ size_t i;
+ for (i = 0; i < connection_list->list_size; ++i)
+ {
+ if (connection_list->list[i].slot_active && connection_list->list[i].established)
+ {
+ if (connection_list->list[i].connection_id_pair.ot_connection_id == ot_connection_id)
+ {
+ connection = &connection_list->list[i];
+ break;
+ }
+
+ if (connection_list->list[i].connection_id_pair.to_connection_id == to_connection_id)
+ {
+ connection = &connection_list->list[i];
+ break;
+ }
+ }
+ }
+
+ return connection;
+}
+
+static const CipConnection* cip_find_connection_any(const CipConnectionList* connection_list,
+ const CipConnectionSignature* signature)
+{
+ const CipConnection* connection = NULL;
+
+ size_t i;
+ for (i = 0; i < connection_list->list_size; ++i)
+ {
+ if (connection_list->list[i].slot_active
+ && cip_connection_signature_match(&connection_list->list[i].signature, signature))
+ {
+ connection = &connection_list->list[i];
+ break;
+ }
+ }
+
+ return connection;
+}
+
+static CipConnection* cip_find_connection(CipConnectionList* connection_list,
+ const CipConnectionSignature* signature,
+ bool established)
+{
+ CipConnection* connection = NULL;
+
+ size_t i;
+ for (i = 0; i < connection_list->list_size; ++i)
+ {
+ if (connection_list->list[i].slot_active
+ && (connection_list->list[i].established == established)
+ && cip_connection_signature_match(&connection_list->list[i].signature, signature))
+ {
+ connection = &connection_list->list[i];
+ break;
+ }
+ }
+
+ return connection;
+}
+
+bool cip_add_connection_to_active(CipConnectionList* connection_list,
+ const CipForwardOpenResponse* forward_open_response)
+{
+ // Check that no existing connection has a matching connection ID for either direction.
+ const CipConnection* existing_connection = cip_find_connection_by_id_any(connection_list,
+ forward_open_response->connection_pair.ot_connection_id,
+ forward_open_response->connection_pair.to_connection_id);
+ if (existing_connection)
+ {
+ return false;
+ }
+
+ // Find the existing pending connection.
+ CipConnection* connection = cip_find_connection(connection_list,
+ &forward_open_response->connection_signature,
+ false);
+ if (!connection)
+ {
+ return false;
+ }
+
+ // Save the new Connection ID information, and mark the connection as
+ // fully established.
+ connection->connection_id_pair = forward_open_response->connection_pair;
+ connection->established = true;
+ connection->to_timestamp = forward_open_response->timestamp;
+
+ return true;
+}
+
+bool cip_remove_connection(CipConnectionList* connection_list,
+ const CipConnectionSignature* connection_signature,
+ bool established)
+{
+ CipConnection* connection = cip_find_connection(connection_list,
+ connection_signature,
+ established);
+ if (!connection)
+ {
+ return false;
+ }
+
+ connection->slot_active = false;
+ connection_list->count--;
+
+ return true;
+}
+
+bool cip_add_connection_to_pending(CipConnectionList* connection_list,
+ const CipForwardOpenRequest* forward_open_request)
+{
+ // Check that there are no pending or existing connections with this signature.
+ const CipConnection* existing_connection = cip_find_connection_any(connection_list,
+ &forward_open_request->connection_signature);
+ if (existing_connection)
+ {
+ return false;
+ }
+
+ CipConnection* connection = cip_find_connection_slot(connection_list,
+ &forward_open_request->timestamp);
+ if (!connection)
+ {
+ return false;
+ }
+
+ connection->signature = forward_open_request->connection_signature;
+ connection->class_id = forward_open_request->connection_path.class_id;
+ connection->established = false;
+
+ // Round up to the nearest whole second.
+ connection->ot_connection_timeout_sec
+ = ceil(forward_open_request->ot_connection_timeout_us / (double)USEC_PER_SEC);
+ connection->to_connection_timeout_sec
+ = ceil(forward_open_request->to_connection_timeout_us / (double)USEC_PER_SEC);
+
+ connection->ot_timestamp = forward_open_request->timestamp;
+ connection->to_timestamp = forward_open_request->timestamp;
+
+ connection->slot_active = true;
+ connection_list->count++;
+
+ return true;
+}
+
+/// CIP Request/Response Matching.
+static CipUnconnectedMessage* find_unconnected_request_slot(
+ CipUnconnectedMessageList* unconnected_list,
+ const struct timeval* timestamp)
+{
+ CipUnconnectedMessage* unconnected_message = NULL;
+
+ // Prune old messages if the list is at max capacity.
+ if (unconnected_list->count == unconnected_list->list_size)
+ {
+ prune_cip_unconnected_list(unconnected_list, timestamp);
+ }
+
+ size_t i;
+ for (i = 0; i < unconnected_list->list_size; ++i)
+ {
+ if (!unconnected_list->list[i].slot_active)
+ {
+ unconnected_message = &unconnected_list->list[i];
+ break;
+ }
+ }
+
+ return unconnected_message;
+}
+
+static CipUnconnectedMessage* find_unconnected_request(
+ CipUnconnectedMessageList* unconnected_list,
+ uint64_t sender_context)
+{
+ CipUnconnectedMessage* unconnected_message = NULL;
+
+ size_t i;
+ for (i = 0; i < unconnected_list->list_size; ++i)
+ {
+ if (unconnected_list->list[i].slot_active
+ && unconnected_list->list[i].sender_context == sender_context)
+ {
+ unconnected_message = &unconnected_list->list[i];
+ break;
+ }
+ }
+
+ return unconnected_message;
+}
+
+bool cip_request_add(CipUnconnectedMessageList* unconnected_list,
+ const EnipSessionData* enip_data,
+ const CipRequest* cip_request,
+ const struct timeval* timestamp)
+{
+ bool valid = true;
+
+ if (enip_data->enip_header.command == ENIP_COMMAND_SEND_RR_DATA)
+ {
+ CipUnconnectedMessage* slot = find_unconnected_request_slot(unconnected_list, timestamp);
+ if (slot)
+ {
+ slot->sender_context = enip_data->enip_header.sender_context;
+ slot->request_type = cip_request->request_type;
+
+ if (cip_request->has_timeout)
+ {
+ slot->timeout_ms = cip_request->timeout_ms;
+ }
+ else
+ {
+ slot->timeout_ms = f_unconnected_timeout_ms;
+ }
+
+ slot->timestamp = *timestamp;
+ slot->slot_active = true;
+ unconnected_list->count++;
+
+ valid = true;
+ }
+ else
+ {
+ valid = false;
+ }
+ }
+
+ return valid;
+}
+
+bool cip_request_remove(CipUnconnectedMessageList* unconnected_list,
+ const EnipSessionData* enip_data,
+ CipRequestType* request_type)
+{
+ bool valid = true;
+
+ if (enip_data->enip_header.command == ENIP_COMMAND_SEND_RR_DATA)
+ {
+ CipUnconnectedMessage* request = find_unconnected_request(unconnected_list,
+ enip_data->enip_header.sender_context);
+ if (request)
+ {
+ *request_type = request->request_type;
+
+ // Remove the request from the list.
+ request->slot_active = false;
+ unconnected_list->count--;
+
+ valid = true;
+ }
+ else
+ {
+ valid = false;
+ }
+ }
+
+ return valid;
+}
+
+void set_unconnected_timeout(uint32_t unconnected_timeout)
+{
+ f_unconnected_timeout_ms = unconnected_timeout;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2014-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// cip_session.h author RA/Cisco
+
+/* Description: Functions for managing CIP state data across multiple packets and TCP connections.
+ */
+
+#ifndef CIP_SESSION_H
+#define CIP_SESSION_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "main/snort_config.h"
+#include "main/snort_types.h"
+
+#include "cip_definitions.h"
+
+// Default unconnected request timeout, milliseconds.
+#define DEFAULT_UNCONNECTED_REQUEST_TIMEOUT (30000)
+
+/// ENIP Session Management.
+bool enip_session_add(EnipSession* enip_session, uint32_t session_handle);
+bool enip_session_remove(EnipSession* enip_session, uint32_t session_handle);
+
+// Returns true if session_handle matches the active session.
+bool enip_session_handle_valid(const EnipSession* enip_session, uint32_t session_handle);
+
+/// CIP Connection Management.
+CipConnection* cip_find_connection_by_id(
+ CipConnectionList* connection_list,
+ CipPacketDirection direction,
+ uint32_t connection_id,
+ bool established);
+
+bool cip_add_connection_to_active(CipConnectionList* connection_list,
+ const CipForwardOpenResponse* forward_open_response);
+bool cip_remove_connection(CipConnectionList* connection_list,
+ const CipConnectionSignature* connection_signature,
+ bool established);
+
+bool cip_add_connection_to_pending(CipConnectionList* connection_list,
+ const CipForwardOpenRequest* forward_open_request);
+
+/// CIP Request/Response Matching.
+bool cip_request_add(CipUnconnectedMessageList* unconnected_list,
+ const EnipSessionData* enip_data,
+ const CipRequest* cip_request,
+ const struct timeval* timestamp);
+
+// Find a request in the list, and remove it.
+bool cip_request_remove(CipUnconnectedMessageList* unconnected_list,
+ const EnipSessionData* enip_data,
+ CipRequestType* request_type);
+
+// Set timeout (milliseconds) to use for unconnected messages that don't have a built-in timeout.
+void set_unconnected_timeout(uint32_t unconnected_timeout);
+
+#endif // CIP_SESSION_H
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2014-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// cip_util.h author RA/Cisco
+
+/* Description: Common utility functions. */
+
+#ifndef CIP_UTIL_H
+#define CIP_UTIL_H
+
+#include <sys/types.h> // For endian checks
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+// Get 16-bit value from little endian byte array.
+static inline uint16_t GetLEUint16(const uint8_t* pData)
+{
+ return (static_cast<uint16_t>(*(pData + 1) << 8)
+ | static_cast<uint16_t>(*(pData + 0) << 0));
+}
+
+// Get 32-bit value from little endian byte array.
+static inline uint32_t GetLEUint32(const uint8_t* pData)
+{
+ return (static_cast<uint32_t>(*(pData + 3) << 24)
+ | static_cast<uint32_t>(*(pData + 2) << 16)
+ | static_cast<uint32_t>(*(pData + 1) << 8)
+ | static_cast<uint32_t>(*(pData + 0) << 0));
+}
+
+#else // __BYTE_ORDER
+#error "CIP Preprocessor is only supported on Little Endian."
+#endif // __BYTE_ORDER
+
+#endif /* CIP_UTIL_H */
+
--- /dev/null
+CIP (Common Industrial Protocol) is a protocol used in SCADA networks. The
+standard is managed by ODVA (Open DeviceNet Vendor Association).
+
+The CIP inspector decodes the CIP protocol and provides rule options to
+access certain protocol fields. This allows a user to write rules for CIP
+packets without decoding the protocol with a series of ”content” and ”byte
+test” options.
+
+The preprocessor only evaluates PAF-flushed PDUs. If the rule options don't
+check for this, they'll fire on stale session data when the original packet
+goes through before flushing.
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// ips_cip_attribute.cc author Jian Wu <jiawu2@cisco.com>
+
+/* Description: Rule options for CIP preprocessor */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "framework/cursor.h"
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "framework/range.h"
+#include "hash/hashfcn.h"
+#include "profiler/profiler.h"
+#include "protocols/packet.h"
+
+#include "cip.h"
+
+using namespace snort;
+
+#define s_name "cip_attribute"
+#define s_help \
+ "detection option to match CIP attribute"
+
+//-------------------------------------------------------------------------
+// CIP Attribute rule option
+//-------------------------------------------------------------------------
+
+static THREAD_LOCAL ProfileStats cip_attribute_perf_stats;
+
+class CipAttributeOption : public IpsOption
+{
+public:
+ CipAttributeOption(RangeCheck& v) : IpsOption(s_name)
+ { cip_attr = v; }
+
+ uint32_t hash() const override;
+ bool operator==(const IpsOption&) const override;
+ EvalStatus eval(Cursor&, Packet*) override;
+
+private:
+ RangeCheck cip_attr;
+};
+
+uint32_t CipAttributeOption::hash() const
+{
+ uint32_t a, b, c;
+
+ a = cip_attr.op;
+ b = cip_attr.min;
+ c = cip_attr.max;
+
+ mix_str(a, b, c, get_name());
+ finalize(a,b,c);
+
+ return c;
+}
+
+bool CipAttributeOption::operator==(const IpsOption& ips) const
+{
+ if ( strcmp(get_name(), ips.get_name()) )
+ return false;
+
+ const CipAttributeOption& rhs = static_cast<const CipAttributeOption&>(ips);
+ return ( cip_attr == rhs.cip_attr );
+}
+
+IpsOption::EvalStatus CipAttributeOption::eval(Cursor&, Packet* p)
+{
+ Profile profile(cip_attribute_perf_stats);
+
+ if ( !p->flow || !p->is_full_pdu() )
+ return NO_MATCH;
+
+ CipFlowData* fd = static_cast<CipFlowData*>(p->flow->get_flow_data(CipFlowData::inspector_id));
+
+ if (!fd)
+ return NO_MATCH;
+
+ CipSessionData* session_data = &fd->session;
+
+ if (session_data->current_data.cip_message_type != CipMessageTypeExplicit
+ || !session_data->current_data.cip_msg.is_cip_request
+ || !session_data->current_data.cip_msg.request.request_path.has_attribute_id)
+ {
+ return NO_MATCH;
+ }
+
+ if ( cip_attr.eval(session_data->current_data.cip_msg.request.request_path.attribute_id) )
+ {
+ return MATCH;
+ }
+
+ return NO_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+#define RANGE "0:65535"
+
+static const Parameter s_params[] =
+{
+ { "~range", Parameter::PT_INTERVAL, RANGE, nullptr,
+ "match CIP attribute" },
+
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+class CipAttributeModule : public Module
+{
+public:
+ CipAttributeModule() : Module(s_name, s_help, s_params) { }
+
+ bool begin(const char*, int, SnortConfig*) override;
+ bool set(const char*, Value&, SnortConfig*) override;
+ ProfileStats* get_profile() const override;
+
+ Usage get_usage() const override
+ { return DETECT; }
+
+public:
+ RangeCheck cip_attr;
+};
+
+bool CipAttributeModule::begin(const char*, int, SnortConfig*)
+{
+ cip_attr.init();
+ return true;
+}
+
+bool CipAttributeModule::set(const char*, Value& v, SnortConfig*)
+{
+ if ( !v.is("~range") )
+ return false;
+
+ return cip_attr.validate(v.get_string(), RANGE);
+}
+
+ProfileStats* CipAttributeModule::get_profile() const
+{
+ return &cip_attribute_perf_stats;
+}
+
+//-------------------------------------------------------------------------
+// api
+//-------------------------------------------------------------------------
+
+static Module* cip_attribute_mod_ctor()
+{
+ return new CipAttributeModule;
+}
+
+static void cip_attribute_mod_dtor(Module* m)
+{
+ delete m;
+}
+
+static IpsOption* cip_attribute_ctor(Module* p, OptTreeNode*)
+{
+ CipAttributeModule* m = static_cast<CipAttributeModule*>(p);
+ return new CipAttributeOption(m->cip_attr);
+}
+
+static void cip_attribute_dtor(IpsOption* p)
+{
+ delete p;
+}
+
+static const IpsApi ips_api =
+{
+ {
+ PT_IPS_OPTION,
+ sizeof(IpsApi),
+ IPSAPI_VERSION,
+ 0,
+ API_RESERVED,
+ API_OPTIONS,
+ s_name,
+ s_help,
+ cip_attribute_mod_ctor,
+ cip_attribute_mod_dtor
+ },
+ OPT_TYPE_DETECTION,
+ 0, PROTO_BIT__TCP | PROTO_BIT__UDP,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ cip_attribute_ctor,
+ cip_attribute_dtor,
+ nullptr
+};
+
+const BaseApi* ips_cip_attribute = &ips_api.base;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// ips_cip_class.cc author Jian Wu <jiawu2@cisco.com>
+
+/* Description: Rule options for CIP preprocessor */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "framework/cursor.h"
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "framework/range.h"
+#include "hash/hashfcn.h"
+#include "profiler/profiler.h"
+#include "protocols/packet.h"
+
+#include "cip.h"
+
+using namespace snort;
+
+#define s_name "cip_class"
+#define s_help \
+ "detection option to match CIP class"
+
+//-------------------------------------------------------------------------
+// CIP Class rule option
+//-------------------------------------------------------------------------
+
+static THREAD_LOCAL ProfileStats cip_class_perf_stats;
+
+class CipClassOption : public IpsOption
+{
+public:
+ CipClassOption(const RangeCheck& v) : IpsOption(s_name)
+ { cip_class = v; }
+
+ uint32_t hash() const override;
+ bool operator==(const IpsOption&) const override;
+
+ EvalStatus eval(Cursor&, Packet*) override;
+
+private:
+ RangeCheck cip_class;
+};
+
+uint32_t CipClassOption::hash() const
+{
+ uint32_t a,b,c;
+
+ a = cip_class.op;
+ b = cip_class.min;
+ c = cip_class.max;
+
+ mix_str(a, b, c, get_name());
+ finalize(a,b,c);
+
+ return c;
+}
+
+bool CipClassOption::operator==(const IpsOption& ips) const
+{
+ if ( strcmp(get_name(), ips.get_name()) )
+ return false;
+
+ const CipClassOption& rhs = static_cast<const CipClassOption&>(ips);
+ return (cip_class == rhs.cip_class);
+}
+
+IpsOption::EvalStatus CipClassOption::eval(Cursor&, Packet* p)
+{
+ Profile profile(cip_class_perf_stats);
+
+ if ( !p->flow || !p->is_full_pdu() )
+ return NO_MATCH;
+
+ CipFlowData* fd = static_cast<CipFlowData*>(p->flow->get_flow_data(CipFlowData::inspector_id));
+
+ if (!fd)
+ return NO_MATCH;
+
+ CipSessionData* session_data = &fd->session;
+
+ if (session_data->current_data.cip_message_type != CipMessageTypeExplicit
+ || !session_data->current_data.cip_msg.is_cip_request
+ || !session_data->current_data.cip_msg.request.request_path.has_class_id)
+ {
+ return NO_MATCH;
+ }
+
+ if ( cip_class.eval(session_data->current_data.cip_msg.request.request_path.class_id) )
+ return MATCH;
+
+ return NO_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+#define RANGE "0:65535"
+
+static const Parameter s_params[] =
+{
+ { "~range", Parameter::PT_INTERVAL, RANGE, nullptr,
+ "match CIP class" },
+
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+class CipClassModule : public Module
+{
+public:
+ CipClassModule() : Module(s_name, s_help, s_params) { }
+
+ bool begin(const char*, int, SnortConfig*) override;
+ bool set(const char*, Value&, SnortConfig*) override;
+ ProfileStats* get_profile() const override;
+
+ Usage get_usage() const override
+ { return DETECT; }
+
+public:
+ RangeCheck cip_class;
+};
+
+bool CipClassModule::begin(const char*, int, SnortConfig*)
+{
+ cip_class.init();
+ return true;
+}
+
+bool CipClassModule::set(const char*, Value& v, SnortConfig*)
+{
+ if ( !v.is("~range") )
+ return false;
+
+ return cip_class.validate(v.get_string(), RANGE);
+}
+
+ProfileStats* CipClassModule::get_profile() const
+{
+ return &cip_class_perf_stats;
+}
+
+//-------------------------------------------------------------------------
+// api
+//-------------------------------------------------------------------------
+
+static Module* cip_class_mod_ctor()
+{
+ return new CipClassModule;
+}
+
+static void cip_class_mod_dtor(Module* m)
+{
+ delete m;
+}
+
+static IpsOption* cip_class_ctor(Module* p, OptTreeNode*)
+{
+ CipClassModule* m = static_cast<CipClassModule*>(p);
+ return new CipClassOption(m->cip_class);
+}
+
+static void cip_class_dtor(IpsOption* p)
+{
+ delete p;
+}
+
+static const IpsApi ips_api =
+{
+ {
+ PT_IPS_OPTION,
+ sizeof(IpsApi),
+ IPSAPI_VERSION,
+ 0,
+ API_RESERVED,
+ API_OPTIONS,
+ s_name,
+ s_help,
+ cip_class_mod_ctor,
+ cip_class_mod_dtor
+ },
+ OPT_TYPE_DETECTION,
+ 0, PROTO_BIT__TCP | PROTO_BIT__UDP,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ cip_class_ctor,
+ cip_class_dtor,
+ nullptr
+};
+
+const BaseApi* ips_cip_class = &ips_api.base;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// ips_cip_connpathclass.cc author Jian Wu <jiawu2@cisco.com>
+
+/* Description: Rule options for CIP preprocessor */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "framework/cursor.h"
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "framework/range.h"
+#include "hash/hashfcn.h"
+#include "profiler/profiler.h"
+#include "protocols/packet.h"
+
+#include "cip.h"
+
+using namespace snort;
+
+#define s_name "cip_conn_path_class"
+#define s_help \
+ "detection option to match CIP Connection Path Class"
+
+//-------------------------------------------------------------------------
+// CIP Connpathclass rule option
+//-------------------------------------------------------------------------
+
+static THREAD_LOCAL ProfileStats cip_connpathclass_perf_stats;
+
+class CipConnpathclassOption : public IpsOption
+{
+public:
+ CipConnpathclassOption(RangeCheck& v) : IpsOption(s_name)
+ { cip_cpc = v; }
+
+ uint32_t hash() const override;
+ bool operator==(const IpsOption&) const override;
+ EvalStatus eval(Cursor&, Packet*) override;
+
+private:
+ RangeCheck cip_cpc;
+};
+
+uint32_t CipConnpathclassOption::hash() const
+{
+ uint32_t a,b,c;
+
+ a = cip_cpc.op;
+ b = cip_cpc.min;
+ c = cip_cpc.max;
+
+ mix_str(a, b, c, get_name());
+ finalize(a,b,c);
+
+ return c;
+}
+
+bool CipConnpathclassOption::operator==(const IpsOption& ips) const
+{
+ if ( strcmp(get_name(), ips.get_name()) )
+ return false;
+
+ const CipConnpathclassOption& rhs = static_cast<const CipConnpathclassOption&>(ips);
+ return ( cip_cpc == rhs.cip_cpc );
+}
+
+IpsOption::EvalStatus CipConnpathclassOption::eval(Cursor&, Packet* p)
+{
+ Profile profile(cip_connpathclass_perf_stats);
+
+ if ( !p->flow || !p->is_full_pdu() )
+ return NO_MATCH;
+
+ CipFlowData* fd = static_cast<CipFlowData*>(p->flow->get_flow_data(CipFlowData::inspector_id));
+
+ if (!fd)
+ return NO_MATCH;
+
+ CipSessionData* session_data = &fd->session;
+
+ if (session_data->current_data.cip_message_type != CipMessageTypeExplicit
+ || !session_data->current_data.cip_msg.is_cip_request
+ || !session_data->current_data.cip_msg.request.is_forward_open_request)
+ {
+ return NO_MATCH;
+ }
+
+ if ( cip_cpc.eval(session_data->current_data.cip_msg.request.connection_path_class_id) )
+ {
+ return MATCH;
+ }
+
+ return NO_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+#define RANGE "0:65535"
+
+static const Parameter s_params[] =
+{
+ { "~range", Parameter::PT_INTERVAL, RANGE, nullptr,
+ "match CIP Connection Path Class" },
+
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+class CipConnpathclassModule : public Module
+{
+public:
+ CipConnpathclassModule() : Module(s_name, s_help, s_params) { }
+
+ bool begin(const char*, int, SnortConfig*) override;
+ bool set(const char*, Value&, SnortConfig*) override;
+ ProfileStats* get_profile() const override;
+
+ Usage get_usage() const override
+ { return DETECT; }
+
+public:
+ RangeCheck cip_cpc;
+};
+
+bool CipConnpathclassModule::begin(const char*, int, SnortConfig*)
+{
+ cip_cpc.init();
+ return true;
+}
+
+bool CipConnpathclassModule::set(const char*, Value& v, SnortConfig*)
+{
+ if ( !v.is("~range") )
+ return false;
+
+ return cip_cpc.validate(v.get_string(), RANGE);
+}
+
+ProfileStats* CipConnpathclassModule::get_profile() const
+{
+ return &cip_connpathclass_perf_stats;
+}
+
+//-------------------------------------------------------------------------
+// api
+//-------------------------------------------------------------------------
+
+static Module* cip_connpathclass_mod_ctor()
+{
+ return new CipConnpathclassModule;
+}
+
+static void cip_connpathclass_mod_dtor(Module* m)
+{
+ delete m;
+}
+
+static IpsOption* cip_connpathclass_ctor(Module* p, OptTreeNode*)
+{
+ CipConnpathclassModule* m = static_cast<CipConnpathclassModule*>(p);
+ return new CipConnpathclassOption(m->cip_cpc);
+}
+
+static void cip_connpathclass_dtor(IpsOption* p)
+{
+ delete p;
+}
+
+static const IpsApi ips_api =
+{
+ {
+ PT_IPS_OPTION,
+ sizeof(IpsApi),
+ IPSAPI_VERSION,
+ 0,
+ API_RESERVED,
+ API_OPTIONS,
+ s_name,
+ s_help,
+ cip_connpathclass_mod_ctor,
+ cip_connpathclass_mod_dtor
+ },
+ OPT_TYPE_DETECTION,
+ 0, PROTO_BIT__TCP | PROTO_BIT__UDP,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ cip_connpathclass_ctor,
+ cip_connpathclass_dtor,
+ nullptr
+};
+
+const BaseApi* ips_cip_connpathclass = &ips_api.base;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// ips_cip_enipcommand.cc author Jian Wu <jiawu2@cisco.com>
+
+/* Description: Rule options for CIP preprocessor */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "framework/cursor.h"
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "framework/range.h"
+#include "hash/hashfcn.h"
+#include "profiler/profiler.h"
+#include "protocols/packet.h"
+
+#include "cip.h"
+
+using namespace snort;
+
+#define s_name "enip_command"
+#define s_help \
+ "detection option to match CIP Enip Command"
+
+//-------------------------------------------------------------------------
+// CIP EnipCommand rule option
+//-------------------------------------------------------------------------
+
+static THREAD_LOCAL ProfileStats cip_enipcommand_perf_stats;
+
+class CipEnipCommandOption : public IpsOption
+{
+public:
+ CipEnipCommandOption(RangeCheck& v) : IpsOption(s_name)
+ { cip_enip_cmd = v; }
+
+ uint32_t hash() const override;
+ bool operator==(const IpsOption&) const override;
+ EvalStatus eval(Cursor&, Packet*) override;
+
+private:
+ RangeCheck cip_enip_cmd;
+};
+
+uint32_t CipEnipCommandOption::hash() const
+{
+ uint32_t a,b,c;
+
+ a = cip_enip_cmd.op;
+ b = cip_enip_cmd.min;
+ c = cip_enip_cmd.max;
+
+ mix_str(a, b, c, get_name());
+ finalize(a,b,c);
+
+ return c;
+}
+
+bool CipEnipCommandOption::operator==(const IpsOption& ips) const
+{
+ if ( strcmp(get_name(), ips.get_name()) )
+ return false;
+
+ const CipEnipCommandOption& rhs = static_cast<const CipEnipCommandOption&>(ips);
+ return ( cip_enip_cmd == rhs.cip_enip_cmd );
+}
+
+IpsOption::EvalStatus CipEnipCommandOption::eval(Cursor&, Packet* p)
+{
+ Profile profile(cip_enipcommand_perf_stats);
+
+ if ( !p->flow || !p->is_full_pdu() )
+ return NO_MATCH;
+
+ CipFlowData* fd = static_cast<CipFlowData*>(p->flow->get_flow_data(CipFlowData::inspector_id));
+
+ if (!fd)
+ return NO_MATCH;
+
+ CipSessionData* session_data = &fd->session;
+
+ if ( cip_enip_cmd.eval(session_data->current_data.enip_data.enip_header.command) )
+ {
+ return MATCH;
+ }
+
+ return NO_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+#define RANGE "0:65535"
+
+static const Parameter s_params[] =
+{
+ { "~range", Parameter::PT_INTERVAL, RANGE, nullptr,
+ "match CIP Enip Command" },
+
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+class CipEnipCommandModule : public Module
+{
+public:
+ CipEnipCommandModule() : Module(s_name, s_help, s_params) { }
+
+ bool begin(const char*, int, SnortConfig*) override;
+ bool set(const char*, Value&, SnortConfig*) override;
+ ProfileStats* get_profile() const override;
+
+ Usage get_usage() const override
+ { return DETECT; }
+
+public:
+ RangeCheck cip_enip_cmd;
+};
+
+bool CipEnipCommandModule::begin(const char*, int, SnortConfig*)
+{
+ cip_enip_cmd.init();
+ return true;
+}
+
+bool CipEnipCommandModule::set(const char*, Value& v, SnortConfig*)
+{
+ if ( !v.is("~range") )
+ return false;
+
+ return cip_enip_cmd.validate(v.get_string(), RANGE);
+}
+
+ProfileStats* CipEnipCommandModule::get_profile() const
+{
+ return &cip_enipcommand_perf_stats;
+}
+
+//-------------------------------------------------------------------------
+// api
+//-------------------------------------------------------------------------
+
+static Module* cip_enipcommand_mod_ctor()
+{
+ return new CipEnipCommandModule;
+}
+
+static void cip_enipcommand_mod_dtor(Module* m)
+{
+ delete m;
+}
+
+static IpsOption* cip_enipcommand_ctor(Module* p, OptTreeNode*)
+{
+ CipEnipCommandModule* m = static_cast<CipEnipCommandModule*>(p);
+ return new CipEnipCommandOption(m->cip_enip_cmd);
+}
+
+static void cip_enipcommand_dtor(IpsOption* p)
+{
+ delete p;
+}
+
+static const IpsApi ips_api =
+{
+ {
+ PT_IPS_OPTION,
+ sizeof(IpsApi),
+ IPSAPI_VERSION,
+ 0,
+ API_RESERVED,
+ API_OPTIONS,
+ s_name,
+ s_help,
+ cip_enipcommand_mod_ctor,
+ cip_enipcommand_mod_dtor
+ },
+ OPT_TYPE_DETECTION,
+ 0, PROTO_BIT__TCP | PROTO_BIT__UDP,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ cip_enipcommand_ctor,
+ cip_enipcommand_dtor,
+ nullptr
+};
+
+const BaseApi* ips_cip_enipcommand = &ips_api.base;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// ips_cip_enipreq.cc author Jian Wu <jiawu2@cisco.com>
+
+/* Description: Rule options for CIP preprocessor */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "framework/cursor.h"
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "hash/hashfcn.h"
+#include "profiler/profiler.h"
+#include "protocols/packet.h"
+
+#include "cip.h"
+
+using namespace snort;
+
+#define s_name "enip_req"
+#define s_help \
+ "detection option to match ENIP Request"
+
+//-------------------------------------------------------------------------
+// CIP Enipreq rule option
+//-------------------------------------------------------------------------
+
+static THREAD_LOCAL ProfileStats cip_enipreq_perf_stats;
+
+class CipEnipreqOption : public IpsOption
+{
+public:
+ CipEnipreqOption() : IpsOption(s_name) { }
+
+ uint32_t hash() const override;
+ bool operator==(const IpsOption&) const override;
+
+ EvalStatus eval(Cursor&, Packet*) override;
+};
+
+uint32_t CipEnipreqOption::hash() const
+{
+ uint32_t a = 0, b = 0, c = 0;
+
+ mix_str(a, b, c, get_name());
+ finalize(a,b,c);
+
+ return c;
+}
+
+bool CipEnipreqOption::operator==(const IpsOption& ips) const
+{
+ return !strcmp(get_name(), ips.get_name());
+}
+
+IpsOption::EvalStatus CipEnipreqOption::eval(Cursor&, Packet* p)
+{
+ Profile profile(cip_enipreq_perf_stats);
+
+ if ( !p->flow || !p->is_full_pdu() )
+ return NO_MATCH;
+
+ CipFlowData* fd = static_cast<CipFlowData*>(p->flow->get_flow_data(CipFlowData::inspector_id));
+
+ if (!fd)
+ return NO_MATCH;
+
+ CipSessionData* session_data = &fd->session;
+
+ if (session_data->current_data.direction == CIP_FROM_CLIENT)
+ {
+ return MATCH;
+ }
+
+ return NO_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+class CipEnipreqModule : public Module
+{
+public:
+ CipEnipreqModule() : Module(s_name, s_help) { }
+ ProfileStats* get_profile() const override;
+
+ Usage get_usage() const override
+ { return DETECT; }
+};
+
+ProfileStats* CipEnipreqModule::get_profile() const
+{
+ return &cip_enipreq_perf_stats;
+}
+
+//-------------------------------------------------------------------------
+// api
+//-------------------------------------------------------------------------
+
+static Module* cip_enipreq_mod_ctor()
+{
+ return new CipEnipreqModule;
+}
+
+static void cip_enipreq_mod_dtor(Module* m)
+{
+ delete m;
+}
+
+static IpsOption* cip_enipreq_ctor(Module*, OptTreeNode*)
+{
+ return new CipEnipreqOption;
+}
+
+static void cip_enipreq_dtor(IpsOption* p)
+{
+ delete p;
+}
+
+static const IpsApi ips_api =
+{
+ {
+ PT_IPS_OPTION,
+ sizeof(IpsApi),
+ IPSAPI_VERSION,
+ 0,
+ API_RESERVED,
+ API_OPTIONS,
+ s_name,
+ s_help,
+ cip_enipreq_mod_ctor,
+ cip_enipreq_mod_dtor
+ },
+ OPT_TYPE_DETECTION,
+ 0, PROTO_BIT__TCP | PROTO_BIT__UDP,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ cip_enipreq_ctor,
+ cip_enipreq_dtor,
+ nullptr
+};
+
+const BaseApi* ips_cip_enipreq = &ips_api.base;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// ips_cip_eniprsp.cc author Jian Wu <jiawu2@cisco.com>
+
+/* Description: Rule options for CIP preprocessor */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "framework/cursor.h"
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "hash/hashfcn.h"
+#include "profiler/profiler.h"
+#include "protocols/packet.h"
+
+#include "cip.h"
+
+using namespace snort;
+
+#define s_name "enip_rsp"
+#define s_help \
+ "detection option to match ENIP response"
+
+//-------------------------------------------------------------------------
+// CIP EnipRsp rule option
+//-------------------------------------------------------------------------
+
+static THREAD_LOCAL ProfileStats cip_eniprsp_perf_stats;
+
+class CipEnipRspOption : public IpsOption
+{
+public:
+ CipEnipRspOption() : IpsOption(s_name) { }
+
+ uint32_t hash() const override;
+ bool operator==(const IpsOption&) const override;
+
+ EvalStatus eval(Cursor&, Packet*) override;
+};
+
+uint32_t CipEnipRspOption::hash() const
+{
+ uint32_t a = 0, b = 0, c = 0;
+
+ mix_str(a, b, c, get_name());
+ finalize(a,b,c);
+
+ return c;
+}
+
+bool CipEnipRspOption::operator==(const IpsOption& ips) const
+{
+ return !strcmp(get_name(), ips.get_name());
+}
+
+IpsOption::EvalStatus CipEnipRspOption::eval(Cursor&, Packet* p)
+{
+ Profile profile(cip_eniprsp_perf_stats);
+
+ if ( !p->flow || !p->is_full_pdu() )
+ return NO_MATCH;
+
+ CipFlowData* fd = static_cast<CipFlowData*>(p->flow->get_flow_data(CipFlowData::inspector_id));
+
+ if (!fd)
+ return NO_MATCH;
+
+ CipSessionData* session_data = &fd->session;
+
+ if (session_data->current_data.direction == CIP_FROM_SERVER)
+ {
+ return MATCH;
+ }
+
+ return NO_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+class CipEnipRspModule : public Module
+{
+public:
+ CipEnipRspModule() : Module(s_name, s_help) { }
+ ProfileStats* get_profile() const override;
+
+ Usage get_usage() const override
+ { return DETECT; }
+};
+
+ProfileStats* CipEnipRspModule::get_profile() const
+{
+ return &cip_eniprsp_perf_stats;
+}
+
+//-------------------------------------------------------------------------
+// api
+//-------------------------------------------------------------------------
+
+static Module* cip_eniprsp_mod_ctor()
+{
+ return new CipEnipRspModule;
+}
+
+static void cip_eniprsp_mod_dtor(Module* m)
+{
+ delete m;
+}
+
+static IpsOption* cip_eniprsp_ctor(Module*, OptTreeNode*)
+{
+ return new CipEnipRspOption;
+}
+
+static void cip_eniprsp_dtor(IpsOption* p)
+{
+ delete p;
+}
+
+static const IpsApi ips_api =
+{
+ {
+ PT_IPS_OPTION,
+ sizeof(IpsApi),
+ IPSAPI_VERSION,
+ 0,
+ API_RESERVED,
+ API_OPTIONS,
+ s_name,
+ s_help,
+ cip_eniprsp_mod_ctor,
+ cip_eniprsp_mod_dtor
+ },
+ OPT_TYPE_DETECTION,
+ 0, PROTO_BIT__TCP | PROTO_BIT__UDP,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ cip_eniprsp_ctor,
+ cip_eniprsp_dtor,
+ nullptr
+};
+
+const BaseApi* ips_cip_eniprsp = &ips_api.base;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// ips_cip_instance.cc author Jian Wu <jiawu2@cisco.com>
+
+/* Description: Rule options for CIP preprocessor */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "framework/cursor.h"
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "framework/range.h"
+#include "hash/hashfcn.h"
+#include "profiler/profiler.h"
+#include "protocols/packet.h"
+
+#include "cip.h"
+
+using namespace snort;
+
+#define s_name "cip_instance"
+#define s_help \
+ "detection option to match CIP instance"
+
+//-------------------------------------------------------------------------
+// CIP Instance rule option
+//-------------------------------------------------------------------------
+
+static THREAD_LOCAL ProfileStats cip_instance_perf_stats;
+
+class CipInstanceOption : public IpsOption
+{
+public:
+ CipInstanceOption(RangeCheck& v) : IpsOption(s_name)
+ { cip_inst = v; }
+
+ uint32_t hash() const override;
+ bool operator==(const IpsOption&) const override;
+ EvalStatus eval(Cursor&, Packet*) override;
+
+private:
+ RangeCheck cip_inst;
+};
+
+uint32_t CipInstanceOption::hash() const
+{
+ uint32_t a, b, c;
+
+ a = cip_inst.op;
+ b = cip_inst.min;
+ c = cip_inst.max;
+
+ mix_str(a, b, c, get_name());
+ finalize(a,b,c);
+
+ return c;
+}
+
+bool CipInstanceOption::operator==(const IpsOption& ips) const
+{
+ if ( strcmp(get_name(), ips.get_name()) )
+ return false;
+
+ const CipInstanceOption& rhs = static_cast<const CipInstanceOption&>(ips);
+ return ( cip_inst == rhs.cip_inst );
+}
+
+IpsOption::EvalStatus CipInstanceOption::eval(Cursor&, Packet* p)
+{
+ Profile profile(cip_instance_perf_stats);
+
+ if ( !p->flow || !p->is_full_pdu() )
+ return NO_MATCH;
+
+ CipFlowData* fd = static_cast<CipFlowData*>(p->flow->get_flow_data(CipFlowData::inspector_id));
+
+ if (!fd)
+ return NO_MATCH;
+
+ CipSessionData* session_data = &fd->session;
+
+ if (session_data->current_data.cip_message_type != CipMessageTypeExplicit
+ || !session_data->current_data.cip_msg.is_cip_request
+ || !session_data->current_data.cip_msg.request.request_path.has_instance_id)
+ {
+ return NO_MATCH;
+ }
+
+ if ( cip_inst.eval(session_data->current_data.cip_msg.request.request_path.instance_id) )
+ {
+ return MATCH;
+ }
+
+ return NO_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+#define RANGE "0:4294967295"
+
+static const Parameter s_params[] =
+{
+ { "~range", Parameter::PT_INTERVAL, RANGE, nullptr,
+ "match CIP instance" },
+
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+class CipInstanceModule : public Module
+{
+public:
+ CipInstanceModule() : Module(s_name, s_help, s_params) { }
+
+ bool begin(const char*, int, SnortConfig*) override;
+ bool set(const char*, Value&, SnortConfig*) override;
+ ProfileStats* get_profile() const override;
+
+ Usage get_usage() const override
+ { return DETECT; }
+
+public:
+ RangeCheck cip_inst;
+};
+
+bool CipInstanceModule::begin(const char*, int, SnortConfig*)
+{
+ cip_inst.init();
+ return true;
+}
+
+bool CipInstanceModule::set(const char*, Value& v, SnortConfig*)
+{
+ if ( !v.is("~range") )
+ return false;
+
+ return cip_inst.validate(v.get_string(), RANGE);
+}
+
+ProfileStats* CipInstanceModule::get_profile() const
+{
+ return &cip_instance_perf_stats;
+}
+
+//-------------------------------------------------------------------------
+// api
+//-------------------------------------------------------------------------
+
+static Module* cip_instance_mod_ctor()
+{
+ return new CipInstanceModule;
+}
+
+static void cip_instance_mod_dtor(Module* m)
+{
+ delete m;
+}
+
+static IpsOption* cip_instance_ctor(Module* p, OptTreeNode*)
+{
+ CipInstanceModule* m = static_cast<CipInstanceModule*>(p);
+ return new CipInstanceOption(m->cip_inst);
+}
+
+static void cip_instance_dtor(IpsOption* p)
+{
+ delete p;
+}
+
+static const IpsApi ips_api =
+{
+ {
+ PT_IPS_OPTION,
+ sizeof(IpsApi),
+ IPSAPI_VERSION,
+ 0,
+ API_RESERVED,
+ API_OPTIONS,
+ s_name,
+ s_help,
+ cip_instance_mod_ctor,
+ cip_instance_mod_dtor
+ },
+ OPT_TYPE_DETECTION,
+ 0, PROTO_BIT__TCP | PROTO_BIT__UDP,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ cip_instance_ctor,
+ cip_instance_dtor,
+ nullptr
+};
+
+const BaseApi* ips_cip_instance = &ips_api.base;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// ips_cip_req.cc author Jian Wu <jiawu2@cisco.com>
+
+/* Description: Rule options for CIP preprocessor */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "framework/cursor.h"
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "hash/hashfcn.h"
+#include "profiler/profiler.h"
+#include "protocols/packet.h"
+
+#include "cip.h"
+
+using namespace snort;
+
+#define s_name "cip_req"
+#define s_help \
+ "detection option to match CIP request"
+
+//-------------------------------------------------------------------------
+// CIP Req rule option
+//-------------------------------------------------------------------------
+
+static THREAD_LOCAL ProfileStats cip_req_perf_stats;
+
+class CipReqOption : public IpsOption
+{
+public:
+ CipReqOption() : IpsOption(s_name) { }
+
+ uint32_t hash() const override;
+ bool operator==(const IpsOption&) const override;
+
+ EvalStatus eval(Cursor&, Packet*) override;
+};
+
+uint32_t CipReqOption::hash() const
+{
+ uint32_t a = 0, b = 0, c = 0;
+
+ mix_str(a, b, c, get_name());
+ finalize(a,b,c);
+
+ return c;
+}
+
+bool CipReqOption::operator==(const IpsOption& ips) const
+{
+ return !strcmp(get_name(), ips.get_name());
+}
+
+IpsOption::EvalStatus CipReqOption::eval(Cursor&, Packet* p)
+{
+ Profile profile(cip_req_perf_stats);
+
+ if ( !p->flow || !p->is_full_pdu() )
+ return NO_MATCH;
+
+ CipFlowData* fd = static_cast<CipFlowData*>(p->flow->get_flow_data(CipFlowData::inspector_id));
+
+ if (!fd)
+ return NO_MATCH;
+
+ CipSessionData* session_data = &fd->session;
+
+ if (session_data->current_data.cip_message_type == CipMessageTypeExplicit
+ && session_data->current_data.cip_msg.is_cip_request)
+ {
+ return MATCH;
+ }
+
+ return NO_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+class CipReqModule : public Module
+{
+public:
+ CipReqModule() : Module(s_name, s_help) { }
+ ProfileStats* get_profile() const override;
+
+ Usage get_usage() const override
+ { return DETECT; }
+};
+
+ProfileStats* CipReqModule::get_profile() const
+{
+ return &cip_req_perf_stats;
+}
+
+//-------------------------------------------------------------------------
+// api
+//-------------------------------------------------------------------------
+
+static Module* cip_req_mod_ctor()
+{
+ return new CipReqModule;
+}
+
+static void cip_req_mod_dtor(Module* m)
+{
+ delete m;
+}
+
+static IpsOption* cip_req_ctor(Module*, OptTreeNode*)
+{
+ return new CipReqOption;
+}
+
+static void cip_req_dtor(IpsOption* p)
+{
+ delete p;
+}
+
+static const IpsApi ips_api =
+{
+ {
+ PT_IPS_OPTION,
+ sizeof(IpsApi),
+ IPSAPI_VERSION,
+ 0,
+ API_RESERVED,
+ API_OPTIONS,
+ s_name,
+ s_help,
+ cip_req_mod_ctor,
+ cip_req_mod_dtor
+ },
+ OPT_TYPE_DETECTION,
+ 0, PROTO_BIT__TCP | PROTO_BIT__UDP,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ cip_req_ctor,
+ cip_req_dtor,
+ nullptr
+};
+
+const BaseApi* ips_cip_req = &ips_api.base;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// ips_cip_rsp.cc author Jian Wu <jiawu2@cisco.com>
+
+/* Description: Rule options for CIP preprocessor */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "framework/cursor.h"
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "hash/hashfcn.h"
+#include "profiler/profiler.h"
+#include "protocols/packet.h"
+
+#include "cip.h"
+
+using namespace snort;
+
+#define s_name "cip_rsp"
+#define s_help \
+ "detection option to match CIP response"
+
+//-------------------------------------------------------------------------
+// CIP Rsp rule option
+//-------------------------------------------------------------------------
+
+static THREAD_LOCAL ProfileStats cip_rsp_perf_stats;
+
+class CipRspOption : public IpsOption
+{
+public:
+ CipRspOption() : IpsOption(s_name) { }
+
+ uint32_t hash() const override;
+ bool operator==(const IpsOption&) const override;
+
+ EvalStatus eval(Cursor&, Packet*) override;
+};
+
+uint32_t CipRspOption::hash() const
+{
+ uint32_t a = 0, b = 0, c = 0;
+
+ mix_str(a, b, c, get_name());
+ finalize(a,b,c);
+
+ return c;
+}
+
+bool CipRspOption::operator==(const IpsOption& ips) const
+{
+ return !strcmp(get_name(), ips.get_name());
+}
+
+IpsOption::EvalStatus CipRspOption::eval(Cursor&, Packet* p)
+{
+ Profile profile(cip_rsp_perf_stats);
+
+ if ( !p->flow || !p->is_full_pdu() )
+ return NO_MATCH;
+
+ CipFlowData* fd = static_cast<CipFlowData*>(p->flow->get_flow_data(CipFlowData::inspector_id));
+
+ if (!fd)
+ return NO_MATCH;
+
+ CipSessionData* session_data = &fd->session;
+
+ if (session_data->current_data.cip_message_type == CipMessageTypeExplicit
+ && !session_data->current_data.cip_msg.is_cip_request)
+ {
+ return MATCH;
+ }
+
+ return NO_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+class CipRspModule : public Module
+{
+public:
+ CipRspModule() : Module(s_name, s_help) { }
+ ProfileStats* get_profile() const override;
+
+ Usage get_usage() const override
+ { return DETECT; }
+};
+
+ProfileStats* CipRspModule::get_profile() const
+{
+ return &cip_rsp_perf_stats;
+}
+
+//-------------------------------------------------------------------------
+// api
+//-------------------------------------------------------------------------
+
+static Module* cip_rsp_mod_ctor()
+{
+ return new CipRspModule;
+}
+
+static void cip_rsp_mod_dtor(Module* m)
+{
+ delete m;
+}
+
+static IpsOption* cip_rsp_ctor(Module*, OptTreeNode*)
+{
+ return new CipRspOption;
+}
+
+static void cip_rsp_dtor(IpsOption* p)
+{
+ delete p;
+}
+
+static const IpsApi ips_api =
+{
+ {
+ PT_IPS_OPTION,
+ sizeof(IpsApi),
+ IPSAPI_VERSION,
+ 0,
+ API_RESERVED,
+ API_OPTIONS,
+ s_name,
+ s_help,
+ cip_rsp_mod_ctor,
+ cip_rsp_mod_dtor
+ },
+ OPT_TYPE_DETECTION,
+ 0, PROTO_BIT__TCP | PROTO_BIT__UDP,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ cip_rsp_ctor,
+ cip_rsp_dtor,
+ nullptr
+};
+
+const BaseApi* ips_cip_rsp = &ips_api.base;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// ips_cip_service.cc author Jian Wu <jiawu2@cisco.com>
+
+/* Description: Rule options for CIP preprocessor */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "framework/cursor.h"
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "framework/range.h"
+#include "hash/hashfcn.h"
+#include "profiler/profiler.h"
+#include "protocols/packet.h"
+
+#include "cip.h"
+
+using namespace snort;
+
+#define s_name "cip_service"
+#define s_help \
+ "detection option to match CIP service"
+
+//-------------------------------------------------------------------------
+// CIP Service rule option
+//-------------------------------------------------------------------------
+
+static THREAD_LOCAL ProfileStats cip_service_perf_stats;
+
+class CipServiceOption : public IpsOption
+{
+public:
+ CipServiceOption(RangeCheck& v) : IpsOption(s_name)
+ { cip_serv = v; }
+
+ uint32_t hash() const override;
+ bool operator==(const IpsOption&) const override;
+ EvalStatus eval(Cursor&, Packet*) override;
+
+private:
+ RangeCheck cip_serv;
+};
+
+uint32_t CipServiceOption::hash() const
+{
+ uint32_t a, b, c;
+
+ a = cip_serv.op;
+ b = cip_serv.min;
+ c = cip_serv.max;
+
+ mix_str(a, b, c, get_name());
+ finalize(a,b,c);
+
+ return c;
+}
+
+bool CipServiceOption::operator==(const IpsOption& ips) const
+{
+ if ( strcmp(get_name(), ips.get_name()) )
+ return false;
+
+ const CipServiceOption& rhs = static_cast<const CipServiceOption&>(ips);
+ return ( cip_serv == rhs.cip_serv );
+}
+
+IpsOption::EvalStatus CipServiceOption::eval(Cursor&, Packet* p)
+{
+ Profile profile(cip_service_perf_stats);
+
+ if ( !p->flow || !p->is_full_pdu() )
+ return NO_MATCH;
+
+ CipFlowData* fd = static_cast<CipFlowData*>(p->flow->get_flow_data(CipFlowData::inspector_id));
+
+ if (!fd)
+ return NO_MATCH;
+
+ CipSessionData* session_data = &fd->session;
+
+ if (session_data->current_data.cip_message_type != CipMessageTypeExplicit)
+ {
+ return NO_MATCH;
+ }
+
+ if (session_data->current_data.cip_msg.is_cip_request)
+ {
+ if ( cip_serv.eval(session_data->current_data.cip_msg.request.service) )
+ return MATCH;
+ }
+ else
+ {
+ if ( cip_serv.eval(session_data->current_data.cip_msg.response.service) )
+ return MATCH;
+ }
+
+ return NO_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+#define RANGE "0:127"
+
+static const Parameter s_params[] =
+{
+ { "~range", Parameter::PT_INTERVAL, RANGE, nullptr,
+ "match CIP service" },
+
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+class CipServiceModule : public Module
+{
+public:
+ CipServiceModule() : Module(s_name, s_help, s_params) { }
+
+ bool begin(const char*, int, SnortConfig*) override;
+ bool set(const char*, Value&, SnortConfig*) override;
+ ProfileStats* get_profile() const override;
+
+ Usage get_usage() const override
+ { return DETECT; }
+
+public:
+ RangeCheck cip_serv;
+};
+
+bool CipServiceModule::begin(const char*, int, SnortConfig*)
+{
+ cip_serv.init();
+ return true;
+}
+
+bool CipServiceModule::set(const char*, Value& v, SnortConfig*)
+{
+ if ( !v.is("~range") )
+ return false;
+
+ return cip_serv.validate(v.get_string(), RANGE);
+}
+
+ProfileStats* CipServiceModule::get_profile() const
+{
+ return &cip_service_perf_stats;
+}
+
+//-------------------------------------------------------------------------
+// api
+//-------------------------------------------------------------------------
+
+static Module* cip_service_mod_ctor()
+{
+ return new CipServiceModule;
+}
+
+static void cip_service_mod_dtor(Module* m)
+{
+ delete m;
+}
+
+static IpsOption* cip_service_ctor(Module* p, OptTreeNode*)
+{
+ CipServiceModule* m = static_cast<CipServiceModule*>(p);
+ return new CipServiceOption(m->cip_serv);
+}
+
+static void cip_service_dtor(IpsOption* p)
+{
+ delete p;
+}
+
+static const IpsApi ips_api =
+{
+ {
+ PT_IPS_OPTION,
+ sizeof(IpsApi),
+ IPSAPI_VERSION,
+ 0,
+ API_RESERVED,
+ API_OPTIONS,
+ s_name,
+ s_help,
+ cip_service_mod_ctor,
+ cip_service_mod_dtor
+ },
+ OPT_TYPE_DETECTION,
+ 0, PROTO_BIT__TCP | PROTO_BIT__UDP,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ cip_service_ctor,
+ cip_service_dtor,
+ nullptr
+};
+
+const BaseApi* ips_cip_service = &ips_api.base;
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2019-2019 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// ips_cip_status.cc author Jian Wu <jiawu2@cisco.com>
+
+/* Description: Rule options for CIP preprocessor */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "framework/cursor.h"
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "framework/range.h"
+#include "hash/hashfcn.h"
+#include "profiler/profiler.h"
+#include "protocols/packet.h"
+
+#include "cip.h"
+
+using namespace snort;
+
+#define s_name "cip_status"
+#define s_help \
+ "detection option to match CIP response status"
+
+//-------------------------------------------------------------------------
+// CIP Status rule option
+//-------------------------------------------------------------------------
+
+static THREAD_LOCAL ProfileStats cip_status_perf_stats;
+
+class CipStatusOption : public IpsOption
+{
+public:
+ CipStatusOption(RangeCheck& v) : IpsOption(s_name)
+ { cip_status = v; }
+
+ uint32_t hash() const override;
+ bool operator==(const IpsOption&) const override;
+ EvalStatus eval(Cursor&, Packet*) override;
+
+private:
+ RangeCheck cip_status;
+};
+
+uint32_t CipStatusOption::hash() const
+{
+ uint32_t a, b, c;
+
+ a = cip_status.op;
+ b = cip_status.min;
+ c = cip_status.max;
+
+ mix_str(a, b, c, get_name());
+ finalize(a,b,c);
+
+ return c;
+}
+
+bool CipStatusOption::operator==(const IpsOption& ips) const
+{
+ if ( strcmp(get_name(), ips.get_name()) )
+ return false;
+
+ const CipStatusOption& rhs = static_cast<const CipStatusOption&>(ips);
+ return ( cip_status == rhs.cip_status );
+}
+
+IpsOption::EvalStatus CipStatusOption::eval(Cursor&, Packet* p)
+{
+ Profile profile(cip_status_perf_stats);
+
+ if ( !p->flow || !p->is_full_pdu() )
+ return NO_MATCH;
+
+ CipFlowData* fd = (CipFlowData*)p->flow->get_flow_data(CipFlowData::inspector_id);
+
+ if (!fd)
+ return NO_MATCH;
+
+ CipSessionData* session_data = &fd->session;
+
+ if (session_data->current_data.cip_message_type != CipMessageTypeExplicit
+ || session_data->current_data.cip_msg.is_cip_request)
+ {
+ return NO_MATCH;
+ }
+
+ if ( cip_status.eval(session_data->current_data.cip_msg.response.status.general_status) )
+ {
+ return MATCH;
+ }
+
+ return NO_MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+#define RANGE "0:255"
+
+static const Parameter s_params[] =
+{
+ { "~range", Parameter::PT_INTERVAL, RANGE, nullptr,
+ "match CIP response status" },
+
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+class CipStatusModule : public Module
+{
+public:
+ CipStatusModule() : Module(s_name, s_help, s_params) { }
+
+ bool begin(const char*, int, SnortConfig*) override;
+ bool set(const char*, Value&, SnortConfig*) override;
+ ProfileStats* get_profile() const override;
+
+ Usage get_usage() const override
+ { return DETECT; }
+
+public:
+ RangeCheck cip_status;
+};
+
+bool CipStatusModule::begin(const char*, int, SnortConfig*)
+{
+ cip_status.init();
+ return true;
+}
+
+bool CipStatusModule::set(const char*, Value& v, SnortConfig*)
+{
+ if ( !v.is("~range") )
+ return false;
+
+ return cip_status.validate(v.get_string(), RANGE);
+}
+
+ProfileStats* CipStatusModule::get_profile() const
+{
+ return &cip_status_perf_stats;
+}
+
+//-------------------------------------------------------------------------
+// api
+//-------------------------------------------------------------------------
+
+static Module* cip_status_mod_ctor()
+{
+ return new CipStatusModule;
+}
+
+static void cip_status_mod_dtor(Module* m)
+{
+ delete m;
+}
+
+static IpsOption* cip_status_ctor(Module* p, OptTreeNode*)
+{
+ CipStatusModule* m = static_cast<CipStatusModule*>(p);
+ return new CipStatusOption(m->cip_status);
+}
+
+static void cip_status_dtor(IpsOption* p)
+{
+ delete p;
+}
+
+static const IpsApi ips_api =
+{
+ {
+ PT_IPS_OPTION,
+ sizeof(IpsApi),
+ IPSAPI_VERSION,
+ 0,
+ API_RESERVED,
+ API_OPTIONS,
+ s_name,
+ s_help,
+ cip_status_mod_ctor,
+ cip_status_mod_dtor
+ },
+ OPT_TYPE_DETECTION,
+ 0, PROTO_BIT__TCP | PROTO_BIT__UDP,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ cip_status_ctor,
+ cip_status_dtor,
+ nullptr
+};
+
+const BaseApi* ips_cip_status = &ips_api.base;
+
extern const BaseApi* sin_wizard;
// these define multiple plugins
+extern const BaseApi* sin_cip[];
extern const BaseApi* sin_dce[];
extern const BaseApi* sin_dnp3[];
extern const BaseApi* sin_gtp[];
PluginManager::load_plugins(sin_ssl);
#ifdef STATIC_INSPECTORS
+ PluginManager::load_plugins(sin_cip);
PluginManager::load_plugins(sin_dce);
PluginManager::load_plugins(sin_dnp3);
PluginManager::load_plugins(sin_gtp);