git: 9be802c04b7c - stable/14 - Avoid IPv6 source address selection on accepting TCP connections

From: Andrey V. Elsukov <ae_at_FreeBSD.org>
Date: Mon, 30 Oct 2023 17:13:26 UTC
The branch stable/14 has been updated by ae:

URL: https://cgit.FreeBSD.org/src/commit/?id=9be802c04b7cd98fcf7a1dd2f41d9ebac0e32996

commit 9be802c04b7cd98fcf7a1dd2f41d9ebac0e32996
Author:     Andrey V. Elsukov <ae@FreeBSD.org>
AuthorDate: 2023-09-14 08:39:06 +0000
Commit:     Andrey V. Elsukov <ae@FreeBSD.org>
CommitDate: 2023-10-30 17:12:50 +0000

    Avoid IPv6 source address selection on accepting TCP connections
    
    When an application listens IPv6 TCP socket, due to ipfw
    forwarding tag it may handle connections for addresses that do not
    belongs to the jail or even current host (transparent proxy).
    Syncache code can successfully handle TCP handshake for such connections.
    When syncache finally accepts connection it uses in6_pcbconnect() to
    properly initlize new connection info.
    
    For IPv4 this scenario just works, but for IPv6 it fails when
    local address doesn't belongs to the jail. This check occurs when
    in6_pcbladdr() applies IPv6 SAS algorithm.
    We need IPv6 SAS when we are connection initiator, but in the above
    case connection is already established and both source and destination
    addresses are known.
    
    Use unused argument to notify in6_pcbconnect() when we don't need
    source address selection. This will fix `ipfw fwd` to jailed IPv6
    address.
    
    When we are connection initiator, we stil use IPv6 SAS algorithm and
    apply all related restrictions.
    
    MFC after:              1 month
    Sponsored by:           Yandex LLC
    Differential Revision:  https://reviews.freebsd.org/D41685
    
    (cherry picked from commit 0bf5377b6b9642acc85355062b921a07604b7c04)
---
 sys/netinet6/in6_pcb.c | 29 +++++++++++++++++++++--------
 1 file changed, 21 insertions(+), 8 deletions(-)

diff --git a/sys/netinet6/in6_pcb.c b/sys/netinet6/in6_pcb.c
index bf81de78f992..5c4ef7570ddc 100644
--- a/sys/netinet6/in6_pcb.c
+++ b/sys/netinet6/in6_pcb.c
@@ -335,7 +335,7 @@ in6_pcbbind(struct inpcb *inp, struct sockaddr_in6 *sin6, struct ucred *cred)
  */
 static int
 in6_pcbladdr(struct inpcb *inp, struct sockaddr_in6 *sin6,
-    struct in6_addr *plocal_addr6)
+    struct in6_addr *plocal_addr6, bool sas_required)
 {
 	int error = 0;
 	int scope_ambiguous = 0;
@@ -364,13 +364,25 @@ in6_pcbladdr(struct inpcb *inp, struct sockaddr_in6 *sin6,
 	if ((error = prison_remote_ip6(inp->inp_cred, &sin6->sin6_addr)) != 0)
 		return (error);
 
-	error = in6_selectsrc_socket(sin6, inp->in6p_outputopts,
-	    inp, inp->inp_cred, scope_ambiguous, &in6a, NULL);
-	if (error)
-		return (error);
+	if (sas_required) {
+		error = in6_selectsrc_socket(sin6, inp->in6p_outputopts,
+		    inp, inp->inp_cred, scope_ambiguous, &in6a, NULL);
+		if (error)
+			return (error);
+	} else {
+		/*
+		 * Source address selection isn't required when syncache
+		 * has already established connection and both source and
+		 * destination addresses was chosen.
+		 *
+		 * This also includes the case when fwd_tag was used to
+		 * select source address in tcp_input().
+		 */
+		in6a = inp->in6p_laddr;
+	}
+
 	if (IN6_IS_ADDR_UNSPECIFIED(&in6a))
 		return (EHOSTUNREACH);
-
 	/*
 	 * Do not update this earlier, in case we return with an error.
 	 *
@@ -398,7 +410,7 @@ in6_pcbladdr(struct inpcb *inp, struct sockaddr_in6 *sin6,
  */
 int
 in6_pcbconnect(struct inpcb *inp, struct sockaddr_in6 *sin6, struct ucred *cred,
-    bool rehash __unused)
+    bool sas_required)
 {
 	struct inpcbinfo *pcbinfo = inp->inp_pcbinfo;
 	struct sockaddr_in6 laddr6;
@@ -432,7 +444,8 @@ in6_pcbconnect(struct inpcb *inp, struct sockaddr_in6 *sin6, struct ucred *cred,
 	 * Call inner routine, to assign local interface address.
 	 * in6_pcbladdr() may automatically fill in sin6_scope_id.
 	 */
-	if ((error = in6_pcbladdr(inp, sin6, &laddr6.sin6_addr)) != 0)
+	if ((error = in6_pcbladdr(inp, sin6, &laddr6.sin6_addr,
+	    sas_required)) != 0)
 		return (error);
 
 	if (in6_pcblookup_hash_locked(pcbinfo, &sin6->sin6_addr,