[PATCH net v3 2/3] bpf: bpf_out_neigh_v6: Fix nd_tbl NULL dereference when IPv6 is disabled

Ricardo B. Marlière posted 3 patches 1 month ago
There is a newer version of this series
[PATCH net v3 2/3] bpf: bpf_out_neigh_v6: Fix nd_tbl NULL dereference when IPv6 is disabled
Posted by Ricardo B. Marlière 1 month ago
When booting with the 'ipv6.disable=1' parameter, the nd_tbl is never
initialized because inet6_init() exits before ndisc_init() is called which
initializes it. If bpf_redirect_neigh() is called with explicit AF_INET6
nexthop parameters, __bpf_redirect_neigh_v6() can skip the IPv6 FIB lookup
and call bpf_out_neigh_v6() directly. bpf_out_neigh_v6() then calls
ip_neigh_gw6(), which uses ipv6_stub->nd_tbl.

 BUG: kernel NULL pointer dereference, address: 0000000000000248
 Oops: Oops: 0000 [#1] SMP NOPTI
 RIP: 0010:skb_do_redirect+0x44f/0xf40
 Call Trace:
  <TASK>
  ? srso_alias_return_thunk+0x5/0xfbef5
  ? __tcf_classify.constprop.0+0x83/0x160
  ? srso_alias_return_thunk+0x5/0xfbef5
  ? tcf_classify+0x2b/0x50
  ? srso_alias_return_thunk+0x5/0xfbef5
  ? tc_run+0xb8/0x120
  ? srso_alias_return_thunk+0x5/0xfbef5
  __dev_queue_xmit+0x6fa/0x1000
  ? srso_alias_return_thunk+0x5/0xfbef5
  packet_sendmsg+0x10da/0x1700
  ? srso_alias_return_thunk+0x5/0xfbef5
  __sys_sendto+0x1f3/0x220
  __x64_sys_sendto+0x24/0x30
  do_syscall_64+0x101/0xf80
  ? exc_page_fault+0x6e/0x170
  ? srso_alias_return_thunk+0x5/0xfbef5
  entry_SYSCALL_64_after_hwframe+0x77/0x7f
  </TASK>

Fix this by adding an early check in bpf_out_neigh_v6(). If
ipv6_stub->nd_tbl is NULL, drop the packet before neighbor lookup.

Suggested-by: Fernando Fernandez Mancera <fmancera@suse.de>
Fixes: ba452c9e996d ("bpf: Fix bpf_redirect_neigh helper api to support supplying nexthop")
Signed-off-by: Ricardo B. Marlière <rbm@suse.com>
---
 net/core/filter.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/net/core/filter.c b/net/core/filter.c
index 4f8ff05c4ec4..3be669347e62 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -2228,6 +2228,9 @@ static int bpf_out_neigh_v6(struct net *net, struct sk_buff *skb,
 			return -ENOMEM;
 	}
 
+	if (unlikely(!ipv6_stub->nd_tbl))
+		goto out_drop;
+
 	rcu_read_lock();
 	if (!nh) {
 		dst = skb_dst(skb);

-- 
2.53.0

Re: [PATCH net v3 2/3] bpf: bpf_out_neigh_v6: Fix nd_tbl NULL dereference when IPv6 is disabled
Posted by Jakub Kicinski 1 month ago
On Thu, 05 Mar 2026 18:29:56 -0300 Ricardo B. Marlière wrote:
> +++ b/net/core/filter.c
> @@ -2228,6 +2228,9 @@ static int bpf_out_neigh_v6(struct net *net, struct sk_buff *skb,
>  			return -ENOMEM;
>  	}
>  
> +	if (unlikely(!ipv6_stub->nd_tbl))
> +		goto out_drop;

So neither Fernando nor you checked whether this code can be built
as a module? ipv6_mod_enabled() should work just fine

SMH
Re: [PATCH net v3 2/3] bpf: bpf_out_neigh_v6: Fix nd_tbl NULL dereference when IPv6 is disabled
Posted by Fernando Fernandez Mancera 1 month ago
On 3/6/26 3:25 AM, Jakub Kicinski wrote:
> On Thu, 05 Mar 2026 18:29:56 -0300 Ricardo B. Marlière wrote:
>> +++ b/net/core/filter.c
>> @@ -2228,6 +2228,9 @@ static int bpf_out_neigh_v6(struct net *net, struct sk_buff *skb,
>>   			return -ENOMEM;
>>   	}
>>   
>> +	if (unlikely(!ipv6_stub->nd_tbl))
>> +		goto out_drop;
> 
> So neither Fernando nor you checked whether this code can be built
> as a module? ipv6_mod_enabled() should work just fine
> 

I checked it. If IPV6=m this is not fine.

ld: vmlinux.o: in function `bpf_out_neigh_v6':
filter.c:(.text+0x3231d60): undefined reference to `ipv6_mod_enabled'

> SMH

Re: [PATCH net v3 2/3] bpf: bpf_out_neigh_v6: Fix nd_tbl NULL dereference when IPv6 is disabled
Posted by Jakub Kicinski 1 month ago
On Fri, 6 Mar 2026 10:58:44 +0100 Fernando Fernandez Mancera wrote:
> >> +	if (unlikely(!ipv6_stub->nd_tbl))
> >> +		goto out_drop;  
> > 
> > So neither Fernando nor you checked whether this code can be built
> > as a module? ipv6_mod_enabled() should work just fine
> 
> I checked it. If IPV6=m this is not fine.
> 
> ld: vmlinux.o: in function `bpf_out_neigh_v6':
> filter.c:(.text+0x3231d60): undefined reference to `ipv6_mod_enabled'

Guess I got it backwards :/ Should we add this to the series?

-->8--------
Subject: ipv6: move the disable_ipv6_mod knob to core code

Make sure disable_ipv6_mod itself is not part of the IPv6 module,
in case core code wants to refer to it. We will remove support
for IPv6=m soon, this change helps make fixes we commit before
that less messy.

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
 include/linux/ipv6.h | 7 ++++++-
 net/ipv4/af_inet.c   | 6 ++++++
 net/ipv6/af_inet6.c  | 8 --------
 3 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h
index 443053a76dcf..e5b6853d517a 100644
--- a/include/linux/ipv6.h
+++ b/include/linux/ipv6.h
@@ -333,7 +333,12 @@ struct tcp6_timewait_sock {
 };
 
 #if IS_ENABLED(CONFIG_IPV6)
-bool ipv6_mod_enabled(void);
+extern int disable_ipv6_mod;
+
+static inline bool ipv6_mod_enabled(void)
+{
+        return disable_ipv6_mod == 0;
+}
 
 static inline struct ipv6_pinfo *inet6_sk(const struct sock *__sk)
 {
diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
index babcd75a08e2..f1944e8fd9d3 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -124,6 +124,12 @@
 
 #include <trace/events/sock.h>
 
+/* Keep the definition of IPv6 disable here for now, to avoid annoying linker
+ * issues in case IPv6=m
+ */
+int disable_ipv6_mod;
+EXPORT_SYMBOL(disable_ipv6_mod);
+
 /* The inetsw table contains everything that inet_create needs to
  * build a new socket.
  */
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index 0b995a961359..03c175cbbdb6 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -86,8 +86,6 @@ struct ipv6_params ipv6_defaults = {
 	.autoconf = 1,
 };
 
-static int disable_ipv6_mod;
-
 module_param_named(disable, disable_ipv6_mod, int, 0444);
 MODULE_PARM_DESC(disable, "Disable IPv6 module such that it is non-functional");
 
@@ -97,12 +95,6 @@ MODULE_PARM_DESC(disable_ipv6, "Disable IPv6 on all interfaces");
 module_param_named(autoconf, ipv6_defaults.autoconf, int, 0444);
 MODULE_PARM_DESC(autoconf, "Enable IPv6 address autoconfiguration on all interfaces");
 
-bool ipv6_mod_enabled(void)
-{
-	return disable_ipv6_mod == 0;
-}
-EXPORT_SYMBOL_GPL(ipv6_mod_enabled);
-
 static struct ipv6_pinfo *inet6_sk_generic(struct sock *sk)
 {
 	const int offset = sk->sk_prot->ipv6_pinfo_offset;
-- 
2.53.0
Re: [PATCH net v3 2/3] bpf: bpf_out_neigh_v6: Fix nd_tbl NULL dereference when IPv6 is disabled
Posted by Fernando Fernandez Mancera 1 month ago
On 3/7/26 3:39 AM, Jakub Kicinski wrote:
> On Fri, 6 Mar 2026 10:58:44 +0100 Fernando Fernandez Mancera wrote:
>>>> +	if (unlikely(!ipv6_stub->nd_tbl))
>>>> +		goto out_drop;
>>>
>>> So neither Fernando nor you checked whether this code can be built
>>> as a module? ipv6_mod_enabled() should work just fine
>>
>> I checked it. If IPV6=m this is not fine.
>>
>> ld: vmlinux.o: in function `bpf_out_neigh_v6':
>> filter.c:(.text+0x3231d60): undefined reference to `ipv6_mod_enabled'
> 
> Guess I got it backwards :/ Should we add this to the series?
> 

No worries.

> -->8--------
> Subject: ipv6: move the disable_ipv6_mod knob to core code
> 
> Make sure disable_ipv6_mod itself is not part of the IPv6 module,
> in case core code wants to refer to it. We will remove support
> for IPv6=m soon, this change helps make fixes we commit before
> that less messy.
> 
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---

Ricardo could you add it and check if it is fine? FWIW; I am sending 
today the series removing IPV6=m after some testing to net-next tree.

Thanks,
Fernando.

>   include/linux/ipv6.h | 7 ++++++-
>   net/ipv4/af_inet.c   | 6 ++++++
>   net/ipv6/af_inet6.c  | 8 --------
>   3 files changed, 12 insertions(+), 9 deletions(-)
> 
> diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h
> index 443053a76dcf..e5b6853d517a 100644
> --- a/include/linux/ipv6.h
> +++ b/include/linux/ipv6.h
> @@ -333,7 +333,12 @@ struct tcp6_timewait_sock {
>   };
>   
>   #if IS_ENABLED(CONFIG_IPV6)
> -bool ipv6_mod_enabled(void);
> +extern int disable_ipv6_mod;
> +
> +static inline bool ipv6_mod_enabled(void)
> +{
> +        return disable_ipv6_mod == 0;
> +}
>   
>   static inline struct ipv6_pinfo *inet6_sk(const struct sock *__sk)
>   {
> diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
> index babcd75a08e2..f1944e8fd9d3 100644
> --- a/net/ipv4/af_inet.c
> +++ b/net/ipv4/af_inet.c
> @@ -124,6 +124,12 @@
>   
>   #include <trace/events/sock.h>
>   
> +/* Keep the definition of IPv6 disable here for now, to avoid annoying linker
> + * issues in case IPv6=m
> + */
> +int disable_ipv6_mod;
> +EXPORT_SYMBOL(disable_ipv6_mod);
> +
>   /* The inetsw table contains everything that inet_create needs to
>    * build a new socket.
>    */
> diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
> index 0b995a961359..03c175cbbdb6 100644
> --- a/net/ipv6/af_inet6.c
> +++ b/net/ipv6/af_inet6.c
> @@ -86,8 +86,6 @@ struct ipv6_params ipv6_defaults = {
>   	.autoconf = 1,
>   };
>   
> -static int disable_ipv6_mod;
> -
>   module_param_named(disable, disable_ipv6_mod, int, 0444);
>   MODULE_PARM_DESC(disable, "Disable IPv6 module such that it is non-functional");
>   
> @@ -97,12 +95,6 @@ MODULE_PARM_DESC(disable_ipv6, "Disable IPv6 on all interfaces");
>   module_param_named(autoconf, ipv6_defaults.autoconf, int, 0444);
>   MODULE_PARM_DESC(autoconf, "Enable IPv6 address autoconfiguration on all interfaces");
>   
> -bool ipv6_mod_enabled(void)
> -{
> -	return disable_ipv6_mod == 0;
> -}
> -EXPORT_SYMBOL_GPL(ipv6_mod_enabled);
> -
>   static struct ipv6_pinfo *inet6_sk_generic(struct sock *sk)
>   {
>   	const int offset = sk->sk_prot->ipv6_pinfo_offset;
Re: [PATCH net v3 2/3] bpf: bpf_out_neigh_v6: Fix nd_tbl NULL dereference when IPv6 is disabled
Posted by Ricardo B. Marlière 1 month ago
On Sat Mar 7, 2026 at 4:27 AM -03, Fernando Fernandez Mancera wrote:
> On 3/7/26 3:39 AM, Jakub Kicinski wrote:
>> On Fri, 6 Mar 2026 10:58:44 +0100 Fernando Fernandez Mancera wrote:
>>>>> +	if (unlikely(!ipv6_stub->nd_tbl))
>>>>> +		goto out_drop;
>>>>
>>>> So neither Fernando nor you checked whether this code can be built
>>>> as a module? ipv6_mod_enabled() should work just fine
>>>
>>> I checked it. If IPV6=m this is not fine.
>>>
>>> ld: vmlinux.o: in function `bpf_out_neigh_v6':
>>> filter.c:(.text+0x3231d60): undefined reference to `ipv6_mod_enabled'
>> 
>> Guess I got it backwards :/ Should we add this to the series?
>> 

Nice, thank you!

>
> No worries.
>
>> -->8--------
>> Subject: ipv6: move the disable_ipv6_mod knob to core code
>> 
>> Make sure disable_ipv6_mod itself is not part of the IPv6 module,
>> in case core code wants to refer to it. We will remove support
>> for IPv6=m soon, this change helps make fixes we commit before
>> that less messy.
>> 
>> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
>> ---
>
> Ricardo could you add it and check if it is fine? FWIW; I am sending 
> today the series removing IPV6=m after some testing to net-next tree.

Will do!

>
> Thanks,
> Fernando.
>
>>   include/linux/ipv6.h | 7 ++++++-
>>   net/ipv4/af_inet.c   | 6 ++++++
>>   net/ipv6/af_inet6.c  | 8 --------
>>   3 files changed, 12 insertions(+), 9 deletions(-)
>> 
>> diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h
>> index 443053a76dcf..e5b6853d517a 100644
>> --- a/include/linux/ipv6.h
>> +++ b/include/linux/ipv6.h
>> @@ -333,7 +333,12 @@ struct tcp6_timewait_sock {
>>   };
>>   
>>   #if IS_ENABLED(CONFIG_IPV6)
>> -bool ipv6_mod_enabled(void);
>> +extern int disable_ipv6_mod;
>> +
>> +static inline bool ipv6_mod_enabled(void)
>> +{
>> +        return disable_ipv6_mod == 0;
>> +}
>>   
>>   static inline struct ipv6_pinfo *inet6_sk(const struct sock *__sk)
>>   {
>> diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
>> index babcd75a08e2..f1944e8fd9d3 100644
>> --- a/net/ipv4/af_inet.c
>> +++ b/net/ipv4/af_inet.c
>> @@ -124,6 +124,12 @@
>>   
>>   #include <trace/events/sock.h>
>>   
>> +/* Keep the definition of IPv6 disable here for now, to avoid annoying linker
>> + * issues in case IPv6=m
>> + */
>> +int disable_ipv6_mod;
>> +EXPORT_SYMBOL(disable_ipv6_mod);
>> +
>>   /* The inetsw table contains everything that inet_create needs to
>>    * build a new socket.
>>    */
>> diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
>> index 0b995a961359..03c175cbbdb6 100644
>> --- a/net/ipv6/af_inet6.c
>> +++ b/net/ipv6/af_inet6.c
>> @@ -86,8 +86,6 @@ struct ipv6_params ipv6_defaults = {
>>   	.autoconf = 1,
>>   };
>>   
>> -static int disable_ipv6_mod;
>> -
>>   module_param_named(disable, disable_ipv6_mod, int, 0444);
>>   MODULE_PARM_DESC(disable, "Disable IPv6 module such that it is non-functional");
>>   
>> @@ -97,12 +95,6 @@ MODULE_PARM_DESC(disable_ipv6, "Disable IPv6 on all interfaces");
>>   module_param_named(autoconf, ipv6_defaults.autoconf, int, 0444);
>>   MODULE_PARM_DESC(autoconf, "Enable IPv6 address autoconfiguration on all interfaces");
>>   
>> -bool ipv6_mod_enabled(void)
>> -{
>> -	return disable_ipv6_mod == 0;
>> -}
>> -EXPORT_SYMBOL_GPL(ipv6_mod_enabled);
>> -
>>   static struct ipv6_pinfo *inet6_sk_generic(struct sock *sk)
>>   {
>>   	const int offset = sk->sk_prot->ipv6_pinfo_offset;