All of lore.kernel.org
 help / color / mirror / Atom feed
From: Pablo Neira Ayuso <pablo@netfilter.org>
To: Joas Antonio <joasantonio108@gmail.com>
Cc: netfilter-devel@vger.kernel.org, fw@strlen.de, coreteam@netfilter.org
Subject: Re: [PATCH net] netfilter: ip6t_NPT: ensure skb is writable before header modification
Date: Wed, 30 Sep 2026 22:49:22 +0200	[thread overview]
Message-ID: <ar110uvm8qM1hGEp@chamomile> (raw)
In-Reply-To: <6abd5063.b622e075.2a379c.077a@mx.google.com>

Hi,

On Wed, Sep 30, 2026 at 11:09:39AM -0700, Joas Antonio wrote:
> ip6t_snpt_tg() and ip6t_dnpt_tg() modify the IPv6 source/destination
> address in-place via ip6t_npt_map_pfx() without first calling
> skb_ensure_writable().  When the skb is cloned (nflog, tee, multicast,
> tc mirred), the write hits shared data visible to other users of the
> same buffer:
> 
>   if (!ip6t_npt_map_pfx(npt, &ipv6_hdr(skb)->saddr))
>           ...
> 
> ip6t_npt_map_pfx() does the actual in-place modification:
> 
>   addr->s6_addr32[idx] &= mask;
>   addr->s6_addr32[idx] |= ~mask & npt->dst_pfx.in6.s6_addr32[idx];
> 
> Neither target checks writability first.  Every comparable target in
> the tree does: ipt_ECN (line 32), ebt_dnat (line 24), ebt_snat
> (line 25), xt_TCPOPTSTRIP (line 52).
> 
> Additionally, the ICMPv6 bounced header path uses
> skb_header_pointer(), which returns a pointer to a stack-local copy
> for nonlinear skbs.  ip6t_npt_map_pfx() then modifies the copy;
> changes are silently lost and the inner IPv6 header in ICMPv6 error
> messages is never translated.
> 
> Fix both by adding skb_ensure_writable() before each target function
> operates, and replacing the skb_header_pointer() + helper pattern
> with skb_ensure_writable() up to the inner header offset followed by
> a direct pointer.  Remove the now-unused icmpv6_bounced_ipv6hdr().
> 
> Fixes: 8a91bb0c304b ("netfilter: ip6tables: add stateless IPv6-to-IPv6 Network Prefix Translation target")
> Signed-off-by: Joas Antonio <joasantonio108@gmail.com>
> ---
> Testing: verified with NFLOG + SNPT/DNPT rules on a namespace pair
> that cloned skb data is no longer corrupted after translation.
> Nonlinear-skb path confirmed by forcing GRO aggregation on the
> ingress side; inner ICMPv6 header now carries the translated prefix.

This is nf-next material.

