upstream: Add a facility to sshd(8) to penalise particular

problematic client behaviours, controlled by two new sshd_config(5) options:
PerSourcePenalties and PerSourcePenaltyExemptList.

When PerSourcePenalties are enabled, sshd(8) will monitor the exit
status of its child pre-auth session processes. Through the exit
status, it can observe situations where the session did not
authenticate as expected. These conditions include when the client
repeatedly attempted authentication unsucessfully (possibly indicating
an attack against one or more accounts, e.g. password guessing), or
when client behaviour caused sshd to crash (possibly indicating
attempts to exploit sshd).

When such a condition is observed, sshd will record a penalty of some
duration (e.g. 30 seconds) against the client's address. If this time
is above a minimum threshold specified by the PerSourcePenalties, then
connections from the client address will be refused (along with any
others in the same PerSourceNetBlockSize CIDR range).

Repeated offenses by the same client address will accrue greater
penalties, up to a configurable maximum. A PerSourcePenaltyExemptList
option allows certain address ranges to be exempt from all penalties.

We hope these options will make it significantly more difficult for
attackers to find accounts with weak/guessable passwords or exploit
bugs in sshd(8) itself.

PerSourcePenalties is off by default, but we expect to enable it
automatically in the near future.

much feedback markus@ and others, ok markus@

OpenBSD-Commit-ID: 89ded70eccb2b4926ef0366a4d58a693de366cca
This commit is contained in:
djm@openbsd.org 2024-06-06 17:15:25 +00:00 committed by Damien Miller
parent 916b0b6174
commit 81c1099d22
No known key found for this signature in database
12 changed files with 983 additions and 94 deletions

View File

@ -84,7 +84,7 @@ monitor.o: chacha.h poly1305.h cipher-aesctr.h rijndael.h kex.h mac.h crypto_api
monitor.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h ./openbsd-compat/sys-tree.h openbsd-compat/sys-queue.h openbsd-compat/openssl-compat.h atomicio.h xmalloc.h ssh.h sshkey.h sshbuf.h hostfile.h auth.h auth-pam.h audit.h loginrec.h cipher.h cipher-chachapoly.h
monitor_fdpass.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h log.h ssherr.h monitor_fdpass.h
monitor_wrap.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h openbsd-compat/sys-queue.h xmalloc.h ssh.h sshbuf.h sshkey.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h kex.h mac.h crypto_api.h hostfile.h auth.h auth-pam.h audit.h
monitor_wrap.o: loginrec.h auth-options.h packet.h dispatch.h log.h ssherr.h monitor.h atomicio.h monitor_fdpass.h misc.h channels.h session.h servconf.h monitor_wrap.h
monitor_wrap.o: loginrec.h auth-options.h packet.h dispatch.h log.h ssherr.h monitor.h atomicio.h monitor_fdpass.h misc.h channels.h session.h servconf.h monitor_wrap.h srclimit.h
msg.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h sshbuf.h ssherr.h log.h atomicio.h msg.h misc.h
mux.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h openbsd-compat/sys-queue.h xmalloc.h log.h ssherr.h ssh.h ssh2.h pathnames.h misc.h match.h sshbuf.h channels.h msg.h packet.h dispatch.h monitor_fdpass.h sshpty.h sshkey.h readconf.h clientloop.h
nchan.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h openbsd-compat/sys-queue.h ssh2.h sshbuf.h ssherr.h packet.h dispatch.h channels.h compat.h log.h
@ -126,7 +126,7 @@ sftp-usergroup.o: includes.h config.h defines.h platform.h openbsd-compat/openbs
sftp.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h log.h ssherr.h pathnames.h misc.h utf8.h sftp.h sshbuf.h sftp-common.h sftp-client.h openbsd-compat/glob.h sftp-usergroup.h
sk-usbhid.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
sntrup761.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
srclimit.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h addr.h canohost.h log.h ssherr.h misc.h srclimit.h xmalloc.h
srclimit.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h ./openbsd-compat/sys-tree.h addr.h canohost.h log.h ssherr.h misc.h srclimit.h xmalloc.h servconf.h openbsd-compat/sys-queue.h match.h
ssh-add.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h ssh.h log.h ssherr.h sshkey.h sshbuf.h authfd.h authfile.h pathnames.h misc.h digest.h ssh-sk.h sk-api.h hostfile.h
ssh-agent.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h openbsd-compat/sys-queue.h xmalloc.h ssh.h ssh2.h sshbuf.h sshkey.h authfd.h log.h ssherr.h misc.h digest.h match.h msg.h pathnames.h ssh-pkcs11.h sk-api.h myproposal.h
ssh-dss.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h
@ -162,7 +162,7 @@ sshconnect2.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-c
sshconnect2.o: sshconnect.h authfile.h dh.h authfd.h log.h ssherr.h misc.h readconf.h match.h canohost.h msg.h pathnames.h uidswap.h hostfile.h utf8.h ssh-sk.h sk-api.h
sshd-session.o: chacha.h poly1305.h cipher-aesctr.h rijndael.h digest.h sshkey.h kex.h mac.h crypto_api.h authfile.h pathnames.h atomicio.h canohost.h hostfile.h auth.h auth-pam.h audit.h loginrec.h authfd.h msg.h channels.h session.h monitor.h monitor_wrap.h ssh-sandbox.h auth-options.h version.h sk-api.h srclimit.h dh.h
sshd-session.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h ./openbsd-compat/sys-tree.h openbsd-compat/sys-queue.h xmalloc.h ssh.h ssh2.h sshpty.h packet.h dispatch.h log.h ssherr.h sshbuf.h misc.h match.h servconf.h uidswap.h compat.h cipher.h cipher-chachapoly.h
sshd.o: audit.h loginrec.h authfd.h msg.h version.h sk-api.h srclimit.h
sshd.o: audit.h loginrec.h authfd.h msg.h version.h sk-api.h addr.h srclimit.h
sshd.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h ./openbsd-compat/sys-tree.h openbsd-compat/sys-queue.h xmalloc.h ssh.h sshpty.h log.h ssherr.h sshbuf.h misc.h servconf.h compat.h digest.h sshkey.h authfile.h pathnames.h canohost.h hostfile.h auth.h auth-pam.h
ssherr.o: ssherr.h
sshkey-xmss.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h

18
misc.c
View File

@ -1,4 +1,4 @@
/* $OpenBSD: misc.c,v 1.195 2024/05/17 06:11:17 deraadt Exp $ */
/* $OpenBSD: misc.c,v 1.196 2024/06/06 17:15:25 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
* Copyright (c) 2005-2020 Damien Miller. All rights reserved.
@ -3100,3 +3100,19 @@ lib_contains_symbol(const char *path, const char *s)
return ret;
#endif /* HAVE_NLIST_H */
}
int
signal_is_crash(int sig)
{
switch (sig) {
case SIGSEGV:
case SIGBUS:
case SIGTRAP:
case SIGSYS:
case SIGFPE:
case SIGILL:
case SIGABRT:
return 1;
}
return 0;
}

3
misc.h
View File

@ -1,4 +1,4 @@
/* $OpenBSD: misc.h,v 1.108 2024/05/17 00:30:24 djm Exp $ */
/* $OpenBSD: misc.h,v 1.109 2024/06/06 17:15:25 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@ -252,6 +252,7 @@ void notify_complete(struct notifier_ctx *, const char *, ...)
typedef void (*sshsig_t)(int);
sshsig_t ssh_signal(int, sshsig_t);
int signal_is_crash(int);
/* On OpenBSD time_t is int64_t which is long long. */
/* #define SSH_TIME_T_MAX LLONG_MAX */

View File

@ -1,4 +1,4 @@
/* $OpenBSD: monitor.c,v 1.239 2024/05/17 06:42:04 jsg Exp $ */
/* $OpenBSD: monitor.c,v 1.240 2024/06/06 17:15:25 djm Exp $ */
/*
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
* Copyright 2002 Markus Friedl <markus@openbsd.org>
@ -161,6 +161,7 @@ static char *auth_submethod = NULL;
static u_int session_id2_len = 0;
static u_char *session_id2 = NULL;
static pid_t monitor_child_pid;
int auth_attempted = 0;
struct mon_table {
enum monitor_reqtype type;
@ -296,6 +297,10 @@ monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor)
authenticated = (monitor_read(ssh, pmonitor,
mon_dispatch, &ent) == 1);
/* Record that auth was attempted to set exit status later */
if ((ent->flags & MON_AUTH) != 0)
auth_attempted = 1;
/* Special handling for multiple required authentications */
if (options.num_auth_methods != 0) {
if (authenticated &&
@ -353,6 +358,7 @@ monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor)
fatal_f("authentication method name unknown");
debug_f("user %s authenticated by privileged process", authctxt->user);
auth_attempted = 0;
ssh->authctxt = NULL;
ssh_packet_set_log_preamble(ssh, "user %s", authctxt->user);

View File

@ -1,4 +1,4 @@
/* $OpenBSD: monitor_wrap.c,v 1.130 2024/05/17 00:30:24 djm Exp $ */
/* $OpenBSD: monitor_wrap.c,v 1.131 2024/06/06 17:15:25 djm Exp $ */
/*
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
* Copyright 2002 Markus Friedl <markus@openbsd.org>
@ -29,6 +29,7 @@
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <errno.h>
#include <pwd.h>
@ -73,6 +74,7 @@
#include "session.h"
#include "servconf.h"
#include "monitor_wrap.h"
#include "srclimit.h"
#include "ssherr.h"
@ -137,6 +139,36 @@ mm_request_send(int sock, enum monitor_reqtype type, struct sshbuf *m)
fatal_f("write: %s", strerror(errno));
}
static void
mm_reap(void)
{
int status = -1;
if (!mm_is_monitor())
return;
while (waitpid(pmonitor->m_pid, &status, 0) == -1) {
if (errno == EINTR)
continue;
pmonitor->m_pid = -1;
fatal_f("waitpid: %s", strerror(errno));
}
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) != 0) {
debug_f("preauth child exited with status %d",
WEXITSTATUS(status));
cleanup_exit(255);
}
} else if (WIFSIGNALED(status)) {
error_f("preauth child terminated by signal %d",
WTERMSIG(status));
cleanup_exit(signal_is_crash(WTERMSIG(status)) ?
EXIT_CHILD_CRASH : 255);
} else {
error_f("preauth child terminated abnormally");
cleanup_exit(EXIT_CHILD_CRASH);
}
}
void
mm_request_receive(int sock, struct sshbuf *m)
{
@ -149,6 +181,7 @@ mm_request_receive(int sock, struct sshbuf *m)
if (atomicio(read, sock, buf, sizeof(buf)) != sizeof(buf)) {
if (errno == EPIPE) {
debug3_f("monitor fd closed");
mm_reap();
cleanup_exit(255);
}
fatal_f("read: %s", strerror(errno));

View File

@ -1,4 +1,4 @@
/* $OpenBSD: servconf.c,v 1.407 2024/05/17 01:17:40 djm Exp $ */
/* $OpenBSD: servconf.c,v 1.408 2024/06/06 17:15:25 djm Exp $ */
/*
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
* All rights reserved
@ -163,6 +163,16 @@ initialize_server_options(ServerOptions *options)
options->per_source_max_startups = -1;
options->per_source_masklen_ipv4 = -1;
options->per_source_masklen_ipv6 = -1;
options->per_source_penalty_exempt = NULL;
options->per_source_penalty.enabled = -1;
options->per_source_penalty.max_sources = -1;
options->per_source_penalty.overflow_mode = -1;
options->per_source_penalty.penalty_crash = -1;
options->per_source_penalty.penalty_authfail = -1;
options->per_source_penalty.penalty_noauth = -1;
options->per_source_penalty.penalty_grace = -1;
options->per_source_penalty.penalty_max = -1;
options->per_source_penalty.penalty_min = -1;
options->max_authtries = -1;
options->max_sessions = -1;
options->banner = NULL;
@ -402,6 +412,24 @@ fill_default_server_options(ServerOptions *options)
options->per_source_masklen_ipv4 = 32;
if (options->per_source_masklen_ipv6 == -1)
options->per_source_masklen_ipv6 = 128;
if (options->per_source_penalty.enabled == -1)
options->per_source_penalty.enabled = 0;
if (options->per_source_penalty.max_sources == -1)
options->per_source_penalty.max_sources = 65536;
if (options->per_source_penalty.overflow_mode == -1)
options->per_source_penalty.overflow_mode = PER_SOURCE_PENALTY_OVERFLOW_PERMISSIVE;
if (options->per_source_penalty.penalty_crash == -1)
options->per_source_penalty.penalty_crash = 90;
if (options->per_source_penalty.penalty_grace == -1)
options->per_source_penalty.penalty_grace = 20;
if (options->per_source_penalty.penalty_authfail == -1)
options->per_source_penalty.penalty_authfail = 5;
if (options->per_source_penalty.penalty_noauth == -1)
options->per_source_penalty.penalty_noauth = 1;
if (options->per_source_penalty.penalty_min == -1)
options->per_source_penalty.penalty_min = 15;
if (options->per_source_penalty.penalty_max == -1)
options->per_source_penalty.penalty_max = 600;
if (options->max_authtries == -1)
options->max_authtries = DEFAULT_AUTH_FAIL_MAX;
if (options->max_sessions == -1)
@ -479,6 +507,7 @@ fill_default_server_options(ServerOptions *options)
CLEAR_ON_NONE(options->chroot_directory);
CLEAR_ON_NONE(options->routing_domain);
CLEAR_ON_NONE(options->host_key_agent);
CLEAR_ON_NONE(options->per_source_penalty_exempt);
for (i = 0; i < options->num_host_key_files; i++)
CLEAR_ON_NONE(options->host_key_files[i]);
@ -513,6 +542,7 @@ typedef enum {
sBanner, sUseDNS, sHostbasedAuthentication,
sHostbasedUsesNameFromPacketOnly, sHostbasedAcceptedAlgorithms,
sHostKeyAlgorithms, sPerSourceMaxStartups, sPerSourceNetBlockSize,
sPerSourcePenalties, sPerSourcePenaltyExemptList,
sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor,
sAcceptEnv, sSetEnv, sPermitTunnel,
@ -645,6 +675,8 @@ static struct {
{ "maxstartups", sMaxStartups, SSHCFG_GLOBAL },
{ "persourcemaxstartups", sPerSourceMaxStartups, SSHCFG_GLOBAL },
{ "persourcenetblocksize", sPerSourceNetBlockSize, SSHCFG_GLOBAL },
{ "persourcepenalties", sPerSourcePenalties, SSHCFG_GLOBAL },
{ "persourcepenaltyexemptlist", sPerSourcePenaltyExemptList, SSHCFG_GLOBAL },
{ "maxauthtries", sMaxAuthTries, SSHCFG_ALL },
{ "maxsessions", sMaxSessions, SSHCFG_ALL },
{ "banner", sBanner, SSHCFG_ALL },
@ -1945,6 +1977,89 @@ process_server_config_line_depth(ServerOptions *options, char *line,
options->per_source_max_startups = value;
break;
case sPerSourcePenaltyExemptList:
charptr = &options->per_source_penalty_exempt;
arg = argv_next(&ac, &av);
if (!arg || *arg == '\0')
fatal("%s line %d: missing file name.",
filename, linenum);
if (addr_match_list(NULL, arg) != 0) {
fatal("%s line %d: keyword %s "
"invalid address argument.",
filename, linenum, keyword);
}
if (*activep && *charptr == NULL)
*charptr = xstrdup(arg);
break;
case sPerSourcePenalties:
while ((arg = argv_next(&ac, &av)) != NULL) {
found = 1;
value = -1;
value2 = 0;
p = NULL;
/* Allow no/yes only in first position */
if (strcasecmp(arg, "no") == 0 ||
(value2 = (strcasecmp(arg, "yes") == 0))) {
if (ac > 0) {
fatal("%s line %d: keyword %s \"%s\" "
"argument must appear alone.",
filename, linenum, keyword, arg);
}
if (*activep &&
options->per_source_penalty.enabled == -1)
options->per_source_penalty.enabled = value2;
continue;
} else if (strncmp(arg, "crash:", 6) == 0) {
p = arg + 6;
intptr = &options->per_source_penalty.penalty_crash;
} else if (strncmp(arg, "authfail:", 9) == 0) {
p = arg + 9;
intptr = &options->per_source_penalty.penalty_authfail;
} else if (strncmp(arg, "noauth:", 7) == 0) {
p = arg + 7;
intptr = &options->per_source_penalty.penalty_noauth;
} else if (strncmp(arg, "grace-exceeded:", 15) == 0) {
p = arg + 15;
intptr = &options->per_source_penalty.penalty_grace;
} else if (strncmp(arg, "max:", 4) == 0) {
p = arg + 4;
intptr = &options->per_source_penalty.penalty_max;
} else if (strncmp(arg, "min:", 4) == 0) {
p = arg + 4;
intptr = &options->per_source_penalty.penalty_min;
} else if (strncmp(arg, "max-sources:", 12) == 0) {
intptr = &options->per_source_penalty.max_sources;
if ((errstr = atoi_err(arg+12, &value)) != NULL)
fatal("%s line %d: %s value %s.",
filename, linenum, keyword, errstr);
} else if (strcmp(arg, "overflow:deny-all") == 0) {
intptr = &options->per_source_penalty.overflow_mode;
value = PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL;
} else if (strcmp(arg, "overflow:permissive") == 0) {
intptr = &options->per_source_penalty.overflow_mode;
value = PER_SOURCE_PENALTY_OVERFLOW_PERMISSIVE;
} else {
fatal("%s line %d: unsupported %s keyword %s",
filename, linenum, keyword, arg);
}
/* If no value was parsed above, assume it's a time */
if (value == -1 && (value = convtime(p)) == -1) {
fatal("%s line %d: invalid %s time value.",
filename, linenum, keyword);
}
if (*activep && *intptr == -1) {
*intptr = value;
/* any option implicitly enables penalties */
options->per_source_penalty.enabled = 1;
}
}
if (!found) {
fatal("%s line %d: no %s specified",
filename, linenum, keyword);
}
break;
case sMaxAuthTries:
intptr = &options->max_authtries;
goto parse_int;
@ -3082,6 +3197,7 @@ dump_config(ServerOptions *o)
dump_cfg_string(sRDomain, o->routing_domain);
#endif
dump_cfg_string(sSshdSessionPath, o->sshd_session_path);
dump_cfg_string(sPerSourcePenaltyExemptList, o->per_source_penalty_exempt);
/* string arguments requiring a lookup */
dump_cfg_string(sLogLevel, log_level_name(o->log_level));
@ -3169,4 +3285,20 @@ dump_config(ServerOptions *o)
if (o->pubkey_auth_options & PUBKEYAUTH_VERIFY_REQUIRED)
printf(" verify-required");
printf("\n");
if (o->per_source_penalty.enabled) {
printf("persourcepenalties crash:%d authfail:%d noauth:%d "
"grace-exceeded:%d max:%d min:%d max-sources:%d "
"overflow:%s\n", o->per_source_penalty.penalty_crash,
o->per_source_penalty.penalty_authfail,
o->per_source_penalty.penalty_noauth,
o->per_source_penalty.penalty_grace,
o->per_source_penalty.penalty_max,
o->per_source_penalty.penalty_min,
o->per_source_penalty.max_sources,
o->per_source_penalty.overflow_mode ==
PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL ?
"deny-all" : "permissive");
} else
printf("persourcepenalties no\n");
}

View File

@ -1,4 +1,4 @@
/* $OpenBSD: servconf.h,v 1.163 2024/05/23 23:47:16 jsg Exp $ */
/* $OpenBSD: servconf.h,v 1.164 2024/06/06 17:15:25 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@ -65,6 +65,20 @@ struct listenaddr {
struct addrinfo *addrs;
};
#define PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL 1
#define PER_SOURCE_PENALTY_OVERFLOW_PERMISSIVE 2
struct per_source_penalty {
int enabled;
int max_sources;
int overflow_mode;
int penalty_crash;
int penalty_grace;
int penalty_authfail;
int penalty_noauth;
int penalty_max;
int penalty_min;
};
typedef struct {
u_int num_ports;
u_int ports_from_cmdline;
@ -172,6 +186,8 @@ typedef struct {
int per_source_max_startups;
int per_source_masklen_ipv4;
int per_source_masklen_ipv6;
char *per_source_penalty_exempt;
struct per_source_penalty per_source_penalty;
int max_authtries;
int max_sessions;
char *banner; /* SSH-2 banner message */

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2020 Darren Tucker <dtucker@openbsd.org>
* Copyright (c) 2024 Damien Miller <djm@mindrot.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -18,11 +19,13 @@
#include <sys/socket.h>
#include <sys/types.h>
#include <openbsd-compat/sys-tree.h>
#include <limits.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "addr.h"
#include "canohost.h"
@ -30,8 +33,12 @@
#include "misc.h"
#include "srclimit.h"
#include "xmalloc.h"
#include "servconf.h"
#include "match.h"
static int max_children, max_persource, ipv4_masklen, ipv6_masklen;
static struct per_source_penalty penalty_cfg;
static char *penalty_exempt;
/* Per connection state, used to enforce unauthenticated connection limit. */
static struct child_info {
@ -39,8 +46,58 @@ static struct child_info {
struct xaddr addr;
} *child;
/*
* Penalised addresses, active entries here prohibit connections until expired.
* Entries become active when more than penalty_min seconds of penalty are
* outstanding.
*/
struct penalty {
struct xaddr addr;
time_t expiry;
int active;
const char *reason;
RB_ENTRY(penalty) by_addr;
RB_ENTRY(penalty) by_expiry;
};
static int penalty_addr_cmp(struct penalty *a, struct penalty *b);
static int penalty_expiry_cmp(struct penalty *a, struct penalty *b);
RB_HEAD(penalties_by_addr, penalty) penalties_by_addr;
RB_HEAD(penalties_by_expiry, penalty) penalties_by_expiry;
RB_GENERATE_STATIC(penalties_by_addr, penalty, by_addr, penalty_addr_cmp)
RB_GENERATE_STATIC(penalties_by_expiry, penalty, by_expiry, penalty_expiry_cmp)
static size_t npenalties;
static int
srclimit_mask_addr(const struct xaddr *addr, int bits, struct xaddr *masked)
{
struct xaddr xmask;
/* Mask address off address to desired size. */
if (addr_netmask(addr->af, bits, &xmask) != 0 ||
addr_and(masked, addr, &xmask) != 0) {
debug3_f("%s: invalid mask %d bits", __func__, bits);
return -1;
}
return 0;
}
static int
srclimit_peer_addr(int sock, struct xaddr *addr)
{
struct sockaddr_storage storage;
socklen_t addrlen = sizeof(storage);
struct sockaddr *sa = (struct sockaddr *)&storage;
if (getpeername(sock, sa, &addrlen) != 0)
return 1; /* not remote socket? */
if (addr_sa_to_xaddr(sa, addrlen, addr) != 0)
return 1; /* unknown address family? */
return 0;
}
void
srclimit_init(int max, int persource, int ipv4len, int ipv6len)
srclimit_init(int max, int persource, int ipv4len, int ipv6len,
struct per_source_penalty *penalty_conf, const char *penalty_exempt_conf)
{
int i;
@ -48,6 +105,9 @@ srclimit_init(int max, int persource, int ipv4len, int ipv6len)
ipv4_masklen = ipv4len;
ipv6_masklen = ipv6len;
max_persource = persource;
penalty_cfg = *penalty_conf;
penalty_exempt = penalty_exempt_conf == NULL ?
NULL : xstrdup(penalty_exempt_conf);
if (max_persource == INT_MAX) /* no limit */
return;
debug("%s: max connections %d, per source %d, masks %d,%d", __func__,
@ -57,16 +117,15 @@ srclimit_init(int max, int persource, int ipv4len, int ipv6len)
child = xcalloc(max_children, sizeof(*child));
for (i = 0; i < max_children; i++)
child[i].id = -1;
RB_INIT(&penalties_by_addr);
RB_INIT(&penalties_by_expiry);
}
/* 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;
struct xaddr xa, xb;
int i, bits, first_unused, count = 0;
char xas[NI_MAXHOST];
@ -74,18 +133,11 @@ srclimit_check_allow(int sock, int id)
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);
if (srclimit_peer_addr(sock, &xa) != 0)
return 1;
bits = xa.af == AF_INET ? ipv4_masklen : ipv6_masklen;
if (srclimit_mask_addr(&xa, bits, &xb) != 0)
return 1;
}
first_unused = max_children;
/* Count matching entries and find first unused one. */
@ -138,3 +190,243 @@ srclimit_done(int id)
}
}
}
static int
penalty_addr_cmp(struct penalty *a, struct penalty *b)
{
return addr_cmp(&a->addr, &b->addr);
/* Addresses must be unique in by_addr, so no need to tiebreak */
}
static int
penalty_expiry_cmp(struct penalty *a, struct penalty *b)
{
if (a->expiry != b->expiry)
return a->expiry < b->expiry ? -1 : 1;
/* Tiebreak on addresses */
return addr_cmp(&a->addr, &b->addr);
}
static void
expire_penalties(time_t now)
{
struct penalty *penalty, *tmp;
/* XXX avoid full scan of tree, e.g. min-heap */
RB_FOREACH_SAFE(penalty, penalties_by_expiry,
&penalties_by_expiry, tmp) {
if (penalty->expiry >= now)
break;
if (RB_REMOVE(penalties_by_expiry, &penalties_by_expiry,
penalty) != penalty ||
RB_REMOVE(penalties_by_addr, &penalties_by_addr,
penalty) != penalty)
fatal_f("internal error: penalty tables corrupt");
free(penalty);
if (npenalties-- == 0)
fatal_f("internal error: npenalties underflow");
}
}
static void
addr_masklen_ntop(struct xaddr *addr, int masklen, char *s, size_t slen)
{
size_t o;
if (addr_ntop(addr, s, slen) != 0) {
strlcpy(s, "UNKNOWN", slen);
return;
}
if ((o = strlen(s)) < slen)
snprintf(s + o, slen - o, "/%d", masklen);
}
int
srclimit_penalty_check_allow(int sock, const char **reason)
{
struct xaddr addr;
struct penalty find, *penalty;
time_t now;
int bits;
char addr_s[NI_MAXHOST];
if (!penalty_cfg.enabled)
return 1;
if (srclimit_peer_addr(sock, &addr) != 0)
return 1;
if (penalty_exempt != NULL) {
if (addr_ntop(&addr, addr_s, sizeof(addr_s)) != 0)
return 1; /* shouldn't happen */
if (addr_match_list(addr_s, penalty_exempt) == 1) {
return 1;
}
}
if (npenalties > (size_t)penalty_cfg.max_sources &&
penalty_cfg.overflow_mode == PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL) {
*reason = "too many penalised addresses";
return 0;
}
bits = addr.af == AF_INET ? ipv4_masklen : ipv6_masklen;
memset(&find, 0, sizeof(find));
if (srclimit_mask_addr(&addr, bits, &find.addr) != 0)
return 1;
now = monotime();
if ((penalty = RB_FIND(penalties_by_addr,
&penalties_by_addr, &find)) == NULL)
return 1; /* no penalty */
if (penalty->expiry < now) {
expire_penalties(now);
return 1; /* expired penalty */
}
if (!penalty->active)
return 1; /* Penalty hasn't hit activation threshold yet */
*reason = penalty->reason;
return 0;
}
static void
srclimit_remove_expired_penalties(void)
{
struct penalty *p = NULL;
int bits;
char s[NI_MAXHOST + 4];
/* Delete the soonest-to-expire penalties. */
while (npenalties > (size_t)penalty_cfg.max_sources) {
if ((p = RB_MIN(penalties_by_expiry,
&penalties_by_expiry)) == NULL)
break; /* shouldn't happen */
bits = p->addr.af == AF_INET ? ipv4_masklen : ipv6_masklen;
addr_masklen_ntop(&p->addr, bits, s, sizeof(s));
debug3_f("overflow, remove %s", s);
if (RB_REMOVE(penalties_by_expiry,
&penalties_by_expiry, p) != p ||
RB_REMOVE(penalties_by_addr, &penalties_by_addr, p) != p)
fatal_f("internal error: penalty tables corrupt");
free(p);
npenalties--;
}
}
void
srclimit_penalise(struct xaddr *addr, int penalty_type)
{
struct xaddr masked;
struct penalty *penalty, *existing;
time_t now;
int bits, penalty_secs;
char addrnetmask[NI_MAXHOST + 4];
const char *reason = NULL;
if (!penalty_cfg.enabled)
return;
if (penalty_exempt != NULL) {
if (addr_ntop(addr, addrnetmask, sizeof(addrnetmask)) != 0)
return; /* shouldn't happen */
if (addr_match_list(addrnetmask, penalty_exempt) == 1) {
debug3_f("address %s is exempt", addrnetmask);
return;
}
}
switch (penalty_type) {
case SRCLIMIT_PENALTY_NONE:
return;
case SRCLIMIT_PENALTY_CRASH:
penalty_secs = penalty_cfg.penalty_crash;
reason = "penalty: caused crash";
break;
case SRCLIMIT_PENALTY_AUTHFAIL:
penalty_secs = penalty_cfg.penalty_authfail;
reason = "penalty: failed authentication";
break;
case SRCLIMIT_PENALTY_NOAUTH:
penalty_secs = penalty_cfg.penalty_noauth;
reason = "penalty: connections without attempting authentication";
break;
case SRCLIMIT_PENALTY_GRACE_EXCEEDED:
penalty_secs = penalty_cfg.penalty_crash;
reason = "penalty: exceeded LoginGraceTime";
break;
default:
fatal_f("internal error: unknown penalty %d", penalty_type);
}
bits = addr->af == AF_INET ? ipv4_masklen : ipv6_masklen;
if (srclimit_mask_addr(addr, bits, &masked) != 0)
return;
addr_masklen_ntop(addr, bits, addrnetmask, sizeof(addrnetmask));
now = monotime();
expire_penalties(now);
if (npenalties > (size_t)penalty_cfg.max_sources &&
penalty_cfg.overflow_mode == PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL) {
verbose_f("penalty table full, cannot penalise %s for %s",
addrnetmask, reason);
return;
}
penalty = xcalloc(1, sizeof(*penalty));
penalty->addr = masked;
penalty->expiry = now + penalty_secs;
penalty->reason = reason;
if ((existing = RB_INSERT(penalties_by_addr, &penalties_by_addr,
penalty)) == NULL) {
/* penalty didn't previously exist */
if (penalty_secs > penalty_cfg.penalty_min)
penalty->active = 1;
if (RB_INSERT(penalties_by_expiry, &penalties_by_expiry,
penalty) != NULL)
fatal_f("internal error: penalty tables corrupt");
verbose_f("%s: new %s penalty of %d seconds for %s",
addrnetmask, penalty->active ? "active" : "deferred",
penalty_secs, reason);
if (++npenalties > (size_t)penalty_cfg.max_sources)
srclimit_remove_expired_penalties(); /* permissive */
return;
}
debug_f("%s penalty for %s already exists, %lld seconds remaining",
existing->active ? "active" : "inactive",
addrnetmask, (long long)(existing->expiry - now));
/* Expiry information is about to change, remove from tree */
if (RB_REMOVE(penalties_by_expiry, &penalties_by_expiry,
existing) != existing)
fatal_f("internal error: penalty tables corrupt (remove)");
/* An entry already existed. Accumulate penalty up to maximum */
existing->expiry += penalty_secs;
if (existing->expiry - now > penalty_cfg.penalty_max)
existing->expiry = now + penalty_cfg.penalty_max;
if (existing->expiry - now > penalty_cfg.penalty_min &&
!existing->active) {
verbose_f("%s: activating penalty of %lld seconds for %s",
addrnetmask, (long long)(existing->expiry - now), reason);
existing->active = 1;
}
existing->reason = penalty->reason;
free(penalty);
/* Re-insert into expiry tree */
if (RB_INSERT(penalties_by_expiry, &penalties_by_expiry,
existing) != NULL)
fatal_f("internal error: penalty tables corrupt (insert)");
}
void
srclimit_penalty_info(void)
{
struct penalty *p = NULL;
int bits;
char s[NI_MAXHOST + 4];
time_t now;
now = monotime();
logit("%zu active penalties", npenalties);
RB_FOREACH(p, penalties_by_expiry, &penalties_by_expiry) {
bits = p->addr.af == AF_INET ? ipv4_masklen : ipv6_masklen;
addr_masklen_ntop(&p->addr, bits, s, sizeof(s));
if (p->expiry < now)
logit("client %s %s (expired)", s, p->reason);
else {
logit("client %s %s (%llu secs left)", s, p->reason,
(long long)(p->expiry - now));
}
}
}

