Netdev List
 help / color / mirror / Atom feed
* [PATCH nf-next v3] netfilter: ip6t_NPT: ensure skb is writable before header modification
@ 2026-10-07 15:48 Joas Antonio dos Santos
  0 siblings, 0 replies; only message in thread
From: Joas Antonio dos Santos @ 2026-10-07 15:48 UTC (permalink / raw)
  To: Pablo Neira Ayuso, Florian Westphal
  Cc: Phil Sutter, netfilter-devel, coreteam, netdev

ip6t_snpt_tg() and ip6t_dnpt_tg() rewrite the IPv6 source/destination
address in place via ip6t_npt_map_pfx() without first calling
skb_ensure_writable().  When the skb data is shared with a clone, the
write is visible to the other holder of the buffer.

This is easy to hit with DNPT in PREROUTING: a packet socket on the
ingress interface (tcpdump, or any AF_PACKET listener) queues a clone
of the incoming skb, and DNPT then rewrites the shared data, so the
captured copy shows the translated destination instead of the one that
was on the wire.

The ICMPv6 error path has the same problem for the embedded IPv6
header, and a second one: icmpv6_bounced_ipv6hdr() uses
skb_header_pointer(), which returns a pointer to a stack copy when the
header is not in the linear area.  ip6t_npt_map_pfx() then modifies
the copy, so the inner header of such ICMPv6 errors is never
translated.

Call skb_ensure_writable() on the IPv6 header before translating it,
and make icmpv6_bounced_ipv6hdr() ensure the embedded header is
writable and return a pointer into the skb instead of a stack copy.

Fixes: 8a91bb0c304b ("netfilter: ip6tables: add stateless IPv6-to-IPv6 Network Prefix Translation target")
Fixes: d5608a0578a0 ("netfilter: ip6t_NPT: rewrite addresses in ICMPv6 original packet")
Signed-off-by: Joas Antonio dos Santos <joasantonio108@gmail.com>
Assisted-by: Claude:claude-opus-5-5
---
Changes in v3:
- v2 was hand-edited and does not apply (corrupt hunk); regenerated
  with git format-patch on top of current mainline.
- Keep icmpv6_bounced_ipv6hdr() and make it return a writable pointer
  into the skb instead of open-coding it twice.
- Commit message: describe the reproducer below instead of the
  earlier test notes.

Reproducer (tested on 602042bf29f6, arm64, QEMU):

  ip netns add C; ip netns add R; ip netns add S
  ip link add cr type veth peer name rc
  ip link add rs type veth peer name sr
  ip link set cr netns C; ip link set rc netns R
  ip link set rs netns R; ip link set sr netns S
  ip -n C -6 addr add fd00:1::2/64 dev cr nodad; ip -n C link set cr up
  ip -n R -6 addr add fd00:1::1/64 dev rc nodad; ip -n R link set rc up
  ip -n R -6 addr add fd00:2::1/64 dev rs nodad; ip -n R link set rs up
  ip -n S -6 addr add fd00:2::2/64 dev sr nodad; ip -n S link set sr up
  ip -n C -6 route add default via fd00:1::1
  ip -n S -6 route add fd00:99::/64 via fd00:2::1
  ip netns exec R sysctl -w net.ipv6.conf.all.forwarding=1
  ip netns exec R ip6tables -t mangle -A PREROUTING -i rs \
          -d fd00:99::/64 -j DNPT --src-pfx fd00:99::/64 --dst-pfx fd00:1::/64

  # AF_PACKET listener on rs that reads its queue 4s later (cap.c below)
  ip netns exec R ./cap rs 4 &
  sleep 1
  # any UDP sender works; send6.c below sends 3 datagrams to port 9999
  ip netns exec S ./send6 fd00:99::2
  wait

  before: captured UDP packet, dst=fd00:1::98:0:0:2  (rewritten in the clone)
  after:  captured UDP packet, dst=fd00:99::2        (as sent on the wire)

