summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordtucker@openbsd.org <dtucker@openbsd.org>2021-01-09 12:10:02 +0000
committerDarren Tucker <dtucker@dtucker.net>2021-01-11 15:04:12 +1100
commit3a923129534b007c2e24176a8655dec74eca9c46 (patch)
tree136b1a9d28ec9f1527e5a47401dc72a2e78d2eab
parentd9a2bc71693ea27461a78110005d5a2d8b0c6a50 (diff)
upstream: Add PerSourceMaxStartups and PerSourceNetBlockSize
options which provide more fine grained MaxStartups limits. Man page help jmc@, feedback & ok djm@ OpenBSD-Commit-ID: e2f68664e3d02c0895b35aa751c48a2af622047b
-rw-r--r--Makefile.in2
-rw-r--r--servconf.c61
-rw-r--r--servconf.h5
-rw-r--r--srclimit.c140
-rw-r--r--srclimit.h18
-rw-r--r--sshd.c20
-rw-r--r--sshd_config.521
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@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@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@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@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@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@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