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 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 #include #include #include #include #include #include #include #include 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 #include #include 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