public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH net] netfilter: nf_nat: avoid invalid nat_net pointer use on failed nf_nat_init()
@ 2026-04-28  9:09 Mathias Krause
  2026-04-28 10:01 ` Pablo Neira Ayuso
  0 siblings, 1 reply; 3+ messages in thread
From: Mathias Krause @ 2026-04-28  9:09 UTC (permalink / raw)
  To: netfilter-devel
  Cc: Pablo Neira Ayuso, Florian Westphal, netdev, Mathias Krause

We ran into below KASAN splat, which is mostly uninteresting, beside
for having nf_nat_register_fn() in the call chain as a cause for the
offending access:

==================================================================
BUG: KASAN: slab-out-of-bounds in nf_nat_register_fn+0x5f9/0x640
Read of size 8 at addr ffff890031e54c20 by task iptables/9510

CPU: 0 UID: 0 PID: 9510 Comm: iptables Not tainted 6.18.18-grsec-full-20260320181326 #1 PREEMPT(voluntary)
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
 <TASK>
 […] dump_stack_lvl+0xee/0x160 ffff88004117eeb8
 […] print_report+0x6e/0x640 ffff88004117eee0
 […] ? __phys_addr+0x8e/0x140 ffff88004117eef0
 […] ? kasan_addr_to_slab+0x51/0xe0 ffff88004117ef08
 […] ? complete_report_info+0xec/0x1c0 ffff88004117ef20
 […] ? nf_nat_register_fn+0x5f9/0x640 ffff88004117ef48
 […] kasan_report+0xbc/0x140 ffff88004117ef50
 […] ? nf_nat_register_fn+0x5f9/0x640 ffff88004117ef90
 […] nf_nat_register_fn+0x5f9/0x640 ffff88004117eff8
 […] ? nf_nat_icmp_reply_translation+0x6e0/0x6e0 ffff88004117f070
 […] nf_tables_register_hook.part.0+0xa0/0x220 ffff88004117f080
 […] nf_tables_addchain.constprop.0+0x1054/0x1fc0 ffff88004117f0b8
 […] ? nft_chain_lookup.part.0+0x4ce/0xac0 ffff88004117f130
 […] ? nf_tables_abort+0x3d80/0x3d80 ffff88004117f190
 […] ? nf_tables_dumpreset_obj+0x100/0x100 ffff88004117f1c8
 […] ? nft_table_lookup.part.0+0x255/0x300 ffff88004117f310
 […] ? nf_tables_newchain+0x21a4/0x2fa0 ffff88004117f358
 […] nf_tables_newchain+0x21a4/0x2fa0 ffff88004117f360
 […] ? nf_tables_addchain.constprop.0+0x1fc0/0x1fc0 ffff88004117f458
 […] ? nla_get_range_signed+0x4a0/0x4a0 ffff88004117f488
 […] ? lock_acquire+0x16f/0x320 ffff88004117f490
 […] ? find_held_lock+0x3b/0xe0 ffff88004117f4b0
 […] ? __nla_parse+0x45/0x80 ffff88004117f500
 […] nfnetlink_rcv_batch+0xbca/0x19a0 ffff88004117f550
 […] ? nfnetlink_net_exit_batch+0x120/0x120 ffff88004117f618
 […] ? __sanitizer_cov_trace_switch+0x63/0xe0 ffff88004117f720
 […] ? gr_acl_handle_mmap+0x1c4/0x320 ffff88004117f7c0
 […] ? nla_get_range_signed+0x4a0/0x4a0 ffff88004117f7e8
 […] ? gr_is_capable+0x6f/0xe0 ffff88004117f830
 […] ? __nla_parse+0x45/0x80 ffff88004117f860
 […] ? skb_pull+0x103/0x1a0 ffff88004117f880
 […] nfnetlink_rcv+0x3db/0x4a0 ffff88004117f8b0
 […] ? nfnetlink_rcv_batch+0x19a0/0x19a0 ffff88004117f8d8
 […] ? netlink_lookup+0xe2/0x240 ffff88004117f900
 […] netlink_unicast+0x74b/0xb00 ffff88004117f930
 […] ? netlink_attachskb+0xb20/0xb20 ffff88004117f980
 […] ? __check_object_size+0x3e/0xaa0 ffff88004117f998
 […] ? security_netlink_send+0x51/0x160 ffff88004117f9c8
 […] netlink_sendmsg+0xa03/0x1200 ffff88004117f9f8
 […] ? netlink_unicast+0xb00/0xb00 ffff88004117fa70
 […] ? netlink_unicast+0xb00/0xb00 ffff88004117fac8
 […] ? ____sys_sendmsg+0xe2a/0x1040 ffff88004117faf8
 […] ____sys_sendmsg+0xe2a/0x1040 ffff88004117fb00
 […] ? kernel_recvmsg+0x300/0x300 ffff88004117fb60
 […] ? reacquire_held_locks+0xe9/0x260 ffff88004117fbc8
 […] ___sys_sendmsg+0x138/0x200 ffff88004117fbf8
 […] ? do_recvmmsg+0x7e0/0x7e0 ffff88004117fc30
 […] ? lockdep_hardirqs_on_prepare+0x101/0x1e0 ffff88004117fc50
 […] ? lock_acquire+0x16f/0x320 ffff88004117fd20
 […] ? lock_acquire+0x16f/0x320 ffff88004117fd58
 […] ? find_held_lock+0x3b/0xe0 ffff88004117fd70
 […] __sys_sendmsg+0x17a/0x260 ffff88004117fdc8
 […] ? __sys_sendmsg_sock+0x80/0x80 ffff88004117fdf0
 […] ? syscall_trace_enter+0x15e/0x2c0 ffff88004117fe98
 […] do_syscall_64+0x7d/0x400 ffff88004117fec8
 […] entry_SYSCALL_64_safe_stack+0x4a/0x60 ffff88004117fef8
 </TASK>
==================================================================

The out-of-bounds report, though, is a red herring as it is for an
access that shouldn't have happened in the first place.

When nf_nat_init() fails to register its BPF kfuncs, it'll unwind and,
among others, call unregister_pernet_subsys() to deregister its per-net
ops. This makes the previously allocated net id available for reuse by
the next caller of register_pernet_subsys(), in our case, synproxy.
However, 'nat_net_id' will still hold the previously allocated value.

If nf_nat.o gets build as a module, all this doesn't matter. A failed
initialization routine makes the module fail to load and any dependent
module won't be able to load either. However, if nf_nat.o is built-in,
a failing init won't /completely/ make its functionality unavailable to
dependent modules, namely the code and static data is still there, free
to be called by modules like nft_chain_nat.ko.

Case in point, nft_chain_nat registers hooks that'll call into nf_nat
which, in our case, failed to initialize and therefore won't have a
valid net id nor related net_nat object any more.

Code in nf_nat, namely nf_nat_register_fn() and nf_nat_unregister_fn(),
still making use of the reallocated net id, lead to a type confusion as
the call to net_generic() will no longer return memory belonging to an
object suited to fit 'struct nat_net' but 'struct synproxy_net' instead.
The latter is only 24 bytes on 64-bit systems, much smaller than struct
nat_net which is 176 bytes, perfectly explaining the OOB KASAN report.

Detect and handle a failed nf_nat_init() by testing the 'nf_nat_hook'
pointer which will be reset to NULL on initialization errors to prevent
the usage of an invalid nat_net pointer.

As this check is only needed when nf_nat.o is built-in, guard it by
'#ifndef MODULE...'.

Fixes: cbc1dd5b659f ("netfilter: nf_nat: Fix possible memory leak in nf_nat_init()")
Signed-off-by: Mathias Krause <minipli@grsecurity.net>
---
 net/netfilter/nf_nat_core.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c
index 3b5434e4ec9c..76a150b9d418 100644
--- a/net/netfilter/nf_nat_core.c
+++ b/net/netfilter/nf_nat_core.c
@@ -1187,6 +1187,16 @@ int nf_nat_register_fn(struct net *net, u8 pf, const struct nf_hook_ops *ops,
 	struct nf_hook_ops *nat_ops;
 	int i, ret;
 
+#ifndef MODULE
+	/* If nf_nat_core is built-in and nf_nat_init() fails, dependent
+	 * modules like nft_chain_nat.ko may still call this function.
+	 * However, nat_net would be invalid, likely pointing to some other
+	 * per-net structure.
+	 */
+	if (WARN_ON_ONCE(!nf_nat_hook))
+		return -EOPNOTSUPP;
+#endif
+
 	if (WARN_ON_ONCE(pf >= ARRAY_SIZE(nat_net->nat_proto_net)))
 		return -EINVAL;
 
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH net] netfilter: nf_nat: avoid invalid nat_net pointer use on failed nf_nat_init()
  2026-04-28  9:09 [PATCH net] netfilter: nf_nat: avoid invalid nat_net pointer use on failed nf_nat_init() Mathias Krause
@ 2026-04-28 10:01 ` Pablo Neira Ayuso
  2026-04-28 11:48   ` Mathias Krause
  0 siblings, 1 reply; 3+ messages in thread
From: Pablo Neira Ayuso @ 2026-04-28 10:01 UTC (permalink / raw)
  To: Mathias Krause; +Cc: netfilter-devel, Florian Westphal, netdev

On Tue, Apr 28, 2026 at 11:09:17AM +0200, Mathias Krause wrote:
> We ran into below KASAN splat, which is mostly uninteresting, beside
> for having nf_nat_register_fn() in the call chain as a cause for the
> offending access:
> 
> ==================================================================
> BUG: KASAN: slab-out-of-bounds in nf_nat_register_fn+0x5f9/0x640
> Read of size 8 at addr ffff890031e54c20 by task iptables/9510
> 
> CPU: 0 UID: 0 PID: 9510 Comm: iptables Not tainted 6.18.18-grsec-full-20260320181326 #1 PREEMPT(voluntary)
> Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> Call Trace:
>  <TASK>
>  […] dump_stack_lvl+0xee/0x160 ffff88004117eeb8
>  […] print_report+0x6e/0x640 ffff88004117eee0
>  […] ? __phys_addr+0x8e/0x140 ffff88004117eef0
>  […] ? kasan_addr_to_slab+0x51/0xe0 ffff88004117ef08
>  […] ? complete_report_info+0xec/0x1c0 ffff88004117ef20
>  […] ? nf_nat_register_fn+0x5f9/0x640 ffff88004117ef48
>  […] kasan_report+0xbc/0x140 ffff88004117ef50
>  […] ? nf_nat_register_fn+0x5f9/0x640 ffff88004117ef90
>  […] nf_nat_register_fn+0x5f9/0x640 ffff88004117eff8
>  […] ? nf_nat_icmp_reply_translation+0x6e0/0x6e0 ffff88004117f070
>  […] nf_tables_register_hook.part.0+0xa0/0x220 ffff88004117f080
>  […] nf_tables_addchain.constprop.0+0x1054/0x1fc0 ffff88004117f0b8
>  […] ? nft_chain_lookup.part.0+0x4ce/0xac0 ffff88004117f130
>  […] ? nf_tables_abort+0x3d80/0x3d80 ffff88004117f190
>  […] ? nf_tables_dumpreset_obj+0x100/0x100 ffff88004117f1c8
>  […] ? nft_table_lookup.part.0+0x255/0x300 ffff88004117f310
>  […] ? nf_tables_newchain+0x21a4/0x2fa0 ffff88004117f358
>  […] nf_tables_newchain+0x21a4/0x2fa0 ffff88004117f360
>  […] ? nf_tables_addchain.constprop.0+0x1fc0/0x1fc0 ffff88004117f458
>  […] ? nla_get_range_signed+0x4a0/0x4a0 ffff88004117f488
>  […] ? lock_acquire+0x16f/0x320 ffff88004117f490
>  […] ? find_held_lock+0x3b/0xe0 ffff88004117f4b0
>  […] ? __nla_parse+0x45/0x80 ffff88004117f500
>  […] nfnetlink_rcv_batch+0xbca/0x19a0 ffff88004117f550
>  […] ? nfnetlink_net_exit_batch+0x120/0x120 ffff88004117f618
>  […] ? __sanitizer_cov_trace_switch+0x63/0xe0 ffff88004117f720
>  […] ? gr_acl_handle_mmap+0x1c4/0x320 ffff88004117f7c0
>  […] ? nla_get_range_signed+0x4a0/0x4a0 ffff88004117f7e8
>  […] ? gr_is_capable+0x6f/0xe0 ffff88004117f830
>  […] ? __nla_parse+0x45/0x80 ffff88004117f860
>  […] ? skb_pull+0x103/0x1a0 ffff88004117f880
>  […] nfnetlink_rcv+0x3db/0x4a0 ffff88004117f8b0
>  […] ? nfnetlink_rcv_batch+0x19a0/0x19a0 ffff88004117f8d8
>  […] ? netlink_lookup+0xe2/0x240 ffff88004117f900
>  […] netlink_unicast+0x74b/0xb00 ffff88004117f930
>  […] ? netlink_attachskb+0xb20/0xb20 ffff88004117f980
>  […] ? __check_object_size+0x3e/0xaa0 ffff88004117f998
>  […] ? security_netlink_send+0x51/0x160 ffff88004117f9c8
>  […] netlink_sendmsg+0xa03/0x1200 ffff88004117f9f8
>  […] ? netlink_unicast+0xb00/0xb00 ffff88004117fa70
>  […] ? netlink_unicast+0xb00/0xb00 ffff88004117fac8
>  […] ? ____sys_sendmsg+0xe2a/0x1040 ffff88004117faf8
>  […] ____sys_sendmsg+0xe2a/0x1040 ffff88004117fb00
>  […] ? kernel_recvmsg+0x300/0x300 ffff88004117fb60
>  […] ? reacquire_held_locks+0xe9/0x260 ffff88004117fbc8
>  […] ___sys_sendmsg+0x138/0x200 ffff88004117fbf8
>  […] ? do_recvmmsg+0x7e0/0x7e0 ffff88004117fc30
>  […] ? lockdep_hardirqs_on_prepare+0x101/0x1e0 ffff88004117fc50
>  […] ? lock_acquire+0x16f/0x320 ffff88004117fd20
>  […] ? lock_acquire+0x16f/0x320 ffff88004117fd58
>  […] ? find_held_lock+0x3b/0xe0 ffff88004117fd70
>  […] __sys_sendmsg+0x17a/0x260 ffff88004117fdc8
>  […] ? __sys_sendmsg_sock+0x80/0x80 ffff88004117fdf0
>  […] ? syscall_trace_enter+0x15e/0x2c0 ffff88004117fe98
>  […] do_syscall_64+0x7d/0x400 ffff88004117fec8
>  […] entry_SYSCALL_64_safe_stack+0x4a/0x60 ffff88004117fef8
>  </TASK>
> ==================================================================
> 
> The out-of-bounds report, though, is a red herring as it is for an
> access that shouldn't have happened in the first place.
> 
> When nf_nat_init() fails to register its BPF kfuncs, it'll unwind and,
> among others, call unregister_pernet_subsys() to deregister its per-net
> ops. This makes the previously allocated net id available for reuse by
> the next caller of register_pernet_subsys(), in our case, synproxy.
> However, 'nat_net_id' will still hold the previously allocated value.
> 
> If nf_nat.o gets build as a module, all this doesn't matter. A failed
> initialization routine makes the module fail to load and any dependent
> module won't be able to load either. However, if nf_nat.o is built-in,
> a failing init won't /completely/ make its functionality unavailable to
> dependent modules, namely the code and static data is still there, free
> to be called by modules like nft_chain_nat.ko.
> 
> Case in point, nft_chain_nat registers hooks that'll call into nf_nat
> which, in our case, failed to initialize and therefore won't have a
> valid net id nor related net_nat object any more.
> 
> Code in nf_nat, namely nf_nat_register_fn() and nf_nat_unregister_fn(),
> still making use of the reallocated net id, lead to a type confusion as
> the call to net_generic() will no longer return memory belonging to an
> object suited to fit 'struct nat_net' but 'struct synproxy_net' instead.
> The latter is only 24 bytes on 64-bit systems, much smaller than struct
> nat_net which is 176 bytes, perfectly explaining the OOB KASAN report.
> 
> Detect and handle a failed nf_nat_init() by testing the 'nf_nat_hook'
> pointer which will be reset to NULL on initialization errors to prevent
> the usage of an invalid nat_net pointer.
> 
> As this check is only needed when nf_nat.o is built-in, guard it by
> '#ifndef MODULE...'.
> 
> Fixes: cbc1dd5b659f ("netfilter: nf_nat: Fix possible memory leak in nf_nat_init()")
> Signed-off-by: Mathias Krause <minipli@grsecurity.net>
> ---
>  net/netfilter/nf_nat_core.c | 10 ++++++++++
>  1 file changed, 10 insertions(+)
> 
> diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c
> index 3b5434e4ec9c..76a150b9d418 100644
> --- a/net/netfilter/nf_nat_core.c
> +++ b/net/netfilter/nf_nat_core.c
> @@ -1187,6 +1187,16 @@ int nf_nat_register_fn(struct net *net, u8 pf, const struct nf_hook_ops *ops,
>  	struct nf_hook_ops *nat_ops;
>  	int i, ret;
>  
> +#ifndef MODULE
> +	/* If nf_nat_core is built-in and nf_nat_init() fails, dependent
> +	 * modules like nft_chain_nat.ko may still call this function.
> +	 * However, nat_net would be invalid, likely pointing to some other
> +	 * per-net structure.

Hm, if nf_nat_init() fails, then nft_chain_nat should fail to load.

Maybe there is a different way to validate this dependency?

> +	 */
> +	if (WARN_ON_ONCE(!nf_nat_hook))
> +		return -EOPNOTSUPP;
> +#endif
> +
>  	if (WARN_ON_ONCE(pf >= ARRAY_SIZE(nat_net->nat_proto_net)))
>  		return -EINVAL;
>  
> -- 
> 2.47.3
> 

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH net] netfilter: nf_nat: avoid invalid nat_net pointer use on failed nf_nat_init()
  2026-04-28 10:01 ` Pablo Neira Ayuso
