]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4340: Codec: add new builtin rule
authorYehor Velykozhon -X (yvelykoz - SOFTSERVE INC at Cisco) <yvelykoz@cisco.com>
Thu, 13 Jun 2024 11:53:52 +0000 (11:53 +0000)
committerOleksii Shumeiko -X (oshumeik - SOFTSERVE INC at Cisco) <oshumeik@cisco.com>
Thu, 13 Jun 2024 11:53:52 +0000 (11:53 +0000)
Merge in SNORT/snort3 from ~YVELYKOZ/snort3:codec_update to master

Squashed commit of the following:

commit ce756eadfbc480164894ec2a7873c3640b61df2d
Author: Yehor Velykozhon <yvelykoz@cisco.com>
Date:   Fri May 31 19:02:44 2024 +0300

    codecs: add handling of NDP types

    Added handling for all NDP types of ICMPv6,
    as well as added new builtin rule to cover invalid length.

doc/reference/builtin_stubs.txt
src/codecs/codec_module.h
src/codecs/ip/cd_icmp6.cc
src/protocols/icmp6.h

index c7fa1a4a3221810c40bd4d897f3490d97a873078..71d11f4ff3274b1b4762b5369184a4fd02183d91 100644 (file)
@@ -732,6 +732,10 @@ The IPv6 packet has a reserved source address.
 
 The IPv6 packet has a reserved destination address.
 
+116:478
+
+ICMPv6 option length field is set to 0.
+
 119:1
 
 URI has percent encoding of an unreserved character. The ignore_unreserved option designates
index 5742107a68495d450f2d9bc1f51b34b95ac12ea6..da45400df42c0a6a7f79ccdd568ee636820e9bf5 100644 (file)
@@ -228,6 +228,7 @@ enum CodecSid : uint32_t
     DECODE_MIPV6_BAD_PAYLOAD_PROTO,
     DECODE_IPV6_SRC_RESERVED,
     DECODE_IPV6_DST_RESERVED,
+    DECODE_ICMP6_OPT_ZERO_LENGTH,
     DECODE_INDEX_MAX
 };
 
index 9d3e68ce05cf1d3352fd0061bf99720b90313b42..00e726d817d60ccc6277130b87f0a554b20fd643 100644 (file)
@@ -78,6 +78,7 @@ static const RuleMap icmp6_rules[] =
     { DECODE_ICMPV6_NODE_INFO_BAD_CODE,
       "ICMPv6 node info query/response packet with a code greater than 2" },
     { DECODE_ICMP6_NOT_IP6, "ICMPv6 not encapsulated in IPv6" },
+    { DECODE_ICMP6_OPT_ZERO_LENGTH, "ICMPv6 option length field is set to 0" },
     { 0, nullptr }
 };
 
@@ -110,6 +111,9 @@ public:
 
 private:
     bool valid_checksum_from_daq(const RawData&);
+    template<typename OptionType>
+    inline void validate_ndp_option(CodecData &codec, const OptionType* const icmp_pkt,
+        const uint16_t min_len, const uint16_t dsize);
 };
 } // anonymous namespace
 
@@ -133,6 +137,46 @@ inline bool Icmp6Codec::valid_checksum_from_daq(const RawData& raw)
     return true;
 }
 
+template <typename NDPType>
+void Icmp6Codec::validate_ndp_option(CodecData &codec, const NDPType* const icmp_pkt,
+    const uint16_t min_len, const uint16_t dsize)
+{
+    const uint16_t real_size = dsize + icmp::ICMP6_HEADER_MIN_LEN;
+    bool option_field_not_exists = real_size == min_len;
+    if (option_field_not_exists)
+        return;
+
+    assert(real_size > min_len - icmp::ICMP6_HEADER_MIN_LEN);
+    // Based on RFC2461, for types 133-137, "option" field depends on version
+    // of implementation, so there's no unified format for all types.
+    assert(icmp_pkt->type >= 133 && icmp_pkt->type <= 137);
+
+    const uint8_t* const icmp_pkt_ptr = (const uint8_t* const)icmp_pkt;
+    const uint8_t* curr_option_ptr = (const uint8_t* const)&icmp_pkt->options_start;
+
+    while (real_size > curr_option_ptr - icmp_pkt_ptr )
+    {
+        assert(curr_option_ptr > icmp_pkt_ptr);
+        bool field_incomplete = real_size == curr_option_ptr - icmp_pkt_ptr + 1;
+        if (field_incomplete)
+            return;     // FIXIT-L good candidate for new builtin rule
+
+        const icmp::NDPOptionFormatBasic* curr_option = (const icmp::NDPOptionFormatBasic*)curr_option_ptr;
+
+        // Right now, only option types explicitly specified in RFC4861 are supported
+        if (curr_option->type == 0 || curr_option->type > 5)
+            return;     // FIXIT-L good candidate for non-supported option field
+
+        if (curr_option->length == 0)
+        {
+            codec_event(codec, DECODE_ICMP6_OPT_ZERO_LENGTH);
+            return;
+        }
+
+        curr_option_ptr += curr_option->length * 8;
+    }
+}
+
 bool Icmp6Codec::decode(const RawData& raw, CodecData& codec, DecodeData& snort)
 {
     if (raw.len < icmp::ICMP6_HEADER_MIN_LEN)
@@ -231,8 +275,28 @@ bool Icmp6Codec::decode(const RawData& raw, CodecData& codec, DecodeData& snort)
         }
         break;
 
