summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--usr.bin/ssh/servconf.c61
-rw-r--r--usr.bin/ssh/servconf.h5
-rw-r--r--usr.bin/ssh/srclimit.c138
-rw-r--r--usr.bin/ssh/srclimit.h18
-rw-r--r--usr.bin/ssh/sshd.c20
-rw-r--r--usr.bin/ssh/sshd/Makefile4
-rw-r--r--usr.bin/ssh/sshd_config.521
7 files changed, 255 insertions, 12 deletions
diff --git a/usr.bin/ssh/servconf.c b/usr.bin/ssh/servconf.c
index 85629048945..c52855504e9 100644
--- a/usr.bin/ssh/servconf.c
+++ b/usr.bin/ssh/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
@@ -147,6 +147,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;
@@ -394,6 +397,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)
@@ -493,7 +502,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,
@@ -603,6 +612,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 },
@@ -1833,6 +1844,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;
@@ -2834,6 +2884,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/usr.bin/ssh/servconf.h b/usr.bin/ssh/servconf.h
index 6917b43ecfd..f4ca62f2b46 100644
--- a/usr.bin/ssh/servconf.h
+++ b/usr.bin/ssh/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/usr.bin/ssh/srclimit.c b/usr.bin/ssh/srclimit.c
new file mode 100644
index 00000000000..f40fb19801c
--- /dev/null
+++ b/usr.bin/ssh/srclimit.c
@@ -0,0 +1,138 @@
+/*
+ * 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 <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/usr.bin/ssh/srclimit.h b/usr.bin/ssh/srclimit.h
new file mode 100644
index 00000000000..6e04f32b3ff
--- /dev/null
+++ b/usr.bin/ssh/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/usr.bin/ssh/sshd.c b/usr.bin/ssh/sshd.c
index ad6f4c6eb1e..8b8c9d1158f 100644
--- a/usr.bin/ssh/sshd.c
+++ b/usr.bin/ssh/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
@@ -106,6 +106,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)
@@ -812,7 +813,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";
@@ -822,7 +823,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 */
@@ -1056,6 +1058,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);
@@ -1161,6 +1167,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])
@@ -1191,9 +1198,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/usr.bin/ssh/sshd/Makefile b/usr.bin/ssh/sshd/Makefile
index 51a2fefd9d1..0768b2fb4cd 100644
--- a/usr.bin/ssh/sshd/Makefile
+++ b/usr.bin/ssh/sshd/Makefile
@@ -1,4 +1,4 @@
-# $OpenBSD: Makefile,v 1.104 2020/01/25 23:02:14 djm Exp $
+# $OpenBSD: Makefile,v 1.105 2021/01/09 12:10:02 dtucker Exp $
.PATH: ${.CURDIR}/..
@@ -6,7 +6,7 @@ SRCS= sshd.c auth-rhosts.c auth-passwd.c sshpty.c sshlogin.c servconf.c \
serverloop.c auth.c auth2.c auth-options.c session.c auth2-chall.c \
groupaccess.c auth-bsdauth.c auth2-hostbased.c auth2-kbdint.c \
auth2-none.c auth2-passwd.c auth2-pubkey.c monitor.c monitor_wrap.c \
- sftp-server.c sftp-common.c sftp-realpath.c sandbox-pledge.c
+ sftp-server.c sftp-common.c sftp-realpath.c sandbox-pledge.c srclimit.c
SRCS+= authfd.c compat.c dns.c fatal.c hostfile.c readpass.c utf8.c uidswap.c
SRCS+= ${SRCS_BASE} ${SRCS_KEX} ${SRCS_KEXS} ${SRCS_KEY} ${SRCS_KEYP} \
${SRCS_KRL} ${SRCS_PROT} ${SRCS_PKT} ${SRCS_UTL} ${SRCS_PKCS11} \
diff --git a/usr.bin/ssh/sshd_config.5 b/usr.bin/ssh/sshd_config.5
index 1dfd463eece..bac5b4f03c9 100644
--- a/usr.bin/ssh/sshd_config.5
+++ b/usr.bin/ssh/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
@@ -1436,6 +1436,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