Linux Netfilter development
 help / color / mirror / Atom feed
* [PATCH net] netfilter: ip6t_NPT: ensure skb is writable before header modification
@ 2026-09-30 18:09 Joas Antonio
  2026-09-30 20:49 ` Pablo Neira Ayuso
  0 siblings, 1 reply; 2+ messages in thread
From: Joas Antonio @ 2026-09-30 18:09 UTC (permalink / raw)
  To: netfilter-devel; +Cc: pablo, fw, coreteam

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.

 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

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

* Re: [PATCH net] netfilter: ip6t_NPT: ensure skb is writable before header modification
  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
  0 siblings, 0 replies; 2+ messages in thread
From: Pablo Neira Ayuso @ 2026-09-30 20:49 UTC (permalink / raw)
  To: Joas Antonio; +Cc: netfilter-devel, fw, coreteam

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

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

end of thread, other threads:[~2026-09-30 20:49 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox