]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
erspan: Initialize options_len before referencing options.
authorFrode Nordahl <fnordahl@ubuntu.com>
Sat, 13 Dec 2025 10:13:36 +0000 (10:13 +0000)
committerPaolo Abeni <pabeni@redhat.com>
Tue, 23 Dec 2025 08:21:00 +0000 (09:21 +0100)
The struct ip_tunnel_info has a flexible array member named
options that is protected by a counted_by(options_len)
attribute.

The compiler will use this information to enforce runtime bounds
checking deployed by FORTIFY_SOURCE string helpers.

As laid out in the GCC documentation, the counter must be
initialized before the first reference to the flexible array
member.

After scanning through the files that use struct ip_tunnel_info
and also refer to options or options_len, it appears the normal
case is to use the ip_tunnel_info_opts_set() helper.

Said helper would initialize options_len properly before copying
data into options, however in the GRE ERSPAN code a partial
update is done, preventing the use of the helper function.

Before this change the handling of ERSPAN traffic in GRE tunnels
would cause a kernel panic when the kernel is compiled with
GCC 15+ and having FORTIFY_SOURCE configured:

memcpy: detected buffer overflow: 4 byte write of buffer size 0

Call Trace:
 <IRQ>
 __fortify_panic+0xd/0xf
 erspan_rcv.cold+0x68/0x83
 ? ip_route_input_slow+0x816/0x9d0
 gre_rcv+0x1b2/0x1c0
 gre_rcv+0x8e/0x100
 ? raw_v4_input+0x2a0/0x2b0
 ip_protocol_deliver_rcu+0x1ea/0x210
 ip_local_deliver_finish+0x86/0x110
 ip_local_deliver+0x65/0x110
 ? ip_rcv_finish_core+0xd6/0x360
 ip_rcv+0x186/0x1a0

Cc: stable@vger.kernel.org
Link: https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-counted_005fby-variable-attribute
Reported-at: https://launchpad.net/bugs/2129580
Fixes: bb5e62f2d547 ("net: Add options as a flexible array to struct ip_tunnel_info")
Signed-off-by: Frode Nordahl <fnordahl@ubuntu.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Link: https://patch.msgid.link/20251213101338.4693-1-fnordahl@ubuntu.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
net/ipv4/ip_gre.c
net/ipv6/ip6_gre.c

index 761a53c6a89a64fbb0976b1c76e7634f344bcebd..8178c44a3cdd48c5a737e9f4e5696ad8f9fbd4cc 100644 (file)
@@ -330,6 +330,10 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi,
                        if (!tun_dst)
                                return PACKET_REJECT;
 
+                       /* MUST set options_len before referencing options */
+                       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
@@ -344,10 +348,8 @@ static int erspan_rcv(struct sk_buff *skb, struct tnl_ptk_info *tpi,
                        memcpy(md2, pkt_md, ver == 1 ? ERSPAN_V1_MDSIZE :
                                                       ERSPAN_V2_MDSIZE);
 
-                       info = &tun_dst->u.tun_info;
                        __set_bit(IP_TUNNEL_ERSPAN_OPT_BIT,
                                  info->key.tun_flags);
-                       info->options_len = sizeof(*md);
                }
 
                skb_reset_mac_header(skb);
index 8bc3f05f594ed658c143e2cc85798e22001fa2c4..d19d86ed43766bbc8ec052113be02ab231a5272c 100644 (file)
@@ -535,6 +535,10 @@ static int ip6erspan_rcv(struct sk_buff *skb,
                        if (!tun_dst)
                                return PACKET_REJECT;
 
+                       /* MUST set options_len before referencing options */
+                       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
@@ -543,7 +547,6 @@ static int ip6erspan_rcv(struct sk_buff *skb,
                             skb_network_header_len(skb);
                        pkt_md = (struct erspan_metadata *)(gh + gre_hdr_len +
                                                            sizeof(*ershdr));
-                       info = &tun_dst->u.tun_info;
                        md = ip_tunnel_info_opts(info);
                        md->version = ver;
                        md2 = &md->u.md2;
@@ -551,7 +554,6 @@ static int ip6erspan_rcv(struct sk_buff *skb,
                                                       ERSPAN_V2_MDSIZE);
                        __set_bit(IP_TUNNEL_ERSPAN_OPT_BIT,
                                  info->key.tun_flags);
-                       info->options_len = sizeof(*md);
 
                        ip6_tnl_rcv(tunnel, skb, tpi, tun_dst, log_ecn_error);