[PATCH net-next v4] erspan: drop packets with an unknown ERSPAN version
From: Haishuang Yan
Date: Tue Nov 10 2026 - 02:41:56 EST
The ERSPAN type II and III receive paths take the length of the ERSPAN
header from the version field of the packet, with erspan_hdr_len(), but
never check that the version is one they know: 1 for type II or 2 for
type III.
For any other version, erspan_hdr_len() returns 0 or the type III
length. With version 0, the ERSPAN header is not pulled, so it is
passed up as the start of the inner Ethernet frame, and in collect_md
mode the metadata is filled from the wrong part of the header. The
ETH_HLEN pull in __iptunnel_pull_header() still covers the bytes that
are copied, so nothing is read past the pulled data.
Reject such packets, as a packet with a truncated ERSPAN header already
is.
On IPv4, a packet with a 4-byte GRE header is taken as type I without
looking at the protocol. gre_parse_header() treats an ETH_P_ERSPAN2
packet as carrying an ERSPAN header whatever the GRE header length,
but erspan_rcv() then skips that header's version and passes it up as
the inner frame. Type I only uses ETH_P_ERSPAN, so check the protocol
too. Such a packet now goes through the type III path and its version
check.
IPv6 has no type I, but ip6erspan_rcv() does not reject a type I
packet, an ETH_P_ERSPAN packet with a 4-byte GRE header. Such a packet
has no ERSPAN header, so the version is read from the inner destination
MAC address, and if that happens to be 1 or 2, the start of the inner
frame is stripped as an ERSPAN header. Reject these packets too.
Signed-off-by: Haishuang Yan <yanhaishuang@xxxxxxxxxxxxxxxxxxxx>
---
The issue was found with the help of an AI coding assistant.
Tested on x86_64 with ip_gre and ip6_gre built from net-next
fa5196392691 with this patch, by sending hand-built GRE/ERSPAN packets
over a veth into erspan and ip6erspan devices, in plain and collect_md
mode: version 0 packets, ERSPAN2 packets with a 4-byte GRE header and
version 0 or 3, and on IPv6 type I packets whose inner destination MAC
starts with 0x1 or 0x2 are now dropped. Version 1 and 2 packets and
IPv4 type I packets are unchanged, and erspan v1/v2 ping over IPv4 and
IPv6 still works.
v4:
- Also reject type I packets in ip6erspan_rcv() (Sashiko).
v3: https://lore.kernel.org/netdev/20261108193504.73393-1-yanhaishuang@xxxxxxxxxxxxxxxxxxxx/
- Also check the protocol when classifying a packet as type I, so that
an ETH_P_ERSPAN2 packet with a 4-byte GRE header gets the version
check too (Sashiko).
v2: https://lore.kernel.org/netdev/20261107130030.35220-1-yanhaishuang@xxxxxxxxxxxxxxxxxxxx/
- Drop the Fixes tags and correct the commit message: the ETH_HLEN pull
of __iptunnel_pull_header() covers the metadata copy, so there is no
out of bounds read (Ido, Sashiko).
- Drop the patch that skipped the type III sub-header, as no device is
known to send it (Ido).
v1: https://lore.kernel.org/netdev/20261018090236.2487997-1-yanhaishuang@xxxxxxxxxxxxxxxxxxxx/
net/ipv4/ip_gre.c | 12 +++++++-----
net/ipv6/ip6_gre.c | 9 +++++++++
2 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index 5485586fa0e6..f8a7c4353502 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -255,13 +255,13 @@ static void gre_err(struct sk_buff *skb, u32 info)
ipgre_err(skb, info, &tpi);
}
-static bool is_erspan_type1(int gre_hdr_len)
+static bool is_erspan_type1(const struct tnl_ptk_info *tpi, int gre_hdr_len)
{
/* Both ERSPAN type I (version 0) and type II (version 1) use
* protocol 0x88BE, but the type I has only 4-byte GRE header,
* while type II has 8-byte.
*/
- return gre_hdr_len == 4;
+ return tpi->proto == htons(ETH_P_ERSPAN) && gre_hdr_len == 4;
}
static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi,
@@ -282,7 +282,7 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi,
itn = net_generic(net, erspan_net_id);
iph = ip_hdr(skb);
- if (is_erspan_type1(gre_hdr_len)) {
+ if (is_erspan_type1(tpi, gre_hdr_len)) {
ver = 0;
__set_bit(IP_TUNNEL_NO_KEY_BIT, flags);
tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex, flags,
@@ -301,10 +301,12 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi,
}
if (tunnel) {
- if (is_erspan_type1(gre_hdr_len))
+ if (is_erspan_type1(tpi, gre_hdr_len))
len = gre_hdr_len;
- else
+ else if (ver == ERSPAN_VERSION || ver == ERSPAN_VERSION2)
len = gre_hdr_len + erspan_hdr_len(ver);
+ else
+ return PACKET_REJECT;
if (unlikely(!pskb_may_pull(skb, len)))
return PACKET_REJECT;
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c
index b5d6ed8f0c36..26c0e1c4de18 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -494,6 +494,12 @@ static int ip6erspan_rcv(struct sk_buff *skb,
struct ip6_tnl *tunnel;
u8 ver;
+ /* There is no type I (ETH_P_ERSPAN with a 4-byte GRE header) on
+ * IPv6, and such a packet carries no ERSPAN header to look at.
+ */
+ if (unlikely(tpi->proto == htons(ETH_P_ERSPAN) && gre_hdr_len == 4))
+ return PACKET_REJECT;
+
if (unlikely(!pskb_may_pull(skb, sizeof(*ershdr))))
return PACKET_REJECT;
@@ -507,6 +513,9 @@ static int ip6erspan_rcv(struct sk_buff *skb,
if (tunnel) {
int len = erspan_hdr_len(ver);
+ if (unlikely(ver != ERSPAN_VERSION && ver != ERSPAN_VERSION2))
+ return PACKET_REJECT;
+
if (unlikely(!pskb_may_pull(skb, len)))
return PACKET_REJECT;
base-commit: fa5196392691051963726787944db872420dfa75
--
2.43.0