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 --- 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@cmss.chinamobile.com/ - 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@cmss.chinamobile.com/ - 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@cmss.chinamobile.com/ 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