pull/16421/merge
Jeff Lucovsky 14 hours ago committed by GitHub
commit 73e39c9670
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -409,6 +409,12 @@ In firewall mode, the ``alert.engine`` field identifies which rule engine
generated the alert: ``fw`` for firewall rules and ``td`` for threat detect
rules. This field is omitted outside of firewall mode.
Alerts for a packet with the ``decoder.ethernet.unknown_ethertype`` event, such
as alerts from a rule using ``decode-event:ethernet.unknown_ethertype``, have a
top-level ``unknown_ether_type`` field holding the ethertype the decoder could
not handle. Anomaly records for the packet have the same field, but alerts
have it whether or not decode anomalies are logged.
This event will also have the ``pcap_cnt`` field, when running in pcap mode, to
indicate which packet triggered the signature.
@ -527,6 +533,16 @@ Fields
* "code" If "type" is "unknown", than "code" contains the unrecognized event
code. Otherwise, this field is not present.
Every anomaly record for a packet with the
``decoder.ethernet.unknown_ethertype`` event, including the
``decoder.vlan.unknown_type``, ``decoder.etag.unknown_type`` or
``decoder.vntag.unknown_type`` record raised with it, has a top-level field
outside the "anomaly" object:
* "unknown_ether_type" The ethertype the decoder could not handle. When that
ethertype follows a VLAN, 802.1ah, E-Tag or VN-Tag header, the field differs
from "ether.ether_type", which holds the ethernet header's type.
The following field is included when "type" has the value "applayer":
* "layer" Indicates the handling layer that detected the event. This will be
@ -562,6 +578,21 @@ Examples
"event": "stream.pkt_invalid_timestamp"
}
{
"event_type": "anomaly",
"vlan": [
100
],
"ether": {
"ether_type": 33024
},
"anomaly": {
"type": "decode",
"event": "decoder.ethernet.unknown_ethertype"
},
"unknown_ether_type": 32821
}
{
"timestamp": "1969-12-31T16:04:21.000000-0800",
"pcap_cnt": 9262,

@ -176,6 +176,16 @@ Anomalies are reported by and configured by type:
- Stream
- Application layer
Decode anomaly records for a packet with the
``decoder.ethernet.unknown_ethertype`` event include the ethertype the decoder
could not handle in a top-level ``unknown_ether_type`` field. Decoding stops at
that ethertype, so a packet has at most one. When that ethertype follows a
VLAN, 802.1ah, E-Tag or VN-Tag header, ``ether.ether_type`` (logged with
``ethernet: yes``) holds the ethernet header's type, such as 0x8100 for VLAN,
not the unknown one. After a VLAN, E-Tag or VN-Tag header the decoder also
raises ``decoder.vlan.unknown_type``, ``decoder.etag.unknown_type`` or
``decoder.vntag.unknown_type``, and that record carries the same field.
Metadata::
- anomaly:

@ -110,6 +110,11 @@ Logging Changes
E.g., previously, ``ether_type`` values were logged in host order; an ethertype value of ``0xfbb7``
(network order) was logged as `47099`` (``0xb7fb``). This ethertype value will be logged as ``64439``.
- Anomaly records for a packet with the ``decoder.ethernet.unknown_ethertype``
event now include the ethertype the decoder could not handle in a top-level
``unknown_ether_type`` field.
Alerts for a packet with that event have the same field.
- Alert verdict key is changed from to ``reject-target`` to ``reject_target``
- App-layer stats protocols names replace dash by underscore, meaning

@ -9626,6 +9626,10 @@
"tx_id": {
"type": "integer"
},
"unknown_ether_type": {
"type": "integer",
"description": "The ethertype the decoder could not handle, in alert and 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
@ -165,6 +165,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 */
@ -180,6 +310,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
@ -93,15 +93,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"
@ -1205,6 +1210,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;
@ -1559,5 +1562,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 */

@ -887,6 +887,11 @@ static int AlertJsonDecoderEvent(ThreadVars *tv, JsonAlertLogThread *aft, const
if (p->alerts.cnt == 0)
return TM_ECODE_OK;
/* decoding stopped at an ethertype with no decoder: log that ethertype */
uint16_t ethertype = 0;
const bool has_ethertype = ENGINE_ISSET_EVENT(p, ETHERNET_UNKNOWN_ETHERTYPE) &&
DecodeGetUnknownEthertype(p, &ethertype);
const uint8_t final_action = p->alerts.alerts[p->alerts.cnt - 1].action;
for (int i = 0; i < p->alerts.cnt; i++) {
const PacketAlert *pa = &p->alerts.alerts[i];
@ -905,6 +910,10 @@ static int AlertJsonDecoderEvent(ThreadVars *tv, JsonAlertLogThread *aft, const
AlertJsonTunnel(p, jb, &json_output_ctx->eve_ctx->cfg);
}
if (has_ethertype) {
SCJbSetUint(jb, "unknown_ether_type", ethertype);
}
/* base64-encoded full packet */
if (json_output_ctx->flags & LOG_JSON_PACKET) {
EvePacket(p, jb, 0);

@ -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
@ -106,6 +106,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);
@ -138,6 +144,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