Re: [PATCH net v2] net/ipv6: don't route packets with unknown source address

Ido Schimmel <[email protected]>
Newsgroups gmane.linux.kernel.bpf,gmane.linux.network,gmane.linux.kernel,gmane.comp.security.firewalls.netfilter.devel
Message-ID <20260903145631.GA182962@shredder>
On Thu, Sep 03, 2026 at 02:10:40PM +0200, Íñigo Huguet wrote:
> Don't allow routing packets with a source address that is not configured
> in the host. Allow it only in certain cases like when using a
> transparent socket, by setting the ANYSRC flag in flowi_flags.
> 
> Until now, it was possible to send such a packet if a route can be found
> in the routing table for it. For example:
> 1. Configure an address 1:2::3:4/64 and a static route 1:2::/64
> 2. Establish a TCP connection to 1:2::3:4
> 3. Remove the address from the interface, but keep the route.
> 4. Packets are still sent out by the TCP connection because of
>    the static route. No incoming packets are accepted, though.
> 
> This patch prevents the outgoing packets to be sent in normal
> circumnstances.
> 
> This aligns the behaviour with the IPv4 stack. To determine the places
> where the ANYSRC needs to be set, I set the flag in the same places as
> the IPv4 stack does.
> 
> Apart from consolidating the behaviour of both stacks, there is a more
> important reason why this is needed. RFC 4862 states that "an invalid
> address MUST NOT be used as the source address of outbound packets".
> Therefore, sending packets with a source address considered "invalid",
> like an expired address, is disallowed.
> 
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Signed-off-by: Íñigo Huguet <[email protected]>
> 
> ---
> 
> v2:
> - Fix a slab-out-of-bounds bug: in
>   tcp_v6_send_response we must not read the inet_flags because it may
>   not be an inet_sk, but a request socket.
>   Detected by syzbot, Sashiko and other bots.
> - Use the addr_type from saddr instead of daddr in ip6_route_me_harder.
>   Detected by Sashiko.
> - Don't overwrite flowi_flags in tcp_v6_connect when setting the
>   ANY_SPORT flag. Detected by Sashiko.
> - Fixed line length warnings.
> v1: https://lore.kernel.org/netdev/[email protected]/
> 
> Testing: tested with a manual reproducer executing the steps described
> above. Tested also with transparent sockets to ensure that the packets
> are sent in that case. Also executed the following selftests to prevent
> potential regressions: fcnal-ipv6, fib_tests, fib-onlink-tests,
> nft_nat, nft_tproxy_tcp, nft_tproxy_udp.
> 
> The change in the netfilter's ip6_route_me_harder function is the one
> that I'm more unsure about. It was not clear to me the reason why it was
> done like this in the IPv4 counterpart. Please review carefully.
> ---
>  drivers/net/vrf.c                |  1 +
>  net/core/lwt_bpf.c               |  1 +
>  net/ipv6/af_inet6.c              |  1 +
>  net/ipv6/datagram.c              |  1 +
>  net/ipv6/inet6_connection_sock.c |  2 ++
>  net/ipv6/ip6_output.c            | 28 ++++++++++++++++++++++++++++
>  net/ipv6/netfilter.c             | 11 +++++++++--
>  net/ipv6/ping.c                  |  1 +
>  net/ipv6/raw.c                   |  1 +
>  net/ipv6/syncookies.c            |  1 +
>  net/ipv6/tcp_ipv6.c              |  4 +++-
>  net/ipv6/udp.c                   |  1 +
>  net/l2tp/l2tp_ip6.c              |  2 ++
>  13 files changed, 52 insertions(+), 3 deletions(-)

1. This is a behavior change, not a bug fix, and should be targeted at
net-next without a Fixes tag.

2. What is the motivation for this drastic change beyond RFC conformance
and parity with IPv4? IMO, these two are not a good enough reason to
make such a change with a huge blast radius.

Here's a recent example of a one line change that argued for IPv4 parity
and was eventually reverted due to regression reports:

https://lore.kernel.org/all/[email protected]/
https://lore.kernel.org/netdev/[email protected]/

