[openssh-commits] [openssh] 02/04: upstream: Add PerSourceMaxStartups and PerSourceNetBlockSize

git+noreply at mindrot.org git+noreply at mindrot.org
Mon Jan 11 15:06:06 AEDT 2021


This is an automated email from the git hooks/post-receive script.

dtucker pushed a commit to branch master
in repository openssh.

commit 3a923129534b007c2e24176a8655dec74eca9c46
Author: dtucker at openbsd.org <dtucker at openbsd.org>
Date:   Sat Jan 9 12:10:02 2021 +0000

    upstream: Add PerSourceMaxStartups and PerSourceNetBlockSize
    
    options which provide more fine grained MaxStartups limits.  Man page help
    jmc@, feedback & ok djm@
    
    OpenBSD-Commit-ID: e2f68664e3d02c0895b35aa751c48a2af622047b
---
 Makefile.in   |   2 +-
 servconf.c    |  61 ++++++++++++++++++++++++-
 servconf.h    |   5 ++-
 srclimit.c    | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 srclimit.h    |  18 ++++++++
 sshd.c        |  20 ++++++---
 sshd_config.5 |  21 ++++++++-
 7 files changed, 256 insertions(+), 11 deletions(-)

diff --git a/Makefile.in b/Makefile.in
index 82321341..76d3197f 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -125,7 +125,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o \
 	monitor.o monitor_wrap.o auth-krb5.o \
 	auth2-gss.o gss-serv.o gss-serv-krb5.o \
 	loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \
-	sftp-server.o sftp-common.o \
+	srclimit.o sftp-server.o sftp-common.o \
 	sandbox-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \
 	sandbox-seccomp-filter.o sandbox-capsicum.o sandbox-pledge.o \
 	sandbox-solaris.o uidswap.o $(SKOBJS)
diff --git a/servconf.c b/servconf.c
index ea7625d3..b8d2138f 100644
--- a/servconf.c
+++ b/servconf.c
@@ -1,5 +1,5 @@
 
-/* $OpenBSD: servconf.c,v 1.371 2020/10/18 11:32:02 djm Exp $ */
+/* $OpenBSD: servconf.c,v 1.372 2021/01/09 12:10:02 dtucker Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -165,6 +165,9 @@ initialize_server_options(ServerOptions *options)
 	options->max_startups_begin = -1;
 	options->max_startups_rate = -1;
 	options->max_startups = -1;
+	options->per_source_max_startups = -1;
+	options->per_source_masklen_ipv4 = -1;
+	options->per_source_masklen_ipv6 = -1;
 	options->max_authtries = -1;
 	options->max_sessions = -1;
 	options->banner = NULL;
@@ -419,6 +422,12 @@ fill_default_server_options(ServerOptions *options)
 		options->max_startups_rate = 30;		/* 30% */
 	if (options->max_startups_begin == -1)
 		options->max_startups_begin = 10;
+	if (options->per_source_max_startups == -1)
+		options->per_source_max_startups = INT_MAX;
+	if (options->per_source_masklen_ipv4 == -1)
+		options->per_source_masklen_ipv4 = 32;
+	if (options->per_source_masklen_ipv6 == -1)
+		options->per_source_masklen_ipv6 = 128;
 	if (options->max_authtries == -1)
 		options->max_authtries = DEFAULT_AUTH_FAIL_MAX;
 	if (options->max_sessions == -1)
