]> git.ipfire.org Git - thirdparty/openssh-portable.git/commitdiff
upstream: Add PerSourceMaxStartups and PerSourceNetBlockSize
authordtucker@openbsd.org <dtucker@openbsd.org>
Sat, 9 Jan 2021 12:10:02 +0000 (12:10 +0000)
committerDarren Tucker <dtucker@dtucker.net>
Mon, 11 Jan 2021 04:04:12 +0000 (15:04 +1100)
options which provide more fine grained MaxStartups limits.  Man page help
jmc@, feedback & ok djm@

OpenBSD-Commit-ID: e2f68664e3d02c0895b35aa751c48a2af622047b

Makefile.in
servconf.c
servconf.h
srclimit.c [new file with mode: 0644]
srclimit.h [new file with mode: 0644]
sshd.c
sshd_config.5

index 82321341f54a52099bc98ff123b87884ca1d3d28..76d3197fc361819ea2ca516e1b63619648a9ea91 100644 (file)
@@ -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)
index ea7625d3631f70fecbe2f860e17c6deca81f5371..b8d2138f0172b751fc4f651cc23d43af6a62ebfc 100644 (file)
@@ -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++) {
index a0efe20fce16c4e38c721901a038854bb27ff8c9..e0c3ff60ae23b4368ab83ba242a77d04de91acbe 100644 (file)
@@ -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 (file)
index 0000000..e2446f1
--- /dev/null
@@ -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 (file)
index 0000000..6e04f32
--- /dev/null
@@ -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 7e00873099497a8ebaea1b089b06a709bdfd0dee..1333ef5e691f044e48821dfe28a5485b10bae67b 100644 (file)
--- 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
 #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;
                        }
 
index ee9ff02f2dbba114dfde167c4ffe63875d43bd8e..8f0a5ccf5b117b5fe41fe3b777aeb6ef70b4016b 100644 (file)
@@ -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