On Sun, Sep 13, 2026 at 05:08:50PM +0800, Gris Ge wrote:
> The following command triggers a kernel panic:
> 
>   ip link add d0 type dummy; ip link set d0 up
>   ip route add 10.30.0.0/16 \
>     encap ip id 300 geneve_opts 4660:66:11223344 dev d0
> 
>   memcpy: detected buffer overflow: 4 byte write of buffer size 0
>   kernel BUG at lib/string_helpers.c:1044!
>   ...
>   ip_tun_parse_opts.part.0.cold+0x10/0x10
>   ip_tun_build_state+0x116/0x2a0
> 
> On kernels built with GCC 15+ and `CONFIG_FORTIFY_SOURCE`, the fortified
> `memcpy()` got 0 sized destination with request of 4 bytes length:
> 
>   static int ip_tun_parse_opts_geneve(...)
>   {
>       ...
>       attr = tb[LWTUNNEL_IP_OPT_GENEVE_DATA];
>       data_len = nla_len(attr); /* == 4 */
> 
>       struct geneve_opt *opt = ip_tunnel_info_opts(info) + opts_len;
>       memcpy(opt->opt_data, nla_data(attr), data_len);
>       /*     ^^^^^^^^^^^^^ 0 since options_len is assigned afterwards */
> 
> Fixed by initializing the counter before the options are referenced.
> Matching what `tunnel_key_opts_set()` already does.
> 
> Fixes: bb5e62f2d547 ("net: Add options as a flexible array to struct 
> ip_tunnel_info")
> Cc: [email protected]
> Signed-off-by: Gris Ge <[email protected]>
> ---
> v2:
>   - pass opt_len into ip_tun_set_opts() and initialize options_len there,
>     matching tunnel_key_opts_set(), as suggested by Eric Dumazet.
> v1: https://lore.kernel.org/netdev/[email protected]/
> 
>  net/ipv4/ip_tunnel_core.c | 16 +++++++++++-----
>  1 file changed, 11 insertions(+), 5 deletions(-)
> 
> diff --git a/net/ipv4/ip_tunnel_core.c b/net/ipv4/ip_tunnel_core.c
> index 5168d546ea2f..bab42b9e277f 100644
> --- a/net/ipv4/ip_tunnel_core.c
> +++ b/net/ipv4/ip_tunnel_core.c
> @@ -680,8 +680,14 @@ static int ip_tun_get_optlen(struct nlattr *attr,
>  }
> 
>  static int ip_tun_set_opts(struct nlattr *attr, struct ip_tunnel_info *info,
> -                        struct netlink_ext_ack *extack)
> +                        int opts_len, struct netlink_ext_ack *extack)
>  {
> +     /* `options_len` is the __counted_by() annotation of the `options`
> +      * flexible array, it must be initialized before parsing writes
> +      * into it.
> +      */
> +     info->options_len = opts_len;
> +
>       return ip_tun_parse_opts(attr, info, extack);
>  }
> 
> @@ -712,7 +718,8 @@ static int ip_tun_build_state(struct net *net, struct 
> nlattr *attr,
> 
>       tun_info = lwt_tun_info(new_state);
> 
> -     err = ip_tun_set_opts(tb[LWTUNNEL_IP_OPTS], tun_info, extack);
> +     err = ip_tun_set_opts(tb[LWTUNNEL_IP_OPTS], tun_info, opt_len,
> +                           extack);
>       if (err < 0) {
>               lwtstate_free(new_state);
>               return err;
> @@ -753,7 +760,6 @@ static int ip_tun_build_state(struct net *net, struct 
> nlattr *attr,
>       }
> 
>       tun_info->mode = IP_TUNNEL_INFO_TX;
> -     tun_info->options_len = opt_len;
> 
>       *ts = new_state;
> 
> @@ -1006,7 +1012,8 @@ static int ip6_tun_build_state(struct net *net, struct 
> nlattr *attr,
> 
>       tun_info = lwt_tun_info(new_state);
> 
> -     err = ip_tun_set_opts(tb[LWTUNNEL_IP6_OPTS], tun_info, extack);
> +     err = ip_tun_set_opts(tb[LWTUNNEL_IP6_OPTS], tun_info, opt_len,
> +                           extack);
>       if (err < 0) {
>               lwtstate_free(new_state);
>               return err;
> @@ -1040,7 +1047,6 @@ static int ip6_tun_build_state(struct net *net, struct 
> nlattr *attr,
>       }
> 
>       tun_info->mode = IP_TUNNEL_INFO_TX | IP_TUNNEL_INFO_IPV6;
> -     tun_info->options_len = opt_len;
> 
>       *ts = new_state;
> 
> --
> 2.55.0
> 

Looks good to me.

Reviewed-by: Hangbin Liu <[email protected]>

Reply via email to