@@ -522,7 +531,7 @@ typedef enum {
 	sXAuthLocation, sSubsystem, sMaxStartups, sMaxAuthTries, sMaxSessions,
 	sBanner, sUseDNS, sHostbasedAuthentication,
 	sHostbasedUsesNameFromPacketOnly, sHostbasedAcceptedKeyTypes,
-	sHostKeyAlgorithms,
+	sHostKeyAlgorithms, sPerSourceMaxStartups, sPerSourceNetBlockSize,
 	sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
 	sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor,
 	sAcceptEnv, sSetEnv, sPermitTunnel,
@@ -648,6 +657,8 @@ static struct {
 	{ "gatewayports", sGatewayPorts, SSHCFG_ALL },
 	{ "subsystem", sSubsystem, SSHCFG_GLOBAL },
 	{ "maxstartups", sMaxStartups, SSHCFG_GLOBAL },
+	{ "persourcemaxstartups", sPerSourceMaxStartups, SSHCFG_GLOBAL },
+	{ "persourcenetblocksize", sPerSourceNetBlockSize, SSHCFG_GLOBAL },
 	{ "maxauthtries", sMaxAuthTries, SSHCFG_ALL },
 	{ "maxsessions", sMaxSessions, SSHCFG_ALL },
 	{ "banner", sBanner, SSHCFG_ALL },
@@ -1891,6 +1902,45 @@ process_server_config_line_depth(ServerOptions *options, char *line,
 			options->max_startups = options->max_startups_begin;
 		break;
 
+	case sPerSourceNetBlockSize:
+		arg = strdelim(&cp);
+		if (!arg || *arg == '\0')
+			fatal("%s line %d: Missing PerSourceNetBlockSize spec.",
+			    filename, linenum);
+		switch (n = sscanf(arg, "%d:%d", &value, &value2)) {
+		case 2:
+			if (value2 < 0 || value2 > 128)
+				n = -1;
+			/* FALLTHROUGH */
+		case 1:
+			if (value < 0 || value > 32)
+				n = -1;
+		}
+		if (n != 1 && n != 2)
+			fatal("%s line %d: Invalid PerSourceNetBlockSize"
+			    " spec.", filename, linenum);
+		if (*activep) {
+			options->per_source_masklen_ipv4 = value;
+			options->per_source_masklen_ipv6 = value2;
+		}
+		break;
+
+	case sPerSourceMaxStartups:
+		arg = strdelim(&cp);
+		if (!arg || *arg == '\0')
+			fatal("%s line %d: Missing PerSourceMaxStartups spec.",
+			    filename, linenum);
+		if (strcmp(arg, "none") == 0) { /* no limit */
+			value = INT_MAX;
+		} else {
+			if ((errstr = atoi_err(arg, &value)) != NULL)
+				fatal("%s line %d: integer value %s.",
+				    filename, linenum, errstr);
+		}
+		if (*activep)
+			options->per_source_max_startups = value;
+		break;
+
 	case sMaxAuthTries:
 		intptr = &options->max_authtries;
 		goto parse_int;
@@ -2905,6 +2955,13 @@ dump_config(ServerOptions *o)
 
 	printf("maxstartups %d:%d:%d\n", o->max_startups_begin,
 	    o->max_startups_rate, o->max_startups);
+	printf("persourcemaxstartups ");
+	if (o->per_source_max_startups == INT_MAX)
+		printf("none\n");
+	else
+		printf("%d\n", o->per_source_max_startups);
+	printf("persourcnetblocksize %d:%d\n", o->per_source_masklen_ipv4,
+	    o->per_source_masklen_ipv6);
 
 	s = NULL;
 	for (i = 0; tunmode_desc[i].val != -1; i++) {
diff --git a/servconf.h b/servconf.h
index a0efe20f..e0c3ff60 100644
--- a/servconf.h
+++ b/servconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.h,v 1.148 2020/10/29 03:13:06 djm Exp $ */
+/* $OpenBSD: servconf.h,v 1.149 2021/01/09 12:10:02 dtucker Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
@@ -177,6 +177,9 @@ typedef struct {
 	int	max_startups_begin;
 	int	max_startups_rate;
 	int	max_startups;
+	int	per_source_max_startups;
+	int	per_source_masklen_ipv4;
+	int	per_source_masklen_ipv6;
 	int	max_authtries;
 	int	max_sessions;
 	char   *banner;			/* SSH-2 banner message */
diff --git a/srclimit.c b/srclimit.c
new file mode 100644
index 00000000..e2446f13
--- /dev/null
+++ b/srclimit.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2020 Darren Tucker <dtucker at openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <limits.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "addr.h"
+#include "canohost.h"
+#include "log.h"
+#include "misc.h"
+#include "srclimit.h"
+#include "xmalloc.h"
+
+static int max_children, max_persource, ipv4_masklen, ipv6_masklen;
+
+/* Per connection state, used to enforce unauthenticated connection limit. */
+static struct child_info {
+	int id;
+	struct xaddr addr;
+} *child;
+
+void
+srclimit_init(int max, int persource, int ipv4len, int ipv6len)
+{
+	int i;
+
+	max_children = max;
+	ipv4_masklen = ipv4len;
+	ipv6_masklen = ipv6len;
+	max_persource = persource;
+	if (max_persource == INT_MAX)	/* no limit */
+		return;
+	debug("%s: max connections %d, per source %d, masks %d,%d", __func__,
+	    max, persource, ipv4len, ipv6len);
+	if (max <= 0)
+		fatal("%s: invalid number of sockets: %d", __func__, max);
+	child = xcalloc(max_children, sizeof(*child));
+	for (i = 0; i < max_children; i++)
+		child[i].id = -1;
+}
+
+/* returns 1 if connection allowed, 0 if not allowed. */
+int
+srclimit_check_allow(int sock, int id)
+{
+	struct xaddr xa, xb, xmask;
+	struct sockaddr_storage addr;
+	socklen_t addrlen = sizeof(addr);
+	struct sockaddr *sa = (struct sockaddr *)&addr;
+	int i, bits, first_unused, count = 0;
+	char xas[NI_MAXHOST];
+
+	if (max_persource == INT_MAX)	/* no limit */
+		return 1;
+
+	debug("%s: sock %d id %d limit %d", __func__, sock, id, max_persource);
+	if (getpeername(sock, sa, &addrlen) != 0)
+		return 1;	/* not remote socket? */
+	if (addr_sa_to_xaddr(sa, addrlen, &xa) != 0)
+		return 1;	/* unknown address family? */
+
+	/* Mask address off address to desired size. */
+	bits = xa.af == AF_INET ? ipv4_masklen : ipv6_masklen;
+	if (addr_netmask(xa.af, bits, &xmask) != 0 ||
+	    addr_and(&xb, &xa, &xmask) != 0) {
+		debug3("%s: invalid mask %d bits", __func__, bits);
+		return 1;
+	}
+
+	first_unused = max_children;
+	/* Count matching entries and find first unused one. */
+	for (i = 0; i < max_children; i++) {
+		if (child[i].id == -1) {
+			if (i < first_unused)
+				first_unused = i;
+		} else if (addr_cmp(&child[i].addr, &xb) == 0) {
+			count++;
+		}
+	}
+	if (addr_ntop(&xa, xas, sizeof(xas)) != 0) {
+		debug3("%s: addr ntop failed", __func__);
+		return 1;
+	}
+	debug3("%s: new unauthenticated connection from %s/%d, at %d of %d",
+	     __func__, xas, bits, count, max_persource);
+
+	if (first_unused == max_children) { /* no free slot found */
+		debug3("%s: no free slot", __func__);
+		return 0;
+	}
+	if (first_unused < 0 || first_unused >= max_children)
+		fatal("%s: internal error: first_unused out of range",
+		    __func__);
+
+	if (count >= max_persource)
+		return 0;
+
+	/* Connection allowed, store masked address. */
+	child[first_unused].id = id;
+	memcpy(&child[first_unused].addr, &xb, sizeof(xb));
+	return 1;
+}
+
+void
+srclimit_done(int id)
+{
+	int i;
+
+	if (max_persource == INT_MAX)	/* no limit */
+		return;
+
+	debug("%s: id %d", __func__, id);
+	/* Clear corresponding state entry. */
+	for (i = 0; i < max_children; i++) {
+		if (child[i].id == id) {
+			child[i].id = -1;
+			return;
+		}
+	}
+}
diff --git a/srclimit.h b/srclimit.h
new file mode 100644
index 00000000..6e04f32b
--- /dev/null
+++ b/srclimit.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2020 Darren Tucker <dtucker at openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+void	srclimit_init(int, int, int, int);
+int	srclimit_check_allow(int, int);
+void	srclimit_done(int);
diff --git a/sshd.c b/sshd.c
index 7e008730..1333ef5e 100644
--- a/sshd.c
+++ b/sshd.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshd.c,v 1.566 2020/12/29 00:59:15 djm Exp $ */
+/* $OpenBSD: sshd.c,v 1.567 2021/01/09 12:10:02 dtucker Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -123,6 +123,7 @@
 #include "version.h"
 #include "ssherr.h"
 #include "sk-api.h"
+#include "srclimit.h"
 
 /* Re-exec fds */
 #define REEXEC_DEVCRYPTO_RESERVED_FD	(STDERR_FILENO + 1)
@@ -853,7 +854,7 @@ should_drop_connection(int startups)
  * while in that state.
  */
 static int
-drop_connection(int sock, int startups)
+drop_connection(int sock, int startups, int notify_pipe)
 {
 	char *laddr, *raddr;
 	const char msg[] = "Exceeded MaxStartups\r\n";
@@ -863,7 +864,8 @@ drop_connection(int sock, int startups)
 	time_t now;
 
 	now = monotime();
-	if (!should_drop_connection(startups)) {
+	if (!should_drop_connection(startups) &&
+	    srclimit_check_allow(sock, notify_pipe) == 1) {
 		if (last_drop != 0 &&
 		    startups < options.max_startups_begin - 1) {
 			/* XXX maybe need better hysteresis here */
@@ -1109,6 +1111,10 @@ server_listen(void)
 {
 	u_int i;
 
+	/* Initialise per-source limit tracking. */
+	srclimit_init(options.max_startups, options.per_source_max_startups,
+	    options.per_source_masklen_ipv4, options.per_source_masklen_ipv6);
+
 	for (i = 0; i < options.num_listen_addrs; i++) {
 		listen_on_addrs(&options.listen_addrs[i]);
 		freeaddrinfo(options.listen_addrs[i].addrs);
@@ -1215,6 +1221,7 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s)
 			case 0:
 				/* child exited or completed auth */
 				close(startup_pipes[i]);
+				srclimit_done(startup_pipes[i]);
 				startup_pipes[i] = -1;
 				startups--;
 				if (startup_flags[i])
@@ -1245,9 +1252,12 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s)
 				continue;
 			}
 			if (unset_nonblock(*newsock) == -1 ||
-			    drop_connection(*newsock, startups) ||
-			    pipe(startup_p) == -1) {
+			    pipe(startup_p) == -1)
+				continue;
+			if (drop_connection(*newsock, startups, startup_p[0])) {
 				close(*newsock);
+				close(startup_p[0]);
+				close(startup_p[1]);
 				continue;
 			}
 
diff --git a/sshd_config.5 b/sshd_config.5
index ee9ff02f..8f0a5ccf 100644
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -33,8 +33,8 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: sshd_config.5,v 1.320 2021/01/08 02:19:24 djm Exp $
-.Dd $Mdocdate: January 8 2021 $
+.\" $OpenBSD: sshd_config.5,v 1.321 2021/01/09 12:10:02 dtucker Exp $
+.Dd $Mdocdate: January 9 2021 $
 .Dt SSHD_CONFIG 5
 .Os
 .Sh NAME
@@ -1434,6 +1434,23 @@ SSH daemon, or
 to not write one.
 The default is
 .Pa /var/run/sshd.pid .
+.It Cm PerSourceMaxStartups
+Specifies the number of unauthenticated connections allowed from a
+given source address, or
+.Dq none
+if there is no limit.
+This limit is applied in addition to
+.Cm MaxStartups ,
+whichever is lower.
+The default is
+.Cm none .
+.It Cm PerSourceNetBlockSize
+Specifies the number of bits of source address that are grouped together
+for the purposes of applying PerSourceMaxStartups limits.
+Values for IPv4 and optionally IPv6 may be specified, separated by a colon.
+The default is
+.Cm 32:128
+which means each address is considered individually.
 .It Cm Port
 Specifies the port number that
 .Xr sshd 8

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list