From: Emmanuel Thompson Date: Mon, 4 May 2020 15:38:16 +0000 (-0400) Subject: decode/flow/esp: Add ESP decoder & flow X-Git-Tag: suricata-7.0.0-beta1~1856 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f12daa710f30e45ced2fc5bd975cbf779c96183f;p=thirdparty%2Fsuricata.git decode/flow/esp: Add ESP decoder & flow - Adds an ESP (Encapsulating Security Payload) header decoder - Tracks ESP flows via the SPI field --- diff --git a/src/Makefile.am b/src/Makefile.am index 825ce65d0d..137f310108 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -81,6 +81,7 @@ decode-ppp.c decode-ppp.h \ decode-pppoe.c decode-pppoe.h \ decode-raw.c decode-raw.h \ decode-sctp.c decode-sctp.h \ +decode-esp.c decode-esp.h \ decode-sll.c decode-sll.h \ decode-tcp.c decode-tcp.h \ decode-teredo.c decode-teredo.h \ diff --git a/src/decode-esp.c b/src/decode-esp.c new file mode 100644 index 0000000000..575035e793 --- /dev/null +++ b/src/decode-esp.c @@ -0,0 +1,198 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * 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 + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \ingroup decode + * + * @{ + */ + +/** + * \file + * + * Decode Encapsulating Security Payload (ESP) + */ + +#include "suricata-common.h" +#include "decode-esp.h" +#include "flow.h" + +static int DecodeESPPacket(ThreadVars *tv, Packet *p, const uint8_t *pkt, uint16_t len) +{ + if (unlikely(len < ESP_HEADER_LEN)) { + ENGINE_SET_INVALID_EVENT(p, ESP_PKT_TOO_SMALL); + return -1; + } + + p->esph = (ESPHdr *)pkt; + + p->payload = (uint8_t *)pkt + sizeof(ESPHdr); + p->payload_len = len - sizeof(ESPHdr); + + p->proto = IPPROTO_ESP; + + return 0; +} + +/** + * \brief Function to decode IPSEC-ESP packets + * \param tv thread vars + * \param dtv decoder thread vars + * \param p packet + * \param pkt raw packet data + * \param len length in bytes of pkt array + * \retval TM_ECODE_OK or TM_ECODE_FAILED on serious error + */ +int DecodeESP(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, const uint8_t *pkt, uint16_t len) +{ + StatsIncr(tv, dtv->counter_esp); + + if (unlikely(DecodeESPPacket(tv, p, pkt, len) < 0)) { + CLEAR_ESP_PACKET(p); + return TM_ECODE_FAILED; + } + + SCLogDebug("ESP spi: %" PRIu32 " sequence: %" PRIu32, ESP_GET_SPI(p), ESP_GET_SEQUENCE(p)); + + FlowSetupPacket(p); + + return TM_ECODE_OK; +} + +#ifdef UNITTESTS + +#include "util-unittest.h" + +/** \test Successful decoding */ +static int DecodeESPTest01(void) +{ + uint8_t raw_esp[] = { 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x08 }; + + Packet *p = PacketGetFromAlloc(); + FAIL_IF_NULL(p); + + ThreadVars tv; + DecodeThreadVars dtv; + + memset(&tv, 0, sizeof(ThreadVars)); + memset(&dtv, 0, sizeof(DecodeThreadVars)); + + int ret = DecodeESP(&tv, &dtv, p, raw_esp, sizeof(raw_esp)); + FAIL_IF(ret != TM_ECODE_OK); + + FAIL_IF(p->proto != IPPROTO_ESP); + FAIL_IF(p->payload_len != sizeof(raw_esp) - ESP_HEADER_LEN); + FAIL_IF(ESP_GET_SPI(p) != 0x7b); + FAIL_IF(ESP_GET_SEQUENCE(p) != 0x08); + + SCFree(p); + + PASS; +} + +/** \test Successful decoding, with payload data */ +static int DecodeESPTest02(void) +{ + uint8_t raw_esp[] = { 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF }; + + Packet *p = PacketGetFromAlloc(); + FAIL_IF_NULL(p); + + ThreadVars tv; + DecodeThreadVars dtv; + + memset(&tv, 0, sizeof(ThreadVars)); + memset(&dtv, 0, sizeof(DecodeThreadVars)); + + int ret = DecodeESP(&tv, &dtv, p, raw_esp, sizeof(raw_esp)); + FAIL_IF(ret != TM_ECODE_OK); + + FAIL_IF(p->proto != IPPROTO_ESP); + FAIL_IF(p->payload_len != sizeof(raw_esp) - ESP_HEADER_LEN); + FAIL_IF(memcmp(p->payload, raw_esp + ESP_HEADER_LEN, p->payload_len) != 0); + FAIL_IF(ESP_GET_SPI(p) != 0x7b); + FAIL_IF(ESP_GET_SEQUENCE(p) != 0x08); + + SCFree(p); + + PASS; +} + +/** \test Failure decoding, not enough data */ +static int DecodeESPTest03(void) +{ + uint8_t raw_esp[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + Packet *p = PacketGetFromAlloc(); + FAIL_IF_NULL(p); + + ThreadVars tv; + DecodeThreadVars dtv; + + memset(&tv, 0, sizeof(ThreadVars)); + memset(&dtv, 0, sizeof(DecodeThreadVars)); + + int ret = DecodeESP(&tv, &dtv, p, raw_esp, sizeof(raw_esp)); + FAIL_IF(ret != TM_ECODE_FAILED); + + // expect ESP_PKT_TOO_SMALL + FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, ESP_PKT_TOO_SMALL)); + + SCFree(p); + + PASS; +} + +/** \test Failure decoding, no data */ +static int DecodeESPTest04(void) +{ + uint8_t raw_esp[] = {}; + + Packet *p = PacketGetFromAlloc(); + FAIL_IF_NULL(p); + + ThreadVars tv; + DecodeThreadVars dtv; + + memset(&tv, 0, sizeof(ThreadVars)); + memset(&dtv, 0, sizeof(DecodeThreadVars)); + + int ret = DecodeESP(&tv, &dtv, p, raw_esp, sizeof(raw_esp)); + FAIL_IF(ret != TM_ECODE_FAILED); + + // expect ESP_PKT_TOO_SMALL + FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, ESP_PKT_TOO_SMALL)); + + SCFree(p); + + PASS; +} +#endif /* UNITTESTS */ + +void DecodeESPRegisterTests(void) +{ +#ifdef UNITTESTS + UtRegisterTest("DecodeESPTest01", DecodeESPTest01); + UtRegisterTest("DecodeESPTest02", DecodeESPTest02); + UtRegisterTest("DecodeESPTest03", DecodeESPTest03); + UtRegisterTest("DecodeESPTest04", DecodeESPTest04); +#endif /* UNITTESTS */ +} + +/** + * @} + */ diff --git a/src/decode-esp.h b/src/decode-esp.h new file mode 100644 index 0000000000..432de276d7 --- /dev/null +++ b/src/decode-esp.h @@ -0,0 +1,51 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * 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 + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + */ + +#ifndef __DECODE_ESP_H__ +#define __DECODE_ESP_H__ + +/** \brief size of the ESP header */ +#define ESP_HEADER_LEN 8 + +#define ESP_GET_RAW_SPI(esph) SCNtohl((esph)->spi) +#define ESP_GET_RAW_SEQUENCE(esph) SCNtohl((esph)->sequence) + +/** \brief Get the spi field off a packet */ +#define ESP_GET_SPI(p) ESP_GET_RAW_SPI(p->esph) + +/** \brief Get the sequence field off a packet */ +#define ESP_GET_SEQUENCE(p) ESP_GET_RAW_SEQUENCE(p->esph) + +/** \brief ESP Header */ +typedef struct ESPHdr_ { + uint32_t spi; /** < ESP Security Parameters Index */ + uint32_t sequence; /** < ESP sequence number */ +} __attribute__((__packed__)) ESPHdr; + +#define CLEAR_ESP_PACKET(p) \ + { \ + (p)->esph = NULL; \ + } \ + while (0) + +void DecodeESPRegisterTests(void); + +#endif /* __DECODE_ESP_H__ */ diff --git a/src/decode-events.c b/src/decode-events.c index 2f2373cd0b..ee8fa24b75 100644 --- a/src/decode-events.c +++ b/src/decode-events.c @@ -425,6 +425,12 @@ const struct DecodeEvents_ DEvents[] = { SCTP_PKT_TOO_SMALL, }, + /* ESP EVENTS */ + { + "decoder.esp.pkt_too_small", + ESP_PKT_TOO_SMALL, + }, + /* Fragmentation reasembly events. */ { "decoder.ipv4.frag_pkt_too_large", diff --git a/src/decode-events.h b/src/decode-events.h index 2c8467f1cd..c34ae80ac4 100644 --- a/src/decode-events.h +++ b/src/decode-events.h @@ -157,6 +157,9 @@ enum { /* SCTP EVENTS */ SCTP_PKT_TOO_SMALL, /**< sctp packet smaller than minimum size */ + /* ESP EVENTS */ + ESP_PKT_TOO_SMALL, /**< esp packet smaller than minimum size */ + /* Fragmentation reasembly events. */ IPV4_FRAG_PKT_TOO_LARGE, IPV6_FRAG_PKT_TOO_LARGE, diff --git a/src/decode-ipv4.c b/src/decode-ipv4.c index 5d9d12a8b9..60bb9e63d9 100644 --- a/src/decode-ipv4.c +++ b/src/decode-ipv4.c @@ -575,6 +575,11 @@ int DecodeIPV4(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, DecodeSCTP(tv, dtv, p, pkt + IPV4_GET_HLEN(p), IPV4_GET_IPLEN(p) - IPV4_GET_HLEN(p)); break; + + case IPPROTO_ESP: + DecodeESP(tv, dtv, p, pkt + IPV4_GET_HLEN(p), IPV4_GET_IPLEN(p) - IPV4_GET_HLEN(p)); + break; + case IPPROTO_IPV6: { /* spawn off tunnel packet */ diff --git a/src/decode-ipv6.c b/src/decode-ipv6.c index b20440145a..05ef92d1bd 100644 --- a/src/decode-ipv6.c +++ b/src/decode-ipv6.c @@ -151,7 +151,6 @@ DecodeIPV6ExtHdrs(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, char exthdr_fh_done = 0; int hh = 0; int rh = 0; - int eh = 0; int ah = 0; while(1) @@ -462,23 +461,8 @@ DecodeIPV6ExtHdrs(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, case IPPROTO_ESP: { IPV6_SET_L4PROTO(p,nh); - hdrextlen = sizeof(IPV6EspHdr); - if (hdrextlen > plen) { - ENGINE_SET_INVALID_EVENT(p, IPV6_TRUNC_EXTHDR); - SCReturn; - } - - if (eh) { - ENGINE_SET_EVENT(p, IPV6_EXTHDR_DUPL_EH); - SCReturn; - } - - eh = 1; - - nh = IPPROTO_NONE; - pkt += hdrextlen; - plen -= hdrextlen; - break; + DecodeESP(tv, dtv, p, pkt, plen); + SCReturn; } case IPPROTO_AH: { diff --git a/src/decode.c b/src/decode.c index 93aec6338c..384a7d94c2 100644 --- a/src/decode.c +++ b/src/decode.c @@ -500,6 +500,7 @@ void DecodeRegisterPerfCounters(DecodeThreadVars *dtv, ThreadVars *tv) dtv->counter_tcp = StatsRegisterCounter("decoder.tcp", tv); dtv->counter_udp = StatsRegisterCounter("decoder.udp", tv); dtv->counter_sctp = StatsRegisterCounter("decoder.sctp", tv); + dtv->counter_esp = StatsRegisterCounter("decoder.esp", tv); dtv->counter_icmpv4 = StatsRegisterCounter("decoder.icmpv4", tv); dtv->counter_icmpv6 = StatsRegisterCounter("decoder.icmpv6", tv); dtv->counter_ppp = StatsRegisterCounter("decoder.ppp", tv); diff --git a/src/decode.h b/src/decode.h index 0ff20ff1fc..ea2613ecb7 100644 --- a/src/decode.h +++ b/src/decode.h @@ -90,6 +90,7 @@ enum PktSrcEnum { #include "decode-tcp.h" #include "decode-udp.h" #include "decode-sctp.h" +#include "decode-esp.h" #include "decode-raw.h" #include "decode-null.h" #include "decode-vlan.h" @@ -215,7 +216,6 @@ typedef struct Address_ { } while (0) - #define GET_IPV4_SRC_ADDR_U32(p) ((p)->src.addr_data32[0]) #define GET_IPV4_DST_ADDR_U32(p) ((p)->dst.addr_data32[0]) #define GET_IPV4_SRC_ADDR_PTR(p) ((p)->src.addr_data32) @@ -535,6 +535,8 @@ typedef struct Packet_ SCTPHdr *sctph; + ESPHdr *esph; + ICMPV4Hdr *icmpv4h; ICMPV6Hdr *icmpv6h; @@ -656,6 +658,7 @@ typedef struct DecodeThreadVars_ uint16_t counter_raw; uint16_t counter_null; uint16_t counter_sctp; + uint16_t counter_esp; uint16_t counter_ppp; uint16_t counter_geneve; uint16_t counter_gre; @@ -797,6 +800,9 @@ void CaptureStatsSetup(ThreadVars *tv, CaptureStats *s); if ((p)->sctph != NULL) { \ CLEAR_SCTP_PACKET((p)); \ } \ + if ((p)->esph != NULL) { \ + CLEAR_ESP_PACKET((p)); \ + } \ if ((p)->icmpv4h != NULL) { \ CLEAR_ICMPV4_PACKET((p)); \ } \ @@ -956,6 +962,7 @@ int DecodeICMPV6(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, ui int DecodeTCP(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint16_t); int DecodeUDP(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint16_t); int DecodeSCTP(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint16_t); +int DecodeESP(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint16_t); int DecodeGRE(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t); int DecodeVLAN(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t); int DecodeIEEE8021ah(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t); diff --git a/src/flow-hash.c b/src/flow-hash.c index ebbd836e81..c2a22a15ea 100644 --- a/src/flow-hash.c +++ b/src/flow-hash.c @@ -407,6 +407,27 @@ static inline int FlowCompareICMPv4(Flow *f, const Packet *p) return 0; } +/** + * \brief See if a IP-ESP packet belongs to a flow by comparing the SPI + * + * \param f flow + * \param p ESP packet + * + * \retval 1 match + * \retval 0 no match + */ +static inline int FlowCompareESP(Flow *f, const Packet *p) +{ + const uint32_t *f_src = f->src.address.address_un_data32; + const uint32_t *f_dst = f->dst.address.address_un_data32; + const uint32_t *p_src = p->src.address.address_un_data32; + const uint32_t *p_dst = p->dst.address.address_un_data32; + + return CmpAddrs(f_src, p_src) && CmpAddrs(f_dst, p_dst) && f->proto == p->proto && + f->recursion_level == p->recursion_level && CmpVlanIds(f->vlan_id, p->vlan_id) && + f->esp.spi == ESP_GET_SPI(p); +} + void FlowSetupPacket(Packet *p) { p->flags |= PKT_WANTS_FLOW; @@ -417,6 +438,8 @@ static inline int FlowCompare(Flow *f, const Packet *p) { if (p->proto == IPPROTO_ICMP) { return FlowCompareICMPv4(f, p); + } else if (p->proto == IPPROTO_ESP) { + return FlowCompareESP(f, p); } else { return CmpFlowPacket(f, p); } diff --git a/src/flow-util.c b/src/flow-util.c index b16f7c26e4..a47069a38b 100644 --- a/src/flow-util.c +++ b/src/flow-util.c @@ -187,6 +187,8 @@ void FlowInit(Flow *f, const Packet *p) } else if (p->sctph != NULL) { /* XXX MACRO */ SET_SCTP_SRC_PORT(p,&f->sp); SET_SCTP_DST_PORT(p,&f->dp); + } else if (p->esph != NULL) { + f->esp.spi = ESP_GET_SPI(p); } /* XXX handle default */ #ifdef DEBUG else { diff --git a/src/flow.h b/src/flow.h index 198a899b4e..1ad3bdb99f 100644 --- a/src/flow.h +++ b/src/flow.h @@ -354,6 +354,10 @@ typedef struct Flow_ uint8_t type; /**< icmp type */ uint8_t code; /**< icmp code */ } icmp_s; + + struct { + uint32_t spi; /**< esp spi */ + } esp; }; union { Port dp; /**< tcp/udp destination port */ diff --git a/src/output-json-flow.c b/src/output-json-flow.c index 1a0b823334..3921bc3dda 100644 --- a/src/output-json-flow.c +++ b/src/output-json-flow.c @@ -171,6 +171,9 @@ static JsonBuilder *CreateEveHeaderFromFlow(const Flow *f) jb_set_uint(jb, "response_icmp_code", f->icmp_d.code); } break; + case IPPROTO_ESP: + jb_set_uint(jb, "spi", f->esp.spi); + break; } return jb; } diff --git a/src/output-json-netflow.c b/src/output-json-netflow.c index 5700327224..1e986eadce 100644 --- a/src/output-json-netflow.c +++ b/src/output-json-netflow.c @@ -179,6 +179,9 @@ static JsonBuilder *CreateEveHeaderFromNetFlow(const Flow *f, int dir) jb_set_uint(js, "icmp_code", code); break; } + case IPPROTO_ESP: + jb_set_uint(js, "spi", f->esp.spi); + break; } return js; } diff --git a/src/runmode-unittests.c b/src/runmode-unittests.c index 6c6111a3b8..8aae154e30 100644 --- a/src/runmode-unittests.c +++ b/src/runmode-unittests.c @@ -153,6 +153,7 @@ static void RegisterUnittests(void) DecodeTCPRegisterTests(); DecodeUDPV4RegisterTests(); DecodeGRERegisterTests(); + DecodeESPRegisterTests(); DecodeMPLSRegisterTests(); DecodeNSHRegisterTests(); AppLayerProtoDetectUnittestsRegister(); @@ -292,4 +293,3 @@ void RunUnittests(int list_unittests, const char *regex_arg) FatalError(SC_ERR_FATAL, "Unittests are not build-in"); #endif /* UNITTESTS */ } -