diff --git a/etc/schema.json b/etc/schema.json index ed0f01107b..5234f8295a 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -9510,6 +9510,10 @@ "tx_id": { "type": "integer" }, + "unknown_ether_type": { + "type": "integer", + "description": "The ethertype the decoder could not handle, in anomaly records for packets with the decoder.ethernet.unknown_ethertype event" + }, "verdict": { "$ref": "#/$defs/verdict_type" }, diff --git a/src/decode-ethernet.c b/src/decode-ethernet.c index 8ada77382a..31b6ae8c5e 100644 --- a/src/decode-ethernet.c +++ b/src/decode-ethernet.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2021 Open Information Security Foundation +/* Copyright (C) 2007-2026 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 @@ -166,6 +166,136 @@ static int DecodeEthernetTestDceNextTooSmall(void) PASS; } +/** + * \brief decode a frame with an unhandled ethertype and check the + * ethertype DecodeGetUnknownEthertype() reports for it + */ +static int DecodeEthernetUnknownEthertypeCheck( + int datalink, const uint8_t *raw, uint32_t len, uint16_t expected) +{ + Packet *p = PacketGetFromAlloc(); + FAIL_IF_NULL(p); + ThreadVars tv; + DecodeThreadVars dtv; + + memset(&dtv, 0, sizeof(DecodeThreadVars)); + memset(&tv, 0, sizeof(ThreadVars)); + + FAIL_IF(PacketCopyData(p, raw, len) != 0); + p->datalink = datalink; + DecodeLinkLayer(&tv, &dtv, datalink, p, GET_PKT_DATA(p), GET_PKT_LEN(p)); + + FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, ETHERNET_UNKNOWN_ETHERTYPE)); + uint16_t ethertype = 0; + FAIL_IF_NOT(DecodeGetUnknownEthertype(p, ðertype)); + FAIL_IF(ethertype != expected); + + PacketFree(p); + PASS; +} + +/** \test untagged frame: the unknown ethertype is the ethernet type */ +static int DecodeEthernetTestUnknownEthertype(void) +{ + /* MACs, RARP ethertype, RARP payload */ + const uint8_t raw[] = { 0x00, 0x10, 0x94, 0x55, 0x00, 0x01, 0x00, 0x10, 0x94, 0x56, 0x00, 0x01, + 0x80, 0x35, 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x03 }; + return DecodeEthernetUnknownEthertypeCheck(LINKTYPE_ETHERNET, raw, sizeof(raw), 0x8035); +} + +/** \test QinQ frame: the unknown ethertype follows the inner VLAN tag */ +static int DecodeEthernetTestUnknownEthertypeQinQ(void) +{ + /* MACs, 802.1ad, outer tag (VID 100, next 802.1Q), inner tag (VID 200, + * next RARP), RARP payload */ + const uint8_t raw[] = { 0x00, 0x10, 0x94, 0x55, 0x00, 0x01, 0x00, 0x10, 0x94, 0x56, 0x00, 0x01, + 0x88, 0xa8, 0x00, 0x64, 0x81, 0x00, 0x00, 0xc8, 0x80, 0x35, 0x00, 0x01, 0x08, 0x00, 0x06, + 0x04, 0x00, 0x03 }; + return DecodeEthernetUnknownEthertypeCheck(LINKTYPE_ETHERNET, raw, sizeof(raw), 0x8035); +} + +/** \test 802.1ah frame: the unknown ethertype follows the customer MACs */ +static int DecodeEthernetTestUnknownEthertype8021ah(void) +{ + /* MACs, 802.1ah, flags, customer MACs, RARP ethertype, RARP payload */ + const uint8_t raw[] = { 0x00, 0x10, 0x94, 0x55, 0x00, 0x01, 0x00, 0x10, 0x94, 0x56, 0x00, 0x01, + 0x88, 0xe7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x94, 0x57, 0x00, 0x01, 0x00, 0x10, 0x94, + 0x58, 0x00, 0x01, 0x80, 0x35, 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x03 }; + return DecodeEthernetUnknownEthertypeCheck(LINKTYPE_ETHERNET, raw, sizeof(raw), 0x8035); +} + +/** \test SLL frame without an ethernet header: the unknown ethertype + * follows a VLAN tag after the SLL protocol field */ +static int DecodeEthernetTestUnknownEthertypeSll(void) +{ + /* SLL packet type, ARPHRD type, address length, address, protocol 802.1Q, + * tag (VID 100, next RARP), RARP payload */ + const uint8_t raw[] = { 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x10, 0x94, 0x55, 0x00, 0x01, + 0x00, 0x00, 0x81, 0x00, 0x00, 0x64, 0x80, 0x35, 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, + 0x03 }; + return DecodeEthernetUnknownEthertypeCheck(LINKTYPE_LINUX_SLL, raw, sizeof(raw), 0x8035); +} + +/** \test SLL2 frame: the SLL2 protocol is the first field of its header, + * so the VLAN tag starts after the whole header, not after that field */ +static int DecodeEthernetTestUnknownEthertypeSll2(void) +{ + /* protocol 802.1Q, reserved, ifindex, ARPHRD type, packet type, address + * length, address, tag (VID 100, next RARP), RARP payload */ + const uint8_t raw[] = { 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x06, + 0x00, 0x10, 0x94, 0x55, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x80, 0x35, 0x00, 0x01, 0x08, + 0x00, 0x06, 0x04, 0x00, 0x03 }; + return DecodeEthernetUnknownEthertypeCheck(LINKTYPE_LINUX_SLL2, raw, sizeof(raw), 0x8035); +} + +/** \test Cisco HDLC frame: the unknown ethertype is the HDLC protocol */ +static int DecodeEthernetTestUnknownEthertypeCHDLC(void) +{ + /* address, control, RARP protocol, RARP payload */ + const uint8_t raw[] = { 0x0f, 0x00, 0x80, 0x35, 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, + 0x03 }; + return DecodeEthernetUnknownEthertypeCheck(LINKTYPE_CISCO_HDLC, raw, sizeof(raw), 0x8035); +} + +/** \test DCE frame: the unknown ethertype is in the inner ethernet header, + * not the outer DCE ethertype */ +static int DecodeEthernetTestUnknownEthertypeDce(void) +{ + /* MACs, DCE, 2 DCE bytes, inner MACs, RARP ethertype, RARP payload */ + const uint8_t raw[] = { 0x00, 0x10, 0x94, 0x55, 0x00, 0x01, 0x00, 0x10, 0x94, 0x56, 0x00, 0x01, + 0x89, 0x03, 0x00, 0x00, 0x00, 0x10, 0x94, 0x55, 0x00, 0x01, 0x00, 0x10, 0x94, 0x56, 0x00, + 0x01, 0x80, 0x35, 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x03 }; + return DecodeEthernetUnknownEthertypeCheck(LINKTYPE_ETHERNET, raw, sizeof(raw), 0x8035); +} + +/** \test frame cut off inside a VLAN tag: no unknown ethertype event, and + * the lookup stops at the end of the packet instead of reading past it */ +static int DecodeEthernetTestUnknownEthertypeTruncated(void) +{ + /* macs, 802.1Q, first half of the tag */ + const uint8_t raw[] = { 0x00, 0x10, 0x94, 0x55, 0x00, 0x01, 0x00, 0x10, 0x94, 0x56, 0x00, 0x01, + 0x81, 0x00, 0x00, 0x64 }; + Packet *p = PacketGetFromAlloc(); + FAIL_IF_NULL(p); + ThreadVars tv; + DecodeThreadVars dtv; + + memset(&dtv, 0, sizeof(DecodeThreadVars)); + memset(&tv, 0, sizeof(ThreadVars)); + + FAIL_IF(PacketCopyData(p, raw, sizeof(raw)) != 0); + p->datalink = LINKTYPE_ETHERNET; + DecodeEthernet(&tv, &dtv, p, GET_PKT_DATA(p), GET_PKT_LEN(p)); + + FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, VLAN_HEADER_TOO_SMALL)); + FAIL_IF(ENGINE_ISSET_EVENT(p, ETHERNET_UNKNOWN_ETHERTYPE)); + uint16_t ethertype = 0; + FAIL_IF(DecodeGetUnknownEthertype(p, ðertype)); + + PacketFree(p); + PASS; +} + #endif /* UNITTESTS */ @@ -181,6 +311,19 @@ void DecodeEthernetRegisterTests(void) DecodeEthernetTestDceNextTooSmall); UtRegisterTest("DecodeEthernetTestDceTooSmall", DecodeEthernetTestDceTooSmall); + UtRegisterTest("DecodeEthernetTestUnknownEthertype", DecodeEthernetTestUnknownEthertype); + UtRegisterTest( + "DecodeEthernetTestUnknownEthertypeQinQ", DecodeEthernetTestUnknownEthertypeQinQ); + UtRegisterTest( + "DecodeEthernetTestUnknownEthertype8021ah", DecodeEthernetTestUnknownEthertype8021ah); + UtRegisterTest("DecodeEthernetTestUnknownEthertypeSll", DecodeEthernetTestUnknownEthertypeSll); + UtRegisterTest( + "DecodeEthernetTestUnknownEthertypeSll2", DecodeEthernetTestUnknownEthertypeSll2); + UtRegisterTest( + "DecodeEthernetTestUnknownEthertypeCHDLC", DecodeEthernetTestUnknownEthertypeCHDLC); + UtRegisterTest("DecodeEthernetTestUnknownEthertypeDce", DecodeEthernetTestUnknownEthertypeDce); + UtRegisterTest("DecodeEthernetTestUnknownEthertypeTruncated", + DecodeEthernetTestUnknownEthertypeTruncated); #endif /* UNITTESTS */ } /** diff --git a/src/decode-vlan.c b/src/decode-vlan.c index beb3042909..b7ae31c922 100644 --- a/src/decode-vlan.c +++ b/src/decode-vlan.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2022 Open Information Security Foundation +/* Copyright (C) 2007-2026 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 @@ -94,15 +94,6 @@ int DecodeVLAN(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, return TM_ECODE_OK; } -typedef struct IEEE8021ahHdr_ { - uint32_t flags; - uint8_t c_destination[6]; - uint8_t c_source[6]; - uint16_t type; /**< next protocol */ -} __attribute__((__packed__)) IEEE8021ahHdr; - -#define IEEE8021AH_HEADER_LEN sizeof(IEEE8021ahHdr) - int DecodeIEEE8021ah(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, const uint8_t *pkt, uint32_t len) { diff --git a/src/decode-vlan.h b/src/decode-vlan.h index 586e210ae9..a14dc28ddd 100644 --- a/src/decode-vlan.h +++ b/src/decode-vlan.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2022 Open Information Security Foundation +/* Copyright (C) 2007-2026 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 @@ -45,6 +45,15 @@ typedef struct VLANHdr_ { /** VLAN header length */ #define VLAN_HEADER_LEN 4 +typedef struct IEEE8021ahHdr_ { + uint32_t flags; + uint8_t c_destination[6]; + uint8_t c_source[6]; + uint16_t type; /**< next protocol */ +} __attribute__((__packed__)) IEEE8021ahHdr; + +#define IEEE8021AH_HEADER_LEN sizeof(IEEE8021ahHdr) + void DecodeVLANRegisterTests(void); /** VLAN max encapsulation layer count/index */ diff --git a/src/decode.c b/src/decode.c index e6800d8006..300b9745b2 100644 --- a/src/decode.c +++ b/src/decode.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2024 Open Information Security Foundation +/* Copyright (C) 2007-2026 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 @@ -62,6 +62,11 @@ #include "decode-erspan.h" #include "decode-teredo.h" #include "decode-arp.h" +#include "decode-chdlc.h" +#include "decode-etag.h" +#include "decode-sll.h" +#include "decode-sll2.h" +#include "decode-vntag.h" #include "defrag-hash.h" @@ -1192,6 +1197,93 @@ inline void PcapPacketCntSet(Packet *p, uint64_t pcap_cnt) } } +/** \brief get the ethertype that stopped layer 2 decoding + * + * The decoder raises ETHERNET_UNKNOWN_ETHERTYPE and stops when it reads a + * type it has no decoder for. Find that type again in the raw packet. + * Start at the type field of the last ethernet header decoded. Without an + * ethernet header, start at the type field of the VLAN header that a GRE + * VLAN tunnel packet begins with, or at the protocol field of the SLL, SLL2 + * or Cisco HDLC header. Then step over VLAN, 802.1ah, E-Tag and VN-Tag + * headers as the decoder does, until reaching a type that is none of those. + * + * \param p packet carrying the ETHERNET_UNKNOWN_ETHERTYPE event + * \param ethertype set to the ethertype in host byte order + * \retval true if found, false otherwise + */ +bool DecodeGetUnknownEthertype(const Packet *p, uint16_t *ethertype) +{ + const uint8_t *pkt = GET_PKT_DATA(p); + const uint32_t len = GET_PKT_LEN(p); + /* type_off is where the type field is, next_off is where the header it + * announces starts. These are not adjacent in SLL2, where the protocol + * field comes first. */ + uint32_t type_off; + uint32_t next_off; + + if (PacketIsEthernet(p)) { + const uint8_t *ethh = (const uint8_t *)PacketGetEthernet(p); + if (ethh < pkt || ethh >= pkt + len) + return false; + type_off = (uint32_t)(ethh - pkt) + offsetof(EthernetHdr, eth_type); + next_off = (uint32_t)(ethh - pkt) + ETHERNET_HEADER_LEN; + } else if (PacketIsTunnelChild(p)) { + /* the only tunnel packet that reaches DecodeNetworkLayer() without + * an ethernet header is a GRE VLAN one (GRE protocol 0x8100), and + * its data starts with the VLAN header */ + type_off = offsetof(VLANHdr, protocol); + next_off = VLAN_HEADER_LEN; + } else { + switch (p->datalink) { + case LINKTYPE_LINUX_SLL: + type_off = offsetof(SllHdr, sll_protocol); + next_off = SLL_HEADER_LEN; + break; + case LINKTYPE_LINUX_SLL2: + type_off = offsetof(Sll2Hdr, sll_protocol); + next_off = SLL2_HEADER_LEN; + break; + case LINKTYPE_CISCO_HDLC: + type_off = offsetof(CHDLCHdr, protocol); + next_off = CHDLC_HEADER_LEN; + break; + default: + return false; + } + } + + /* every step moves both offsets forward, so the loop ends */ + while (len >= sizeof(uint16_t) && type_off <= len - sizeof(uint16_t)) { + uint16_t proto; + memcpy(&proto, pkt + type_off, sizeof(proto)); + proto = SCNtohs(proto); + switch (proto) { + case ETHERNET_TYPE_VLAN: + case ETHERNET_TYPE_8021AD: + case ETHERNET_TYPE_8021QINQ: + type_off = next_off + offsetof(VLANHdr, protocol); + next_off += VLAN_HEADER_LEN; + break; + case ETHERNET_TYPE_8021AH: + type_off = next_off + offsetof(IEEE8021ahHdr, type); + next_off += IEEE8021AH_HEADER_LEN; + break; + case ETHERNET_TYPE_ETAG: + type_off = next_off + offsetof(ETagHdr, protocol); + next_off += ETAG_HEADER_LEN; + break; + case ETHERNET_TYPE_VNTAG: + type_off = next_off + offsetof(VNTagHdr, protocol); + next_off += VNTAG_HEADER_LEN; + break; + default: + *ethertype = proto; + return true; + } + } + return false; +} + /** * @} */ diff --git a/src/decode.h b/src/decode.h index c7e8e31d98..0b2420d7ff 100644 --- a/src/decode.h +++ b/src/decode.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2024 Open Information Security Foundation +/* Copyright (C) 2007-2026 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 @@ -1496,6 +1496,9 @@ static inline void DecodeLinkLayer(ThreadVars *tv, DecodeThreadVars *dtv, static inline bool DecodeNetworkLayer(ThreadVars *tv, DecodeThreadVars *dtv, const uint16_t proto, Packet *p, const uint8_t *data, const uint32_t len) { + /* DecodeGetUnknownEthertype() steps over the same VLAN, 802.1ah, E-Tag + * and VN-Tag headers to find the type reported for the unknown + * ethertype event: a header type added here needs adding there too */ switch (proto) { case ETHERNET_TYPE_IP: { uint16_t ip_len = (len < USHRT_MAX) ? (uint16_t)len : (uint16_t)USHRT_MAX; @@ -1560,5 +1563,6 @@ static inline bool DecodeNetworkLayer(ThreadVars *tv, DecodeThreadVars *dtv, uint64_t PcapPacketCntGet(const Packet *p); void PcapPacketCntSet(Packet *p, uint64_t pcap_cnt); +bool DecodeGetUnknownEthertype(const Packet *p, uint16_t *ethertype); #endif /* SURICATA_DECODE_H */ diff --git a/src/output-json-anomaly.c b/src/output-json-anomaly.c index 3081baeaab..153d890d60 100644 --- a/src/output-json-anomaly.c +++ b/src/output-json-anomaly.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2019-2021 Open Information Security Foundation +/* Copyright (C) 2019-2026 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 @@ -107,6 +107,12 @@ static int AnomalyDecodeEventJson(ThreadVars *tv, JsonAnomalyLogThread *aft, const bool log_stream = log_type & LOG_JSON_STREAM_TYPE; const bool log_decode = log_type & LOG_JSON_DECODE_TYPE; + /* decoding stopped at an ethertype with no decoder: log that ethertype + * in every record for the packet */ + uint16_t ethertype = 0; + const bool has_ethertype = ENGINE_ISSET_EVENT(p, ETHERNET_UNKNOWN_ETHERTYPE) && + DecodeGetUnknownEthertype(p, ðertype); + for (int i = 0; i < p->events.cnt; i++) { uint8_t event_code = p->events.events[i]; bool is_decode = EVENT_IS_DECODER_PACKET_ERROR(event_code); @@ -139,6 +145,10 @@ static int AnomalyDecodeEventJson(ThreadVars *tv, JsonAnomalyLogThread *aft, /* Close anomaly object. */ SCJbClose(js); + if (has_ethertype) { + SCJbSetUint(js, "unknown_ether_type", ethertype); + } + if (aft->json_output_ctx->flags & LOG_JSON_PACKETHDR) { EvePacket(p, js, GET_PKT_LEN(p) < 32 ? GET_PKT_LEN(p) : 32); }