@ 2026-04-28 11:48   ` Mathias Krause
  0 siblings, 0 replies; 3+ messages in thread
From: Mathias Krause @ 2026-04-28 11:48 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, netdev

On 28.04.26 12:01, Pablo Neira Ayuso wrote:
> On Tue, Apr 28, 2026 at 11:09:17AM +0200, Mathias Krause wrote:
>> --- a/net/netfilter/nf_nat_core.c
>> +++ b/net/netfilter/nf_nat_core.c
>> @@ -1187,6 +1187,16 @@ int nf_nat_register_fn(struct net *net, u8 pf, const struct nf_hook_ops *ops,
>>  	struct nf_hook_ops *nat_ops;
>>  	int i, ret;
>>  
>> +#ifndef MODULE
>> +	/* If nf_nat_core is built-in and nf_nat_init() fails, dependent
>> +	 * modules like nft_chain_nat.ko may still call this function.
>> +	 * However, nat_net would be invalid, likely pointing to some other
>> +	 * per-net structure.
> 
> Hm, if nf_nat_init() fails, then nft_chain_nat should fail to load.

If nf_nat is a module, that is the case, yes. However, the failing case
had it built-in (CONFIG_NF_NAT=y) and there's little the kernel can do
when some init function fails -- the code and data will still be part of
vmlinux.

> 
> Maybe there is a different way to validate this dependency?

Yeah, maybe. Maybe move the 'nf_nat_hook != NULL' test to
nft_chain_nat_init()? It's exported already, so this should work --
assuming the respective init functions get called in the right order
(nf_nat_init() first, then nft_chain_nat_init()). But that should be the
case, even if both are built-in, according to the order in
net/netfilter/Makefile.

It's a little fragile, though. In case the link order changes one day,
the test would lead to a false positive, making nft_chain_nat fail for
the wrong reason.

Also, I'm uncertain if the link order is really that deterministic wrt.
init functions for LTO builds?

Mathias

> 
>> +	 */
>> +	if (WARN_ON_ONCE(!nf_nat_hook))
>> +		return -EOPNOTSUPP;
>> +#endif
>> +
>>  	if (WARN_ON_ONCE(pf >= ARRAY_SIZE(nat_net->nat_proto_net)))
>>  		return -EINVAL;
>>  
>> -- 
>> 2.47.3
>>


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-04-28 11:48 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-28  9:09 [PATCH net] netfilter: nf_nat: avoid invalid nat_net pointer use on failed nf_nat_init() Mathias Krause
2026-04-28 10:01 ` Pablo Neira Ayuso
2026-04-28 11:48   ` Mathias Krause

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox