upstream: Add PerSourceMaxStartups and PerSourceNetBlockSize

options which provide more fine grained MaxStartups limits.  Man page help
jmc@, feedback & ok djm@

OpenBSD-Commit-ID: e2f68664e3d02c0895b35aa751c48a2af622047b
This commit is contained in:
dtucker@openbsd.org 2021-01-09 12:10:02 +00:00 коммит произвёл Darren Tucker
Родитель d9a2bc7169
Коммит 3a92312953
7 изменённых файлов: 256 добавлений и 11 удалений

Просмотреть файл

@ -125,7 +125,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o \
monitor.o monitor_wrap.o auth-krb5.o \ monitor.o monitor_wrap.o auth-krb5.o \
auth2-gss.o gss-serv.o gss-serv-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 \ 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-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \
sandbox-seccomp-filter.o sandbox-capsicum.o sandbox-pledge.o \ sandbox-seccomp-filter.o sandbox-capsicum.o sandbox-pledge.o \
sandbox-solaris.o uidswap.o $(SKOBJS) sandbox-solaris.o uidswap.o $(SKOBJS)

Просмотреть файл

@ -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 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
* All rights reserved * All rights reserved
@ -165,6 +165,9 @@ initialize_server_options(ServerOptions *options)
options->max_startups_begin = -1; options->max_startups_begin = -1;
options->max_startups_rate = -1; options->max_startups_rate = -1;
options->max_startups = -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_authtries = -1;
options->max_sessions = -1; options->max_sessions = -1;
options->banner = NULL; options->banner = NULL;
@ -419,6 +422,12 @@ fill_default_server_options(ServerOptions *options)
options->max_startups_rate = 30; /* 30% */ options->max_startups_rate = 30; /* 30% */
if (options->max_startups_begin == -1) if (options->max_startups_begin == -1)
options->max_startups_begin = 10; 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) if (options->max_authtries == -1)
options->max_authtries = DEFAULT_AUTH_FAIL_MAX; options->max_authtries = DEFAULT_AUTH_FAIL_MAX;
if (options->max_sessions == -1) if (options->max_sessions == -1)
@ -522,7 +531,7 @@ typedef enum {
sXAuthLocation, sSubsystem, sMaxStartups, sMaxAuthTries, sMaxSessions, sXAuthLocation, sSubsystem, sMaxStartups, sMaxAuthTries, sMaxSessions,
sBanner, sUseDNS, sHostbasedAuthentication, sBanner, sUseDNS, sHostbasedAuthentication,
sHostbasedUsesNameFromPacketOnly, sHostbasedAcceptedKeyTypes, sHostbasedUsesNameFromPacketOnly, sHostbasedAcceptedKeyTypes,
sHostKeyAlgorithms, sHostKeyAlgorithms, sPerSourceMaxStartups, sPerSourceNetBlockSize,
sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor, sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor,
sAcceptEnv, sSetEnv, sPermitTunnel, sAcceptEnv, sSetEnv, sPermitTunnel,
@ -648,6 +657,8 @@ static struct {
{ "gatewayports", sGatewayPorts, SSHCFG_ALL }, { "gatewayports", sGatewayPorts, SSHCFG_ALL },
{ "subsystem", sSubsystem, SSHCFG_GLOBAL }, { "subsystem", sSubsystem, SSHCFG_GLOBAL },
{ "maxstartups", sMaxStartups, SSHCFG_GLOBAL }, { "maxstartups", sMaxStartups, SSHCFG_GLOBAL },
{ "persourcemaxstartups", sPerSourceMaxStartups, SSHCFG_GLOBAL },
{ "persourcenetblocksize", sPerSourceNetBlockSize, SSHCFG_GLOBAL },
{ "maxauthtries", sMaxAuthTries, SSHCFG_ALL }, { "maxauthtries", sMaxAuthTries, SSHCFG_ALL },
{ "maxsessions", sMaxSessions, SSHCFG_ALL }, { "maxsessions", sMaxSessions, SSHCFG_ALL },
{ "banner", sBanner, 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; options->max_startups = options->max_startups_begin;
break; 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: case sMaxAuthTries:
intptr = &options->max_authtries; intptr = &options->max_authtries;
goto parse_int; goto parse_int;
@ -2905,6 +2955,13 @@ dump_config(ServerOptions *o)
printf("maxstartups %d:%d:%d\n", o->max_startups_begin, printf("maxstartups %d:%d:%d\n", o->max_startups_begin,
o->max_startups_rate, o->max_startups); 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; s = NULL;
for (i = 0; tunmode_desc[i].val != -1; i++) { for (i = 0; tunmode_desc[i].val != -1; i++) {

Просмотреть файл

@ -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> * Author: Tatu Ylonen <ylo@cs.hut.fi>
@ -177,6 +177,9 @@ typedef struct {
int max_startups_begin; int max_startups_begin;
int max_startups_rate; int max_startups_rate;
int max_startups; int max_startups;
int per_source_max_startups;
int per_source_masklen_ipv4;
int per_source_masklen_ipv6;
int max_authtries; int max_authtries;
int max_sessions; int max_sessions;
char *banner; /* SSH-2 banner message */ char *banner; /* SSH-2 banner message */

140
srclimit.c Normal file
Просмотреть файл

@ -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;
}
}
}

18
srclimit.h Normal file
Просмотреть файл

@ -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);

20
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> * Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -123,6 +123,7 @@
#include "version.h" #include "version.h"
#include "ssherr.h" #include "ssherr.h"
#include "sk-api.h" #include "sk-api.h"
#include "srclimit.h"
/* Re-exec fds */ /* Re-exec fds */
#define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) #define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1)
@ -853,7 +854,7 @@ should_drop_connection(int startups)
* while in that state. * while in that state.
*/ */
static int static int
drop_connection(int sock, int startups) drop_connection(int sock, int startups, int notify_pipe)
{ {
char *laddr, *raddr; char *laddr, *raddr;
const char msg[] = "Exceeded MaxStartups\r\n"; const char msg[] = "Exceeded MaxStartups\r\n";
@ -863,7 +864,8 @@ drop_connection(int sock, int startups)
time_t now; time_t now;
now = monotime(); now = monotime();
if (!should_drop_connection(startups)) { if (!should_drop_connection(startups) &&
srclimit_check_allow(sock, notify_pipe) == 1) {
if (last_drop != 0 && if (last_drop != 0 &&
startups < options.max_startups_begin - 1) { startups < options.max_startups_begin - 1) {
/* XXX maybe need better hysteresis here */ /* XXX maybe need better hysteresis here */
@ -1109,6 +1111,10 @@ server_listen(void)
{ {
u_int i; 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++) { for (i = 0; i < options.num_listen_addrs; i++) {
listen_on_addrs(&options.listen_addrs[i]); listen_on_addrs(&options.listen_addrs[i]);
freeaddrinfo(options.listen_addrs[i].addrs); 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: case 0:
/* child exited or completed auth */ /* child exited or completed auth */
close(startup_pipes[i]); close(startup_pipes[i]);
srclimit_done(startup_pipes[i]);
startup_pipes[i] = -1; startup_pipes[i] = -1;
startups--; startups--;
if (startup_flags[i]) if (startup_flags[i])
@ -1245,9 +1252,12 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s)
continue; continue;
} }
if (unset_nonblock(*newsock) == -1 || 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(*newsock);
close(startup_p[0]);
close(startup_p[1]);
continue; continue;
} }

Просмотреть файл

@ -33,8 +33,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" 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 $ .\" $OpenBSD: sshd_config.5,v 1.321 2021/01/09 12:10:02 dtucker Exp $
.Dd $Mdocdate: January 8 2021 $ .Dd $Mdocdate: January 9 2021 $
.Dt SSHD_CONFIG 5 .Dt SSHD_CONFIG 5
.Os .Os
.Sh NAME .Sh NAME
@ -1434,6 +1434,23 @@ SSH daemon, or
to not write one. to not write one.
The default is The default is
.Pa /var/run/sshd.pid . .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 .It Cm Port
Specifies the port number that Specifies the port number that
.Xr sshd 8 .Xr sshd 8