anomaly/eve: include unknown ethertype in events

Add the ethertype the decoder could not handle to the anomaly records
for a packet with the decoder.ethernet.unknown_ethertype event, as a
top-level "unknown_ether_type" field. That includes the
decoder.vlan.unknown_type, decoder.etag.unknown_type and
decoder.vntag.unknown_type records the tag decoders raise for the same
ethertype. The anomaly object held only the event type and name. The
only ethertype in the record was "ether.ether_type", logged when eve
"ethernet" is enabled and read from the last ethernet header decoded,
so when the unknown ethertype followed a VLAN, 802.1ah, E-Tag or VN-Tag
header the record showed the type that announced that header (0x8100
for VLAN) and never the type that raised the event.

Look the value up when the event is logged, using the new
DecodeGetUnknownEthertype(). The lookup starts at the type field of the
last ethernet header decoded. Without an ethernet header it starts in
the VLAN header that a GRE VLAN tunnel packet begins with, or at the
protocol field of the SLL, SLL2 or Cisco HDLC header. It then steps over
VLAN, 802.1ah, E-Tag and VN-Tag headers as the decoder does, and the
first type that is none of those is the one the decoder rejected. Move
the 802.1ah header struct to decode-vlan.h so the lookup can use its
layout, note in DecodeNetworkLayer() that both lists of tag headers must
match, and add unit tests for untagged, QinQ, 802.1ah, SLL, SLL2, Cisco
HDLC, DCE and truncated frames.

Issue: 7849
Issue: 8142
pull/16421/head
Jeff Lucovsky 2 weeks ago
parent 8455efd9ac
commit 1d0b4f1985

@ -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"
},

@ -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, &ethertype));
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, &ethertype));
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 */
}
/**

@ -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)
{

@ -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 */

@ -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;
}
/**
* @}
*/

@ -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 */

@ -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, &ethertype);
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);
}

Loading…
Cancel
Save