[PATCH net v2 5/5] gre: fix out-of-bounds read of erspan metadata in collect_md mode
From: Eric Dumazet <edumazet@google.com>
Date: 2026-09-16 10:02:03
Subsystem:
networking [general], networking [ipv4/ipv6], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Ahern, Ido Schimmel, Linus Torvalds
erspan_rcv() and ip6erspan_rcv() copy the ERSPAN metadata out of the
packet for collect_md tunnels:
pkt_md = (struct erspan_metadata *)(gh + gre_hdr_len +
sizeof(*ershdr));
...
memcpy(md2, pkt_md, ver == 1 ? ERSPAN_V1_MDSIZE :
ERSPAN_V2_MDSIZE);
Both read 8 bytes at 12 bytes from the start of the GRE header, but only
ask pskb_may_pull() for erspan_hdr_len(ver) bytes beyond it, which type I
support made 0 for version 0. Two ways to get there:
- An ERSPAN type I packet has a 4 byte GRE header and no ERSPAN header
at all, so erspan_rcv() sets ver = 0 and only pulls the GRE header. It
still falls back to a collect_md tunnel through itn->collect_md_tun,
and there the ternary above picks ERSPAN_V2_MDSIZE.
- A packet with an 8 byte GRE header and ershdr->ver == 0 is malformed,
yet neither erspan_rcv() nor ip6erspan_rcv() validates the version
before using it, so erspan_hdr_len(0) pulls nothing either. A version
above 2 is not an out-of-bounds read, but it does store a version that
the transmit side (erspan_fb_xmit(), ip6erspan_tunnel_xmit()) rejects.
Reject a base header whose version is neither 1 nor 2, and skip the
metadata extraction for type I, which has none: ip_tun_rx_dst() hands
out a zeroed option area, so md->version = 0 alone describes it.
Fixes: f989d546a2d5 ("erspan: Add type I version 0 support.")
Cc: stable@vger.kernel.org
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
net/ipv4/ip_gre.c | 34 ++++++++++++++++++++++------------
net/ipv6/ip6_gre.c | 2 ++
2 files changed, 24 insertions(+), 12 deletions(-)
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index 696884f53cdcc65fe87cf04f357f1cc45a7e0736..92f3a52d20d38186c3ce87824a8e15c9e00a925f 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c@@ -274,7 +274,6 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi, struct ip_tunnel_net *itn; struct ip_tunnel *tunnel; const struct iphdr *iph; - struct erspan_md2 *md2; int ver; int len;
@@ -294,6 +293,9 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi, ershdr = (struct erspan_base_hdr *)(skb->data + gre_hdr_len); ver = ershdr->ver; + if (unlikely(ver != 1 && ver != 2)) + return PACKET_REJECT; + iph = ip_hdr(skb); __set_bit(IP_TUNNEL_KEY_BIT, flags); tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex, flags,
@@ -318,6 +320,7 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi, if (tunnel->collect_md) { struct erspan_metadata *pkt_md, *md; struct ip_tunnel_info *info; + struct erspan_md2 *md2; unsigned char *gh; __be64 tun_id;
@@ -334,19 +337,26 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi, info = &tun_dst->u.tun_info; info->options_len = sizeof(*md); - /* skb can be uncloned in __iptunnel_pull_header, so - * old pkt_md is no longer valid and we need to reset - * it - */ - gh = skb_network_header(skb) + - skb_network_header_len(skb); - pkt_md = (struct erspan_metadata *)(gh + gre_hdr_len + - sizeof(*ershdr)); md = ip_tunnel_info_opts(&tun_dst->u.tun_info); md->version = ver; - md2 = &md->u.md2; - memcpy(md2, pkt_md, ver == 1 ? ERSPAN_V1_MDSIZE : - ERSPAN_V2_MDSIZE); + + /* Type I has no ERSPAN header, thus no metadata to + * extract: reading it would go past the @len bytes + * pulled above. ip_tun_rx_dst() zeroed @md for us. + */ + if (!is_erspan_type1(gre_hdr_len)) { + /* skb can be uncloned in __iptunnel_pull_header, so + * old pkt_md is no longer valid and we need to reset + * it + */ + gh = skb_network_header(skb) + + skb_network_header_len(skb); + pkt_md = (struct erspan_metadata *)(gh + gre_hdr_len + + sizeof(*ershdr)); + md2 = &md->u.md2; + memcpy(md2, pkt_md, ver == 1 ? ERSPAN_V1_MDSIZE : + ERSPAN_V2_MDSIZE); + } __set_bit(IP_TUNNEL_ERSPAN_OPT_BIT, info->key.tun_flags);
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c
index 8ebda0b6a78b2236b439f5499d84f34f652fcbe2..a59fb82c74dad7f0c1d1128338e7bed1e3c7116f 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c@@ -503,6 +503,8 @@ static int ip6erspan_rcv(struct sk_buff *skb, ipv6h = ipv6_hdr(skb); ershdr = (struct erspan_base_hdr *)skb->data; ver = ershdr->ver; + if (unlikely(ver != 1 && ver != 2)) + return PACKET_REJECT; tunnel = ip6gre_tunnel_lookup(skb->dev, &ipv6h->saddr, &ipv6h->daddr, tpi->key,
--
2.55.0.1032.g73a4cd73de-goog