Re: [PATCH nf v2] netfilter: nf_reject: initialize IPCB at inet ingress

Pablo Neira Ayuso <[email protected]>
Newsgroups gmane.comp.security.firewalls.netfilter.devel,gmane.linux.network,gmane.linux.kernel,gmane.linux.kernel.stable
Message-ID <aohV0xIb23Pvs7yt@chamomile>
On Mon, Aug 10, 2026 at 04:07:03PM +0000, David Lee wrote:
> From: Kyle Zeng <[email protected]>
> 
> The inet ingress hook runs before ip_rcv_core() and ip6_rcv_core(), so
> neither IPCB nor IP6CB has been initialized when nft_reject_inet passes
> a packet to nf_send_unreach() or nf_send_unreach6(). skb->cb can
> therefore retain metadata from an earlier protocol layer.
> 
> For IPv4, icmp_send() treats IPCB(skb)->opt as parsed IPv4 options.
> Stale option offsets can make __ip_options_echo() copy an
> attacker-controlled length into its 40-byte reply option buffer and
> overflow the stack. On an unpatched v7.2-rc5 kernel, the IPv4
> reproducer produced:
> 
>   BUG: KASAN: stack-out-of-bounds in __ip_options_echo()
>   Write of size 255
> 
> For IPv6, icmpv6_send() consumes IP6CB(skb) as parsed IPv6 metadata. It
> always uses iif for source selection and, when MIP6 is enabled, uses
> dsthao as an extension-header offset. These fields must not be stale.
> 
> For NF_INET_INGRESS, clear the address-family-specific control block
> and restore its input interface before constructing the ICMP error.
> This matches the initialization normally performed by ip_rcv_core()
> and ip6_rcv_core().
> 
> Fixes: 117ca1f8920c ("netfilter: nft_reject_inet: allow to use reject from inet ingress")
> Cc: [email protected]
> Assisted-by: Codex:gpt-5.6-sol Codex:gpt-5.5-cyber
> Signed-off-by: Kyle Zeng <[email protected]>
> Co-developed-by: David Lee <[email protected]>
> Signed-off-by: David Lee <[email protected]>
> ---
> Changes in v2:
> - Initialize IP6CB in nf_send_unreach6() for inet ingress.
> - Preserve the IPv6 input interface before constructing the reply dst.
> 
> v1: https://lore.kernel.org/netfilter-devel/[email protected]/
> 
> Bug found and triaged by OpenAI Security Research and
> validated by Trail of Bits.
> 
> Trail of Bits has an IPv4 reproducer for this bug that triggers the
> KASAN stack-out-of-bounds write in __ip_options_echo() and can share it
> if needed. A separate IPv6 test reaches nf_send_unreach6() from inet
> ingress and confirms that an ICMPv6 unreachable is constructed.
> 
>  net/ipv4/netfilter/nf_reject_ipv4.c | 6 ++++++
>  net/ipv6/netfilter/nf_reject_ipv6.c | 9 +++++++++
>  2 files changed, 15 insertions(+)
> 
> diff --git a/net/ipv4/netfilter/nf_reject_ipv4.c b/net/ipv4/netfilter/nf_reject_ipv4.c
> index 4626dc46808f..4933f65138d6 100644
> --- a/net/ipv4/netfilter/nf_reject_ipv4.c
> +++ b/net/ipv4/netfilter/nf_reject_ipv4.c
> @@ -355,6 +355,12 @@ void nf_send_unreach(struct sk_buff *skb_in, int code, int hook)
>  	if (!skb_dst(skb_in) && nf_reject_fill_skb_dst(skb_in) < 0)
>  		return;
>  
> +	/* Inet ingress runs before IPv4 initializes IPCB. */
> +	if (hook == NF_INET_INGRESS) {
> +		memset(IPCB(skb_in), 0, sizeof(*IPCB(skb_in)));
> +		IPCB(skb_in)->iif = skb_in->skb_iif;
> +	}
> +
>  	if (skb_csum_unnecessary(skb_in) ||
>  	    !nf_reject_verify_csum(skb_in, dataoff, proto)) {
>  		icmp_send(skb_in, ICMP_DEST_UNREACH, code, 0);
> diff --git a/net/ipv6/netfilter/nf_reject_ipv6.c b/net/ipv6/netfilter/nf_reject_ipv6.c
> index ef5b7e85cffa..c36910b8a95a 100644
> --- a/net/ipv6/netfilter/nf_reject_ipv6.c
> +++ b/net/ipv6/netfilter/nf_reject_ipv6.c
> @@ -9,6 +9,7 @@
>  #include <net/ip6_fib.h>
>  #include <net/ip6_checksum.h>
>  #include <net/netfilter/ipv6/nf_reject.h>
> +#include <net/dst_metadata.h>
>  #include <linux/netfilter_ipv6.h>
>  #include <linux/netfilter_bridge.h>
>  
> @@ -440,6 +441,14 @@ void nf_send_unreach6(struct net *net, struct sk_buff *skb_in,
>  	if (hooknum == NF_INET_LOCAL_OUT && skb_in->dev == NULL)
>  		skb_in->dev = net->loopback_dev;
>  
> +	/* Inet ingress runs before IPv6 initializes IP6CB. */
> +	if (hooknum == NF_INET_INGRESS) {
> +		memset(IP6CB(skb_in), 0, sizeof(*IP6CB(skb_in)));
> +		IP6CB(skb_in)->iif = skb_valid_dst(skb_in) ?
> +				     ip6_dst_idev(skb_dst(skb_in))->dev->ifindex :
> +				     skb_in->dev->ifindex;

Why do different? Maybe just use:

        IP6CB(skb_in)->iif = skb_in->skb_iif;

like in the IPv4 chunk?

> +	}
> +
>  	if (!skb_dst(skb_in) && nf_reject6_fill_skb_dst(skb_in) < 0)
>  		return;
>  
> -- 
> 2.53.0
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.