From 712f96f44842d48106df4a359fd3c7095fc9649b Mon Sep 17 00:00:00 2001 From: "Yehor Velykozhon -X (yvelykoz - SOFTSERVE INC at Cisco)" Date: Thu, 13 Jun 2024 11:53:52 +0000 Subject: [PATCH] Pull request #4340: Codec: add new builtin rule Merge in SNORT/snort3 from ~YVELYKOZ/snort3:codec_update to master Squashed commit of the following: commit ce756eadfbc480164894ec2a7873c3640b61df2d Author: Yehor Velykozhon 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 | 4 ++ src/codecs/codec_module.h | 1 + src/codecs/ip/cd_icmp6.cc | 110 ++++++++++++++++++++++++++++---- src/protocols/icmp6.h | 50 ++++++++++++++- 4 files changed, 152 insertions(+), 13 deletions(-) diff --git a/doc/reference/builtin_stubs.txt b/doc/reference/builtin_stubs.txt index c7fa1a4a3..71d11f4ff 100644 --- a/doc/reference/builtin_stubs.txt +++ b/doc/reference/builtin_stubs.txt @@ -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 diff --git a/src/codecs/codec_module.h b/src/codecs/codec_module.h index 5742107a6..da45400df 100644 --- a/src/codecs/codec_module.h +++ b/src/codecs/codec_module.h @@ -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 }; diff --git a/src/codecs/ip/cd_icmp6.cc b/src/codecs/ip/cd_icmp6.cc index 9d3e68ce0..00e726d81 100644 --- a/src/codecs/ip/cd_icmp6.cc +++ b/src/codecs/ip/cd_icmp6.cc @@ -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 + 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 +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: diff --git a/src/protocols/icmp6.h b/src/protocols/icmp6.h index 1f2d7a912..719f4a839 100644 --- a/src/protocols/icmp6.h +++ b/src/protocols/icmp6.h @@ -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 -- 2.47.3