[PATCH net-next v3] erspan: drop packets with an unknown ERSPAN version

From: Haishuang Yan

Date: Sun Nov 08 2026 - 06:43:00 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.

Signed-off-by: Haishuang Yan <yanhaishuang@xxxxxxxxxxxxxxxxxxxx>
---
Tested on net-next fa5196392691 (x86_64 KVM guest), with hand-built
GRE/ERSPAN packets sent over a veth into erspan and ip6erspan devices,
in plain and collect_md mode: version 0 packets and ERSPAN2 packets
with a 4-byte GRE header and version 0 or 3 are now dropped, version 1
and 2 packets are unchanged, and erspan v1/v2 ping over IPv4 and IPv6
still works.

v3:
- 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 | 3 +++
2 files changed, 10 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..46fac89a0743 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -507,6 +507,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