View File

@ -13,6 +13,26 @@
* 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);
struct xaddr;
struct per_source_penalty;
void srclimit_init(int, int, int, int,
struct per_source_penalty *, const char *);
int srclimit_check_allow(int, int);
void srclimit_done(int);
#define SRCLIMIT_PENALTY_NONE 0
#define SRCLIMIT_PENALTY_CRASH 1
#define SRCLIMIT_PENALTY_AUTHFAIL 2
#define SRCLIMIT_PENALTY_GRACE_EXCEEDED 3
#define SRCLIMIT_PENALTY_NOAUTH 4
/* meaningful exit values, used by sshd listener for penalties */
#define EXIT_LOGIN_GRACE 3 /* login grace period exceeded */
#define EXIT_CHILD_CRASH 4 /* preauth child crashed */
#define EXIT_AUTH_ATTEMPTED 5 /* at least one auth attempt made */
void srclimit_penalise(struct xaddr *, int);
int srclimit_penalty_check_allow(int, const char **);
void srclimit_penalty_info(void);

View File

@ -1,4 +1,4 @@
/* $OpenBSD: sshd-session.c,v 1.2 2024/05/17 02:39:11 jsg Exp $ */
/* $OpenBSD: sshd-session.c,v 1.3 2024/06/06 17:15:25 djm Exp $ */
/*
* SSH2 implementation:
* Privilege Separation:
@ -209,11 +209,7 @@ grace_alarm_handler(int sig)
ssh_signal(SIGTERM, SIG_IGN);
kill(0, SIGTERM);
}
/* Log error and exit. */
sigdie("Timeout before authentication for %s port %d",
ssh_remote_ipaddr(the_active_state),
ssh_remote_port(the_active_state));
_exit(EXIT_LOGIN_GRACE);
}
/* Destroy the host and server keys. They will no longer be needed. */
@ -1303,6 +1299,8 @@ main(int ac, char **av)
ssh_signal(SIGALRM, SIG_DFL);
authctxt->authenticated = 1;
if (startup_pipe != -1) {
/* signal listener that authentication completed successfully */
(void)atomicio(vwrite, startup_pipe, "\001", 1);
close(startup_pipe);
startup_pipe = -1;
}
@ -1451,6 +1449,8 @@ do_ssh2_kex(struct ssh *ssh)
void
cleanup_exit(int i)
{
extern int auth_attempted; /* monitor.c */
if (the_active_state != NULL && the_authctxt != NULL) {
do_cleanup(the_active_state, the_authctxt);
if (privsep_is_preauth &&
@ -1463,6 +1463,9 @@ cleanup_exit(int i)
}
}
}
/* Override default fatal exit value when auth was attempted */
if (i == 255 && auth_attempted)
_exit(EXIT_AUTH_ATTEMPTED);
#ifdef SSH_AUDIT_EVENTS
/* done after do_cleanup so it can cancel the PAM auth 'thread' */
if (the_active_state != NULL && mm_is_monitor())