+    case icmp::Icmp6Types::ROUTER_SOLICITATION:
+        if (dsize >= (ICMPv6_RS_MIN_LEN - icmp::ICMP6_HEADER_MIN_LEN))
+        {
+            const icmp::ICMP6RouterSolicitation* rs = (const icmp::ICMP6RouterSolicitation*)raw.data;
+
+            if (rs->code != 0)
+                codec_event(codec, DECODE_ICMPV6_SOLICITATION_BAD_CODE);
+
+            if (ntohl(rs->reserved) != 0)
+                codec_event(codec, DECODE_ICMPV6_SOLICITATION_BAD_RESERVED);
+
+            validate_ndp_option(codec, rs, ICMPv6_RS_MIN_LEN, dsize);
+        }
+        else
+        {
+            codec_event(codec, DECODE_ICMP_DGRAM_LT_ICMPHDR);
+            return false;
+        }
+        break;
+
     case icmp::Icmp6Types::ROUTER_ADVERTISEMENT:
-        if (dsize >= (sizeof(icmp::ICMP6RouterAdvertisement) - icmp::ICMP6_HEADER_MIN_LEN))
+        if (dsize >= (ICMPv6_RA_MIN_LEN - icmp::ICMP6_HEADER_MIN_LEN))
         {
             const icmp::ICMP6RouterAdvertisement* ra = (const icmp::ICMP6RouterAdvertisement*)raw.data;
 
@@ -241,6 +305,8 @@ bool Icmp6Codec::decode(const RawData& raw, CodecData& codec, DecodeData& snort)
 
             if (ntohl(ra->reachable_time) > 3600000)
                 codec_event(codec, DECODE_ICMPV6_ADVERT_BAD_REACHABLE);
+
+            validate_ndp_option(codec, ra, ICMPv6_RA_MIN_LEN, dsize);
         }
         else
         {
@@ -249,15 +315,40 @@ bool Icmp6Codec::decode(const RawData& raw, CodecData& codec, DecodeData& snort)
         }
         break;
 