cap.c:

  #include <arpa/inet.h>
  #include <linux/if_ether.h>
  #include <linux/if_packet.h>
  #include <net/if.h>
  #include <netinet/ip6.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <sys/socket.h>
  #include <unistd.h>

  int main(int argc, char **argv)
  {
          struct sockaddr_ll ll = { .sll_family = AF_PACKET,
                                    .sll_protocol = htons(ETH_P_IPV6),
                                    .sll_ifindex = if_nametoindex(argv[1]) };
          int s = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6));
          char buf[2048], dst[64];

          bind(s, (void *)&ll, sizeof(ll));
          sleep(atoi(argv[2]));
          for (;;) {
                  struct ip6_hdr *h = (void *)buf;
                  int n = recv(s, buf, sizeof(buf), MSG_DONTWAIT);

                  if (n < (int)sizeof(*h))
                          break;
                  if (h->ip6_nxt != IPPROTO_UDP)
                          continue;
                  inet_ntop(AF_INET6, &h->ip6_dst, dst, sizeof(dst));
                  printf("captured UDP packet, dst=%s\n", dst);
          }
          return 0;
  }

send6.c:

  #include <arpa/inet.h>
  #include <sys/socket.h>
  #include <unistd.h>

  int main(int argc, char **argv)
  {
          struct sockaddr_in6 a = { .sin6_family = AF_INET6,
                                    .sin6_port = htons(9999) };
          int s = socket(AF_INET6, SOCK_DGRAM, 0);

          inet_pton(AF_INET6, argv[1], &a.sin6_addr);
          for (int i = 0; i < 3; i++) {
                  sendto(s, "npt", 3, 0, (void *)&a, sizeof(a));
                  usleep(200000);
          }
          return 0;
  }

Kernel config: IP6_NF_IPTABLES, IP6_NF_MANGLE, IP6_NF_TARGET_NPT, PACKET.
Builds cleanly with W=1.

 net/ipv6/netfilter/ip6t_NPT.c | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/net/ipv6/netfilter/ip6t_NPT.c b/net/ipv6/netfilter/ip6t_NPT.c
index 787c74aa8..e76d202dd 100644
--- a/net/ipv6/netfilter/ip6t_NPT.c
+++ b/net/ipv6/netfilter/ip6t_NPT.c
@@ -77,29 +77,34 @@ 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)
+static struct ipv6hdr *icmpv6_bounced_ipv6hdr(struct sk_buff *skb)
 {
+	unsigned int offset;
+
 	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);
+	offset = skb_transport_offset(skb) + sizeof(struct icmp6hdr);
+	if (skb_ensure_writable(skb, offset + sizeof(struct ipv6hdr)))
+		return NULL;
+
+	return (struct ipv6hdr *)(skb_transport_header(skb) +
+				  sizeof(struct icmp6hdr));
 }
 
 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;
 
+	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));
@@ -107,7 +112,7 @@ ip6t_snpt_tg(struct sk_buff *skb, const struct xt_action_param *par)
 	}
 
 	/* rewrite dst addr of bounced packet which was sent to dst range */
-	bounced_hdr = icmpv6_bounced_ipv6hdr(skb, &_bounced_hdr);
+	bounced_hdr = icmpv6_bounced_ipv6hdr(skb);
 	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)
@@ -121,10 +126,12 @@ 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;
 
+	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));
@@ -132,7 +139,7 @@ ip6t_dnpt_tg(struct sk_buff *skb, const struct xt_action_param *par)
 	}
 
 	/* rewrite src addr of bounced packet which was sent from dst range */
-	bounced_hdr = icmpv6_bounced_ipv6hdr(skb, &_bounced_hdr);
+	bounced_hdr = icmpv6_bounced_ipv6hdr(skb);
 	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)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-10-07 15:49 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-07 15:48 [PATCH nf-next v3] netfilter: ip6t_NPT: ensure skb is writable before header modification Joas Antonio dos Santos

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