428
sshd.c
View File

@ -1,4 +1,4 @@
/* $OpenBSD: sshd.c,v 1.605 2024/06/01 07:03:37 djm Exp $ */
/* $OpenBSD: sshd.c,v 1.606 2024/06/06 17:15:25 djm Exp $ */
/*
* Copyright (c) 2000, 2001, 2002 Markus Friedl. All rights reserved.
* Copyright (c) 2002 Niels Provos. All rights reserved.
@ -89,6 +89,7 @@
#include "version.h"
#include "ssherr.h"
#include "sk-api.h"
#include "addr.h"
#include "srclimit.h"
/* Re-exec fds */
@ -138,6 +139,8 @@ struct {
} sensitive_data;
/* This is set to true when a signal is received. */
static volatile sig_atomic_t received_siginfo = 0;
static volatile sig_atomic_t received_sigchld = 0;
static volatile sig_atomic_t received_sighup = 0;
static volatile sig_atomic_t received_sigterm = 0;
@ -145,8 +148,9 @@ static volatile sig_atomic_t received_sigterm = 0;
u_int utmp_len = HOST_NAME_MAX+1;
/*
* startup_pipes/flags are used for tracking children of the listening sshd
* process early in their lifespans. This tracking is needed for three things:
* The early_child/children array below is used for tracking children of the
* listening sshd process early in their lifespans, before they have
* completed authentication. This tracking is needed for four things:
*
* 1) Implementing the MaxStartups limit of concurrent unauthenticated
* connections.
@ -155,14 +159,31 @@ u_int utmp_len = HOST_NAME_MAX+1;
* after it restarts.
* 3) Ensuring that rexec'd sshd processes have received their initial state
* from the parent listen process before handling SIGHUP.
* 4) Tracking and logging unsuccessful exits from the preauth sshd monitor,
* including and especially those for LoginGraceTime timeouts.
*
* Child processes signal that they have completed closure of the listen_socks
* and (if applicable) received their rexec state by sending a char over their
* sock. Child processes signal that authentication has completed by closing
* the sock (or by exiting).
* sock.
*
* Child processes signal that authentication has completed by sending a
* second char over the socket before closing it, otherwise the listener will
* continue tracking the child (and using up a MaxStartups slot) until the
* preauth subprocess exits, whereupon the listener will log its exit status.
* preauth processes will exit with a status of EXIT_LOGIN_GRACE to indicate
* they did not authenticate before the LoginGraceTime alarm fired.
*/
static int *startup_pipes = NULL;
static int *startup_flags = NULL; /* Indicates child closed listener */
struct early_child {
int pipefd;
int early; /* Indicates child closed listener */
char *id; /* human readable connection identifier */
pid_t pid;
struct xaddr addr;
int have_addr;
int status, have_status;
};
static struct early_child *children;
static int children_active;
static int startup_pipe = -1; /* in child */
/* sshd_config buffer */
@ -192,15 +213,257 @@ close_listen_socks(void)
num_listen_socks = 0;
}
/* Allocate and initialise the children array */
static void
child_alloc(void)
{
int i;
children = xcalloc(options.max_startups, sizeof(*children));
for (i = 0; i < options.max_startups; i++) {
children[i].pipefd = -1;
children[i].pid = -1;
}
}
/* Register a new connection in the children array; child pid comes later */
static struct early_child *
child_register(int pipefd, int sockfd)
{
int i, lport, rport;
char *laddr = NULL, *raddr = NULL;
struct early_child *child = NULL;
struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
struct sockaddr *sa = (struct sockaddr *)&addr;
for (i = 0; i < options.max_startups; i++) {
if (children[i].pipefd != -1 || children[i].pid > 0)
continue;
child = &(children[i]);
break;
}
if (child == NULL) {
fatal_f("error: accepted connection when all %d child "
" slots full", options.max_startups);
}
child->pipefd = pipefd;
child->early = 1;
/* record peer address, if available */
if (getpeername(sockfd, sa, &addrlen) == 0 &&
addr_sa_to_xaddr(sa, addrlen, &child->addr) == 0)
child->have_addr = 1;
/* format peer address string for logs */
if ((lport = get_local_port(sockfd)) == 0 ||
(rport = get_peer_port(sockfd)) == 0) {
/* Not a TCP socket */
raddr = get_peer_ipaddr(sockfd);
xasprintf(&child->id, "connection from %s", raddr);
} else {
laddr = get_local_ipaddr(sockfd);
raddr = get_peer_ipaddr(sockfd);
xasprintf(&child->id, "connection from %s to %s", laddr, raddr);
}
free(laddr);
free(raddr);
if (++children_active > options.max_startups)
fatal_f("internal error: more children than max_startups");
return child;
}
/*
* Finally free a child entry. Don't call this directly.
*/
static void
child_finish(struct early_child *child)
{
if (children_active == 0)
fatal_f("internal error: children_active underflow");
if (child->pipefd != -1)
close(child->pipefd);
free(child->id);
memset(child, '\0', sizeof(*child));
child->pipefd = -1;
child->pid = -1;
children_active--;
}
/*
* Close a child's pipe. This will not stop tracking the child immediately
* (it will still be tracked for waitpid()) unless force_final is set, or
* child has already exited.
*/
static void
child_close(struct early_child *child, int force_final, int quiet)
{
if (!quiet)
debug_f("enter%s", force_final ? " (forcing)" : "");
if (child->pipefd != -1) {
close(child->pipefd);
child->pipefd = -1;
}
if (child->pid == -1 || force_final)
child_finish(child);
}
/* Record a child exit. Safe to call from signal handlers */
static void
child_exit(pid_t pid, int status)
{
int i;
if (children == NULL || pid <= 0)
return;
for (i = 0; i < options.max_startups; i++) {
if (children[i].pid == pid) {
children[i].have_status = 1;
children[i].status = status;
break;
}
}
}
/*
* Reap a child entry that has exited, as previously flagged
* using child_exit().
* Handles logging of exit condition and will finalise the child if its pipe
* had already been closed.
*/
static void
child_reap(struct early_child *child)
{
LogLevel level = SYSLOG_LEVEL_DEBUG1;
int was_crash, penalty_type = SRCLIMIT_PENALTY_NONE;
/* Log exit information */
if (WIFSIGNALED(child->status)) {
/*
* Increase logging for signals potentially associated
* with serious conditions.
*/
if ((was_crash = signal_is_crash(WTERMSIG(child->status))))
level = SYSLOG_LEVEL_ERROR;
do_log2(level, "session process %ld for %s killed by "
"signal %d%s", (long)child->pid, child->id,
WTERMSIG(child->status), child->early ? " (early)" : "");
if (was_crash)
penalty_type = SRCLIMIT_PENALTY_CRASH;
} else if (!WIFEXITED(child->status)) {
penalty_type = SRCLIMIT_PENALTY_CRASH;
error("session process %ld for %s terminated abnormally, "
"status=0x%x%s", (long)child->pid, child->id, child->status,
child->early ? " (early)" : "");
} else {
/* Normal exit. We care about the status */
switch (WEXITSTATUS(child->status)) {
case 0:
debug3_f("preauth child %ld for %s completed "
"normally %s", (long)child->pid, child->id,
child->early ? " (early)" : "");
break;
case EXIT_LOGIN_GRACE:
penalty_type = SRCLIMIT_PENALTY_GRACE_EXCEEDED;
logit("Timeout before authentication for %s, "
"pid = %ld%s", child->id, (long)child->pid,
child->early ? " (early)" : "");
break;
case EXIT_CHILD_CRASH:
penalty_type = SRCLIMIT_PENALTY_CRASH;
logit("Session process %ld unpriv child crash for %s%s",
(long)child->pid, child->id,
child->early ? " (early)" : "");
break;
case EXIT_AUTH_ATTEMPTED:
penalty_type = SRCLIMIT_PENALTY_AUTHFAIL;
debug_f("preauth child %ld for %s exited "
"after unsuccessful auth attempt %s",
(long)child->pid, child->id,
child->early ? " (early)" : "");
break;
default:
penalty_type = SRCLIMIT_PENALTY_NOAUTH;
debug_f("preauth child %ld for %s exited "
"with status %d%s", (long)child->pid, child->id,
WEXITSTATUS(child->status),
child->early ? " (early)" : "");
break;
}
}
/*
* XXX would be nice to have more subtlety here.
* - Different penalties
* a) authentication failures without success (e.g. brute force)
* b) login grace exceeded (penalise DoS)
* c) monitor crash (penalise exploit attempt)
* d) unpriv preauth crash (penalise exploit attempt)
* - Unpriv auth exit status/WIFSIGNALLED is not available because
* the "mm_request_receive: monitor fd closed" fatal kills the
* monitor before waitpid() can occur. It would be good to use the
* unpriv exit status to detect crashes.
*
* For now, just penalise (a), (b) and (c), since that is what we have
* readily available. The authentication failures detection cannot
* discern between failed authentication and other connection problems
* until we have the unpriv exist status plumbed through (and the unpriv
* child modified to use a different exit status when auth has been
* attempted), but it's a start.
*/
if (child->have_addr)
srclimit_penalise(&child->addr, penalty_type);
child->pid = -1;
child->have_status = 0;
if (child->pipefd == -1)
child_finish(child);
}
/* Reap all children that have exited; called after SIGCHLD */
static void
child_reap_all_exited(void)
{
int i;
if (children == NULL)
return;
for (i = 0; i < options.max_startups; i++) {
if (!children[i].have_status)
continue;
child_reap(&(children[i]));
}
}
static void
close_startup_pipes(void)
{
int i;
if (startup_pipes)
for (i = 0; i < options.max_startups; i++)
if (startup_pipes[i] != -1)
close(startup_pipes[i]);
if (children == NULL)
return;
for (i = 0; i < options.max_startups; i++) {
if (children[i].pipefd != -1)
child_close(&(children[i]), 1, 1);
}
}
/* Called after SIGINFO */
static void
show_info(void)
{
int i;
/* XXX print listening sockets here too */
if (children == NULL)
return;
logit("%d active startups", children_active);
for (i = 0; i < options.max_startups; i++) {
if (children[i].pipefd == -1 && children[i].pid <= 0)
continue;
logit("child %d: fd=%d pid=%ld %s%s", i, children[i].pipefd,
(long)children[i].pid, children[i].id,
children[i].early ? " (early)" : "");
}
srclimit_penalty_info();
}
/*
@ -244,6 +507,14 @@ sigterm_handler(int sig)
received_sigterm = sig;
}
#ifdef SIGINFO
static void
siginfo_handler(int sig)
{
received_siginfo = 1;
}
#endif
/*
* SIGCHLD handler. This is called whenever a child dies. This will then
* reap any zombies left by exited children.
@ -255,9 +526,17 @@ main_sigchld_handler(int sig)
pid_t pid;
int status;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0 ||
(pid == -1 && errno == EINTR))
;
for (;;) {
if ((pid = waitpid(-1, &status, WNOHANG)) == 0)
break;
else if (pid == -1) {
if (errno == EINTR)
continue;
break;
}
child_exit(pid, status);
received_sigchld = 1;
}
errno = save_errno;
}
@ -290,7 +569,7 @@ should_drop_connection(int startups)
}
/*
* Check whether connection should be accepted by MaxStartups.
* Check whether connection should be accepted by MaxStartups or for penalty.
* Returns 0 if the connection is accepted. If the connection is refused,
* returns 1 and attempts to send notification to client.
* Logs when the MaxStartups condition is entered or exited, and periodically
@ -300,12 +579,17 @@ static int
drop_connection(int sock, int startups, int notify_pipe)
{
char *laddr, *raddr;
const char msg[] = "Exceeded MaxStartups\r\n";
const char *reason = NULL, msg[] = "Not allowed at this time\r\n";
static time_t last_drop, first_drop;
static u_int ndropped;
LogLevel drop_level = SYSLOG_LEVEL_VERBOSE;
time_t now;
if (!srclimit_penalty_check_allow(sock, &reason)) {
drop_level = SYSLOG_LEVEL_INFO;
goto handle;
}
now = monotime();
if (!should_drop_connection(startups) &&
srclimit_check_allow(sock, notify_pipe) == 1) {
@ -335,12 +619,16 @@ drop_connection(int sock, int startups, int notify_pipe)
}
last_drop = now;
ndropped++;
reason = "past Maxstartups";
handle:
laddr = get_local_ipaddr(sock);
raddr = get_peer_ipaddr(sock);
do_log2(drop_level, "drop connection #%d from [%s]:%d on [%s]:%d "
"past MaxStartups", startups, raddr, get_peer_port(sock),
laddr, get_local_port(sock));
do_log2(drop_level, "drop connection #%d from [%s]:%d on [%s]:%d %s",
startups,
raddr, get_peer_port(sock),
laddr, get_local_port(sock),
reason);
free(laddr);
free(raddr);
/* best-effort notification to client */
@ -547,8 +835,12 @@ 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);
srclimit_init(options.max_startups,
options.per_source_max_startups,
options.per_source_masklen_ipv4,
options.per_source_masklen_ipv6,
&options.per_source_penalty,
options.per_source_penalty_exempt);
for (i = 0; i < options.num_listen_addrs; i++) {
listen_on_addrs(&options.listen_addrs[i]);
@ -574,32 +866,32 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s,
int log_stderr)
{
struct pollfd *pfd = NULL;
int i, j, ret, npfd;
int ostartups = -1, startups = 0, listening = 0, lameduck = 0;
int i, ret, npfd;
int oactive = -1, listening = 0, lameduck = 0;
int startup_p[2] = { -1 , -1 }, *startup_pollfd;
char c = 0;
struct sockaddr_storage from;
struct early_child *child;
socklen_t fromlen;
pid_t pid;
u_char rnd[256];
sigset_t nsigset, osigset;
/* pipes connected to unauthenticated child sshd processes */
startup_pipes = xcalloc(options.max_startups, sizeof(int));
startup_flags = xcalloc(options.max_startups, sizeof(int));
child_alloc();
startup_pollfd = xcalloc(options.max_startups, sizeof(int));
for (i = 0; i < options.max_startups; i++)
startup_pipes[i] = -1;
/*
* Prepare signal mask that we use to block signals that might set
* received_sigterm or received_sighup, so that we are guaranteed
* received_sigterm/hup/chld/info, so that we are guaranteed
* to immediately wake up the ppoll if a signal is received after
* the flag is checked.
*/
sigemptyset(&nsigset);
sigaddset(&nsigset, SIGHUP);
sigaddset(&nsigset, SIGCHLD);
#ifdef SIGINFO
sigaddset(&nsigset, SIGINFO);
#endif
sigaddset(&nsigset, SIGTERM);
sigaddset(&nsigset, SIGQUIT);
@ -621,11 +913,19 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s,
unlink(options.pid_file);
exit(received_sigterm == SIGTERM ? 0 : 255);
}
if (ostartups != startups) {
if (received_sigchld) {
child_reap_all_exited();
received_sigchld = 0;
}
if (received_siginfo) {
show_info();
received_siginfo = 0;
}
if (oactive != children_active) {
setproctitle("%s [listener] %d of %d-%d startups",
listener_proctitle, startups,
listener_proctitle, children_active,
options.max_startups_begin, options.max_startups);
ostartups = startups;
oactive = children_active;
}
if (received_sighup) {
if (!lameduck) {
@ -646,8 +946,8 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s,
npfd = num_listen_socks;
for (i = 0; i < options.max_startups; i++) {
startup_pollfd[i] = -1;
if (startup_pipes[i] != -1) {
pfd[npfd].fd = startup_pipes[i];
if (children[i].pipefd != -1) {
pfd[npfd].fd = children[i].pipefd;
pfd[npfd].events = POLLIN;
startup_pollfd[i] = npfd++;
}
@ -665,34 +965,46 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s,
continue;
for (i = 0; i < options.max_startups; i++) {
if (startup_pipes[i] == -1 ||
if (children[i].pipefd == -1 ||
startup_pollfd[i] == -1 ||
!(pfd[startup_pollfd[i]].revents & (POLLIN|POLLHUP)))
continue;
switch (read(startup_pipes[i], &c, sizeof(c))) {
switch (read(children[i].pipefd, &c, sizeof(c))) {
case -1:
if (errno == EINTR || errno == EAGAIN)
continue;
if (errno != EPIPE) {
error_f("startup pipe %d (fd=%d): "
"read %s", i, startup_pipes[i],
"read %s", i, children[i].pipefd,
strerror(errno));
}
/* FALLTHROUGH */
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])
/* child exited preauth */
if (children[i].early)
listening--;
srclimit_done(children[i].pipefd);
child_close(&(children[i]), 0, 0);
break;
case 1:
/* child has finished preliminaries */
if (startup_flags[i]) {
if (children[i].early && c == '\0') {
/* child has finished preliminaries */
listening--;
startup_flags[i] = 0;
children[i].early = 0;
debug2_f("child %lu for %s received "
"config", (long)children[i].pid,
children[i].id);
} else if (!children[i].early && c == '\001') {
/* child has completed auth */
debug2_f("child %lu for %s auth done",
(long)children[i].pid,
children[i].id);
child_close(&(children[i]), 1, 0);
} else {
error_f("unexpected message 0x%02x "
"child %ld for %s in state %d",
(int)c, (long)children[i].pid,
children[i].id, children[i].early);
}
break;
}
@ -721,7 +1033,8 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s,
close(*newsock);
continue;
}
if (drop_connection(*newsock, startups, startup_p[0])) {
if (drop_connection(*newsock,
children_active, startup_p[0])) {
close(*newsock);
close(startup_p[0]);
close(startup_p[1]);
@ -738,14 +1051,6 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s,
continue;
}
for (j = 0; j < options.max_startups; j++)
if (startup_pipes[j] == -1) {
startup_pipes[j] = startup_p[0];
startups++;
startup_flags[j] = 1;
break;
}
/*
* Got connection. Fork a child to handle it, unless
* we are in debugging mode.
@ -763,7 +1068,6 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s,
close(startup_p[0]);
close(startup_p[1]);
startup_pipe = -1;
pid = getpid();
send_rexec_state(config_s[0], cfg);
close(config_s[0]);
free(pfd);
@ -777,7 +1081,8 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s,
*/
platform_pre_fork();
listening++;
if ((pid = fork()) == 0) {
child = child_register(startup_p[0], *newsock);
if ((child->pid = fork()) == 0) {
/*
* Child. Close the listening and
* max_startup sockets. Start using
@ -802,11 +1107,11 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s,
}
/* Parent. Stay in the loop. */
platform_post_fork_parent(pid);
if (pid == -1)
platform_post_fork_parent(child->pid);
if (child->pid == -1)
error("fork: %.100s", strerror(errno));
else
debug("Forked child %ld.", (long)pid);
debug("Forked child %ld.", (long)child->pid);
close(startup_p[1]);
@ -1428,6 +1733,9 @@ main(int ac, char **av)
ssh_signal(SIGCHLD, main_sigchld_handler);
ssh_signal(SIGTERM, sigterm_handler);
ssh_signal(SIGQUIT, sigterm_handler);
#ifdef SIGINFO
ssh_signal(SIGINFO, siginfo_handler);
#endif
platform_post_listen();

View 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.355 2024/02/21 06:17:29 djm Exp $
.Dd $Mdocdate: February 21 2024 $
.\" $OpenBSD: sshd_config.5,v 1.356 2024/06/06 17:15:25 djm Exp $
.Dd $Mdocdate: June 6 2024 $
.Dt SSHD_CONFIG 5
.Os
.Sh NAME
@ -1557,6 +1557,68 @@ 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 PerSourcePenalties
Controls penalties for various conditions that may represent attacks on
.Xr sshd 8 .
If a penalty is enforced against a client then its source address and any
others in the
.Cm PerSourceNetBlockSize
will be refused connection for a period.
Multiple penalties from the same source from concurrent connections will
accumulate up to a maximum.
Conversely, penalties are not applied until a minimum threshold time has been
accumulated.
Penalties are off by default but may be enabled using default settings using the
.Cm yes
keyword or by specifying one or more of the keywords below.
.Pp
Penalties are controlled using the following keywords, all of which accept
arguments, e.g.
.Qq crash:2m .
.Bl -tag -width Ds
.It Cm crash:duration
Specifies how long to refuse clients that cause a crash of
.Xr sshd 8 .
.It Cm authfail:duration
Specifies how long to refuse clients that disconnect after making one or more
unsuccessful authentication attempts.
.It Cm noauth:duration
Specifies how long to refuse clients that disconnect without attempting
authentication.
This timeout should be used cautiously otherwise it may penalise legitimate
scanning tools such as
.Xr ssh-keyscan 1 .
.It Cm grace-exceeded:duration
Specifies how long to refuse clients that fail to authenticate after
.Cm LoginGraceTime .
.It Cm max:duration
Specifies the maximum time a particular source address range will be refused
access for.
Repeated penalties will accumulate up to this maximum.
.It Cm min:duration
Specifies the minimum penalty that must accrue before enforcement begins.
.It Cm max-sources:number
Specifies the maximum number of penalise client address ranges to track.
.It Cm overflow:mode
Controls how the server behaves when
.Cm max-sources
is exceeded.
There are two operating modes:
.Cm deny-all ,
which denies all incoming connections other than those exempted via
.Cm PerSourcePenaltyExemptList
until a penalty expires, and
.Cm permissive ,
which allows new connections by removing existing penalties early.
.El
.It Cm PerSourcePenaltyExemptList
Specifies a comma-separated list of addresses to exempt from penalties.
This list may contain wildcards and CIDR address/masklen ranges.
Note that the mask length provided must be consistent with the address -
it is an error to specify a mask length that is too long for the address
or one with bits set in this host portion of the address.
For example, 192.0.2.0/33 and 192.0.2.0/8, respectively.
The default is not to exempt any addresses.
.It Cm PidFile
Specifies the file that contains the process ID of the
SSH daemon, or