-    case icmp::Icmp6Types::ROUTER_SOLICITATION:
-        if (dsize >= (sizeof(icmp::ICMP6RouterSolicitation) - icmp::ICMP6_HEADER_MIN_LEN))
+    case icmp::Icmp6Types::NEIGHBOR_SOLICITATION:
+        if (dsize >= (ICMPv6_NS_MIN_LEN - icmp::ICMP6_HEADER_MIN_LEN))
         {
-            const icmp::ICMP6RouterSolicitation* rs = (const icmp::ICMP6RouterSolicitation*)raw.data;
-            if (rs->code != 0)
-                codec_event(codec, DECODE_ICMPV6_SOLICITATION_BAD_CODE);
+            const icmp::ICMP6NeighborSolicitation* ns = (const icmp::ICMP6NeighborSolicitation*)raw.data;
 
-            if (ntohl(rs->reserved) != 0)
-                codec_event(codec, DECODE_ICMPV6_SOLICITATION_BAD_RESERVED);
+            validate_ndp_option(codec, ns, ICMPv6_NS_MIN_LEN, dsize);
+        }
+        else
+        {
+            codec_event(codec, DECODE_ICMP_DGRAM_LT_ICMPHDR);
+            return false;
+        }
+        break;
+
+    case icmp::Icmp6Types::NEIGHBOR_ADVERTISEMENT:
+        if (dsize >= (ICMPv6_NA_MIN_LEN - icmp::ICMP6_HEADER_MIN_LEN))
+        {
+            const icmp::ICMP6NeighborAdvertisement* na = (const icmp::ICMP6NeighborAdvertisement*)raw.data;
+
+            validate_ndp_option(codec, na, ICMPv6_NA_MIN_LEN, dsize);
+        }
+        else
+        {
+            codec_event(codec, DECODE_ICMP_DGRAM_LT_ICMPHDR);
+            return false;
+        }
+        break;
+
+    case icmp::Icmp6Types::REDIRECT6:
+        if (dsize >= (ICMPv6_RD_MIN_LEN - icmp::ICMP6_HEADER_MIN_LEN))
+        {
+            const icmp::ICMP6Redirect* rd = (const icmp::ICMP6Redirect*)raw.data;
+
+            validate_ndp_option(codec, rd, ICMPv6_RD_MIN_LEN, dsize);
         }
         else
         {
@@ -287,9 +378,6 @@ bool Icmp6Codec::decode(const RawData& raw, CodecData& codec, DecodeData& snort)
     case icmp::Icmp6Types::MULTICAST_LISTENER_QUERY:
     case icmp::Icmp6Types::MULTICAST_LISTENER_REPORT:
     case icmp::Icmp6Types::MULTICAST_LISTENER_DONE:
-    case icmp::Icmp6Types::NEIGHBOR_SOLICITATION:
-    case icmp::Icmp6Types::NEIGHBOR_ADVERTISEMENT:
-    case icmp::Icmp6Types::REDIRECT6:
     case icmp::Icmp6Types::INVERSE_NEIGHBOR_DISCOVERY_SOLICITATION:
     case icmp::Icmp6Types::INVERSE_NEIGHBOR_DISCOVERY_ADVERTISEMENT:
     case icmp::Icmp6Types::VERSION_2_MULTICAST_LISTENER_REPORT:
index 1f2d7a91289f37df56de17ba62e1867f7cbe4496..719f4a839741477429d1cb398d7cba7f27812cdd 100644 (file)
@@ -31,8 +31,9 @@ constexpr uint16_t ICMP6_HEADER_NORMAL_LEN = 8;
 
 #define ICMPv6_NS_MIN_LEN 24
 #define ICMPv6_NA_MIN_LEN 24
-#define ICMPv6_RS_MIN_LEN 24
+#define ICMPv6_RS_MIN_LEN 8
 #define ICMPv6_RA_MIN_LEN 16
+#define ICMPv6_RD_MIN_LEN 40
 
 #define ICMPV6_OPTION_SOURCE_LINKLAYER_ADDRESS 1
 #define ICMPV6_OPTION_TARGET_LINKLAYER_ADDRESS 2
@@ -40,7 +41,6 @@ constexpr uint16_t ICMP6_HEADER_NORMAL_LEN = 8;
 #define ICMPV6_OPTION_REDIRECT_HEADER          4
 #define ICMPV6_OPTION_MTU                      5
 
-//enum class Icmp6Types : std::uint8_t
 enum Icmp6Types : std::uint8_t
 {
     DESTINATION_UNREACHABLE = 1,
@@ -114,6 +114,14 @@ struct ICMP6TooBig
     uint32_t mtu;
 };
 
+struct NDPOptionFormatBasic
+{
+    uint8_t type;
+    uint8_t length;
+    // everything from this point depends on protocol,
+    // so should be implemented in a different structure
+};
+
 struct ICMP6RouterAdvertisement
 {
     uint8_t type;
@@ -124,6 +132,7 @@ struct ICMP6RouterAdvertisement
     uint16_t lifetime;
     uint32_t reachable_time;
     uint32_t retrans_time;
+    NDPOptionFormatBasic* options_start;
 };
 
 struct ICMP6RouterSolicitation
@@ -132,6 +141,7 @@ struct ICMP6RouterSolicitation
     uint8_t code;
     uint16_t csum;
     uint32_t reserved;
+    NDPOptionFormatBasic* options_start;
 };
 
 struct ICMP6NodeInfo
@@ -143,6 +153,42 @@ struct ICMP6NodeInfo
     uint16_t flags;
     uint64_t nonce;
 };
+
+struct ICMP6NeighborSolicitation
+{
+    uint8_t type;
+    uint8_t code;
+    uint16_t csum;
+    uint32_t reserved;
+    uint64_t target_address_1;
+    uint64_t target_address_2;
+    NDPOptionFormatBasic* options_start;
+};
+
+struct ICMP6NeighborAdvertisement
+{
+    uint8_t type;
+    uint8_t code;
+    uint16_t csum;
+    uint32_t flags;     // flags (3 bit) + reserved bytes
+    uint64_t target_address_1;
+    uint64_t target_address_2;
+    NDPOptionFormatBasic* options_start;
+};
+
+struct ICMP6Redirect
+{
+    uint8_t type;
+    uint8_t code;
+    uint16_t csum;
+    uint32_t reserved;
+    uint64_t target_address_1;
+    uint64_t target_address_2;
+    uint64_t dst_address_1;
+    uint64_t dst_address_2;
+    NDPOptionFormatBasic* options_start;
+};
+
 }  // namespace icmp
 }  // namespace snort