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
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
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
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
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;
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;
© 2016 - 2026 Red Hat, Inc.