3. See [1] for a list of regressions that AI flagged. Even if v3 fixes
all of them (which means a much bigger diff), I don't think such a
change will be merged without a proper real-world motivation beyond RFC
conformance and IPv4 parity.

Thanks

[1]
1. Any-IP (local prefix routes) stops working

   Setup: ip -6 route add local 2001:db8::/64 dev lo (or the rule + table
   form from commit ab79ad14a2d5), TCP listener on [::].

   Path: tcp_v6_send_synack() -> inet6_csk_route_req() sets
   fl6->saddr = ireq->ir_v6_loc_addr with flags 0 -> ip6_dst_lookup_tail()
   -> ipv6_chk_addr_and_flags() misses because the address is only in the
   FIB, not in inet6_addr_lst -> -ENETUNREACH.

   Same for inet6_csk_route_socket() on the accepted socket,
   tcp_v6_send_response() for RSTs, and icmpv6_echo_reply(), which keeps
   the incoming daddr as saddr when ipv6_unicast_destination() (RTF_LOCAL)
   is true.

   Effect: no SYN-ACK, no echo reply, no RST for any Any-IP address.
   IPv4 avoids this via the local-table fallback in __ip_dev_find().

2. Anycast source addresses rejected

   Anycast addresses live in idev->ac_list, not inet6_addr_lst. Three
   paths pick one deliberately:

   - icmp6_send() uses ipv6_chk_acast_addr_src() to source ICMPv6 errors
     from the anycast daddr of the offending packet.
   - icmpv6_echo_reply() with anycast_src_echo_reply=1.
   - ip6_datagram_send_ctl() accepts an anycast IPV6_PKTINFO source
     (commit 7c90cc2d40ca), then udpv6_sendmsg() ->
     ip6_sk_dst_lookup_flow() fails it.

   Effect: ICMPv6 errors and echo replies for anycast destinations are
   dropped with OUTNOROUTES incremented. UDP sendmsg() with an anycast
   pktinfo passes the ancillary-data check and then fails with
   -ENETUNREACH.

3. TIME_WAIT replies of IP_TRANSPARENT connections dropped

   tcp_v6_send_response() uses "sk && sk_fullsock(sk)" to decide the
   flags. tcp_v6_rcv() reaches it with a timewait socket for both
   TCP_TW_ACK (tcp_v6_timewait_ack() -> tcp_v6_send_ack()) and TCP_TW_RST
   (tcp_v6_send_reset()). sk_fullsock() is false there, flags are 0, and
   fl6.saddr is the proxied non-local address.

   Effect: tproxy'd IPv6 connections send no ACK or RST from TIME_WAIT.
   IPv4 uses inet_sk_transparent(), which reads tw->tw_transparent and
   ireq->no_srccheck.

4. BPF-set non-local IPv6 tunnel sources stop working

   bpf_skb_set_tunnel_key() sets key.flow_flags = FLOWI_FLAG_ANYSRC only
   in the IPv4 branch (commit b8fff748521c, added so a program can use
   e.g. a container address as the outer source). udp_tunnel6_dst_lookup()
   (vxlan, geneve, bareudp) copies key->u.ipv6.src into fl6.saddr and
   never copies key->flow_flags. Before the patch this did not matter
   because IPv6 never checked the source.

   Effect: IPv6 collect_md tunnels with a BPF-chosen non-local source get
   -ENETUNREACH while the IPv4 equivalent keeps working.

5. ICMPv6 errors under IPsec lost in the relookup path

   icmpv6_route_lookup() does a second ip6_dst_lookup() with fl2 from
   xfrm_decode_session_reverse() when the first xfrm_lookup() returned
   -EPERM. fl2.saddr is the daddr of the packet in error, which is a
   remote host when the packet was being forwarded through a gateway. The
   new check fails it, relookup_failed has dst == NULL in the -EPERM case,
   and the function returns ERR_PTR(-ENETUNREACH).

   Effect: RFC 4301 ICMP handling on IPsec gateways with a block policy no
   longer sends the error into the tunnel. icmp_route_lookup() sets
   fl4_2.flowi4_flags |= FLOWI_FLAG_ANYSRC for exactly this relookup.
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.