>  net/ipv6/netfilter/ip6t_NPT.c | 66 ++++++++++++++++++--------------
>  1 file changed, 37 insertions(+), 29 deletions(-)
> 
> diff --git a/net/ipv6/netfilter/ip6t_NPT.c b/net/ipv6/netfilter/ip6t_NPT.c
> index XXXXXXX..XXXXXXX 100644
> --- a/net/ipv6/netfilter/ip6t_NPT.c
> +++ b/net/ipv6/netfilter/ip6t_NPT.c
> @@ -78,57 +78,65 @@ static bool ip6t_npt_map_pfx(const struct ip6t_npt_tginfo *npt,
>  	return true;
>  }
> 
> -static struct ipv6hdr *icmpv6_bounced_ipv6hdr(struct sk_buff *skb,
> -					      struct ipv6hdr *_bounced_hdr)
> -{
> -	if (ipv6_hdr(skb)->nexthdr != IPPROTO_ICMPV6)
> -		return NULL;
> -
> -	if (!icmpv6_is_err(icmp6_hdr(skb)->icmp6_type))
> -		return NULL;
> -
> -	return skb_header_pointer(skb,
> -				  skb_transport_offset(skb) + sizeof(struct icmp6hdr),
> -				  sizeof(struct ipv6hdr),
> -				  _bounced_hdr);
> -}
> -
>  static unsigned int
>  ip6t_snpt_tg(struct sk_buff *skb, const struct xt_action_param *par)
>  {
>  	const struct ip6t_npt_tginfo *npt = par->targinfo;
> -	struct ipv6hdr _bounced_hdr;
> -	struct ipv6hdr *bounced_hdr;
>  	struct in6_addr bounced_pfx;
> +	struct ipv6hdr *bounced_hdr;
> +	unsigned int bounced_off;
> +
> +	if (skb_ensure_writable(skb, sizeof(struct ipv6hdr)))
> +		return NF_DROP;
> 
>  	if (!ip6t_npt_map_pfx(npt, &ipv6_hdr(skb)->saddr)) {
>  		icmpv6_send(skb, ICMPV6_PARAMPROB, ICMPV6_HDR_FIELD,
>  			    offsetof(struct ipv6hdr, saddr));
>  		return NF_DROP;
>  	}
> 
>  	/* rewrite dst addr of bounced packet which was sent to dst range */
> -	bounced_hdr = icmpv6_bounced_ipv6hdr(skb, &_bounced_hdr);
> -	if (bounced_hdr) {
> -		ipv6_addr_prefix(&bounced_pfx, &bounced_hdr->daddr, npt->src_pfx_len);
> -		if (ipv6_addr_cmp(&bounced_pfx, &npt->src_pfx.in6) == 0)
> -			ip6t_npt_map_pfx(npt, &bounced_hdr->daddr);
> -	}
> +	if (ipv6_hdr(skb)->nexthdr != IPPROTO_ICMPV6 ||
> +	    !icmpv6_is_err(icmp6_hdr(skb)->icmp6_type))
> +		return XT_CONTINUE;
> +
> +	bounced_off = skb_transport_offset(skb) + sizeof(struct icmp6hdr);
> +	if (skb_ensure_writable(skb, bounced_off + sizeof(struct ipv6hdr)))
> +		return XT_CONTINUE;
> +
> +	bounced_hdr = (struct ipv6hdr *)(skb_transport_header(skb) +
> +					  sizeof(struct icmp6hdr));
> +	ipv6_addr_prefix(&bounced_pfx, &bounced_hdr->daddr, npt->src_pfx_len);
> +	if (ipv6_addr_cmp(&bounced_pfx, &npt->src_pfx.in6) == 0)
> +		ip6t_npt_map_pfx(npt, &bounced_hdr->daddr);
> 
>  	return XT_CONTINUE;
>  }
> 
>  static unsigned int
>  ip6t_dnpt_tg(struct sk_buff *skb, const struct xt_action_param *par)
>  {
>  	const struct ip6t_npt_tginfo *npt = par->targinfo;
> -	struct ipv6hdr _bounced_hdr;
> -	struct ipv6hdr *bounced_hdr;
>  	struct in6_addr bounced_pfx;
> +	struct ipv6hdr *bounced_hdr;
> +	unsigned int bounced_off;
> +
> +	if (skb_ensure_writable(skb, sizeof(struct ipv6hdr)))
> +		return NF_DROP;
> 
>  	if (!ip6t_npt_map_pfx(npt, &ipv6_hdr(skb)->daddr)) {
>  		icmpv6_send(skb, ICMPV6_PARAMPROB, ICMPV6_HDR_FIELD,
>  			    offsetof(struct ipv6hdr, daddr));
>  		return NF_DROP;
>  	}
> 
>  	/* rewrite src addr of bounced packet which was sent from dst range */
> -	bounced_hdr = icmpv6_bounced_ipv6hdr(skb, &_bounced_hdr);
> -	if (bounced_hdr) {
> -		ipv6_addr_prefix(&bounced_pfx, &bounced_hdr->saddr, npt->src_pfx_len);
> -		if (ipv6_addr_cmp(&bounced_pfx, &npt->src_pfx.in6) == 0)
> -			ip6t_npt_map_pfx(npt, &bounced_hdr->saddr);
> -	}
> +	if (ipv6_hdr(skb)->nexthdr != IPPROTO_ICMPV6 ||
> +	    !icmpv6_is_err(icmp6_hdr(skb)->icmp6_type))
> +		return XT_CONTINUE;
> +
> +	bounced_off = skb_transport_offset(skb) + sizeof(struct icmp6hdr);
> +	if (skb_ensure_writable(skb, bounced_off + sizeof(struct ipv6hdr)))
> +		return XT_CONTINUE;
> +
> +	bounced_hdr = (struct ipv6hdr *)(skb_transport_header(skb) +
> +					  sizeof(struct icmp6hdr));
> +	ipv6_addr_prefix(&bounced_pfx, &bounced_hdr->saddr, npt->src_pfx_len);
> +	if (ipv6_addr_cmp(&bounced_pfx, &npt->src_pfx.in6) == 0)
> +		ip6t_npt_map_pfx(npt, &bounced_hdr->saddr);
> 
>  	return XT_CONTINUE;
>  }
> --
> 2.43.0

      reply	other threads:[~2026-09-30 20:49 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-30 18:09 [PATCH net] netfilter: ip6t_NPT: ensure skb is writable before header modification Joas Antonio
2026-09-30 20:49 ` Pablo Neira Ayuso [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=ar110uvm8qM1hGEp@chamomile \
    --to=pablo@netfilter.org \
    --cc=coreteam@netfilter.org \
    --cc=fw@strlen.de \
    --cc=joasantonio108@gmail.com \
    --cc=netfilter-devel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.