git: 46aaea6c19ef - stable/13 - sshd: update the libwrap patch to drop connections early

From: Ed Maste <emaste_at_FreeBSD.org>
Date: Fri, 07 Oct 2022 01:39:45 UTC
The branch stable/13 has been updated by emaste:

URL: https://cgit.FreeBSD.org/src/commit/?id=46aaea6c19ef1f377936eede16b4bdb626421dd6

commit 46aaea6c19ef1f377936eede16b4bdb626421dd6
Author:     Gleb Smirnoff <glebius@FreeBSD.org>
AuthorDate: 2022-01-03 02:32:30 +0000
Commit:     Ed Maste <emaste@FreeBSD.org>
CommitDate: 2022-10-07 01:39:00 +0000

    sshd: update the libwrap patch to drop connections early
    
    OpenSSH has dropped libwrap support in OpenSSH 6.7p in 2014
    (f2719b7c in github.com/openssh/openssh-portable) and we
    maintain the patch ourselves since 2016 (a0ee8cc636cd).
    
    Over the years, the libwrap support has deteriotated and probably
    that was reason for removal upstream.  Original idea of libwrap was
    to drop illegitimate connection as soon as possible, but over the
    years the code was pushed further down and down and ended in the
    forked client connection handler.
    
    The negative effects of late dropping is increasing attack surface
    for hosts that are to be dropped anyway.  Apart from hypothetical
    future vulnerabilities in connection handling, today a malicious
    host listed in /etc/hosts.allow still can trigger sshd to enter
    connection throttling mode, which is enabled by default (see
    MaxStartups in sshd_config(5)), effectively casting DoS attack.
    Note that on OpenBSD this attack isn't possible, since they enable
    MaxStartups together with UseBlacklist.
    
    A only negative effect from early drop, that I can imagine, is that
    now main listener parses file in /etc, and if our root filesystems
    goes bad, it would get stuck.  But unlikely you'd be able to login
    in that case anyway.
    
    Implementation details:
    
    - For brevity we reuse the same struct request_info.  This isn't
      a documented feature of libwrap, but code review, viewing data
      in a debugger and real life testing shows that if we clear
      RQ_CLIENT_NAME and RQ_CLIENT_ADDR every time, it works as intended.
    - We set SO_LINGER on the socket to force immediate connection reset.
    - We log message exactly as libwrap's refuse() would do.
    
    Differential revision:  https://reviews.freebsd.org/D33044
    
    (cherry picked from commit ca573c9a1779bdeeea6d0a6e948676555977737e)
---
 crypto/openssh/sshd.c | 60 ++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 40 insertions(+), 20 deletions(-)

diff --git a/crypto/openssh/sshd.c b/crypto/openssh/sshd.c
index d3da2fa10969..50c8eabb18bb 100644
--- a/crypto/openssh/sshd.c
+++ b/crypto/openssh/sshd.c
@@ -143,8 +143,8 @@
 #ifdef LIBWRAP
 #include <tcpd.h>
 #include <syslog.h>
-int allow_severity;
-int deny_severity;
+extern int allow_severity;
+extern int deny_severity;
 #endif /* LIBWRAP */
 
 /* Re-exec fds */
@@ -1165,6 +1165,11 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s)
 	pid_t pid;
 	u_char rnd[256];
 	sigset_t nsigset, osigset;
+#ifdef LIBWRAP
+	struct request_info req;
+
+	request_init(&req, RQ_DAEMON, __progname, 0);
+#endif
 
 	/* pipes connected to unauthenticated child sshd processes */
 	startup_pipes = xcalloc(options.max_startups, sizeof(int));
@@ -1294,6 +1299,31 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s)
 					usleep(100 * 1000);
 				continue;
 			}
+#ifdef LIBWRAP
+			/* Check whether logins are denied from this host. */
+			request_set(&req, RQ_FILE, *newsock,
+			    RQ_CLIENT_NAME, "", RQ_CLIENT_ADDR, "", 0);
+			sock_host(&req);
+			if (!hosts_access(&req)) {
+				const struct linger l = { .l_onoff = 1,
+				    .l_linger  = 0 };
+
+				(void )setsockopt(*newsock, SOL_SOCKET,
+				    SO_LINGER, &l, sizeof(l));
+				(void )close(*newsock);
+				/*
+				 * Mimic message from libwrap's refuse()
+				 * exactly.  sshguard, and supposedly lots
+				 * of custom made scripts rely on it.
+				 */
+				syslog(deny_severity,
+				    "refused connect from %s (%s)",
+				    eval_client(&req),
+				    eval_hostaddr(req.client));
+				debug("Connection refused by tcp wrapper");
+				continue;
+			}
+#endif /* LIBWRAP */
 			if (unset_nonblock(*newsock) == -1 ||
 			    pipe(startup_p) == -1) {
 				close(*newsock);
@@ -2065,6 +2095,14 @@ main(int ac, char **av)
 	/* Reinitialize the log (because of the fork above). */
 	log_init(__progname, options.log_level, options.log_facility, log_stderr);
 
+#ifdef LIBWRAP
+	/*
+	 * We log refusals ourselves.  However, libwrap will report
+	 * syntax errors in hosts.allow via syslog(3).
+	 */
+	allow_severity = options.log_facility|LOG_INFO;
+	deny_severity = options.log_facility|LOG_WARNING;
+#endif
 	/* Avoid killing the process in high-pressure swapping environments. */
 	if (!inetd_flag && madvise(NULL, 0, MADV_PROTECT) != 0)
 		debug("madvise(): %.200s", strerror(errno));
@@ -2236,24 +2274,6 @@ main(int ac, char **av)
 #ifdef SSH_AUDIT_EVENTS
 	audit_connection_from(remote_ip, remote_port);
 #endif
-#ifdef LIBWRAP
-	allow_severity = options.log_facility|LOG_INFO;
-	deny_severity = options.log_facility|LOG_WARNING;
-	/* Check whether logins are denied from this host. */
-	if (ssh_packet_connection_is_on_socket(ssh)) {
-		struct request_info req;
-
-		request_init(&req, RQ_DAEMON, __progname, RQ_FILE, sock_in, 0);
-		fromhost(&req);
-
-		if (!hosts_access(&req)) {
-			debug("Connection refused by tcp wrapper");
-			refuse(&req);
-			/* NOTREACHED */
-			fatal("libwrap refuse returns");
-		}
-	}
-#endif /* LIBWRAP */
 
 	rdomain = ssh_packet_rdomain_in(ssh);