diff --git a/fix-cve-2024-6387.patch b/fix-cve-2024-6387.patch new file mode 100644 index 0000000000000000000000000000000000000000..7a231d7314b1e4380625a348606eb91d33639d34 --- /dev/null +++ b/fix-cve-2024-6387.patch @@ -0,0 +1,1545 @@ +From 905456164e1337de04138d28b8badaa0e68d05dd Mon Sep 17 00:00:00 2001 +From: yangxinyu +Date: Wed, 31 Jul 2024 17:20:42 +0800 +Subject: [PATCH] fix-cve-2024-6387 + +--- + .depend | 6 +- + misc.c | 16 ++ + misc.h | 1 + + monitor.c | 6 + + monitor_wrap.c | 33 ++++ + servconf.c | 134 +++++++++++++++- + servconf.h | 16 ++ + srclimit.c | 321 +++++++++++++++++++++++++++++++++++-- + srclimit.h | 22 ++- + sshd.c | 427 ++++++++++++++++++++++++++++++++++++++++++------- + sshd_config.5 | 62 +++++++ + 11 files changed, 964 insertions(+), 80 deletions(-) + +diff --git a/.depend b/.depend +index 259bf3b..8536880 100644 +--- a/.depend ++++ b/.depend +@@ -81,7 +81,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 monitor_wrap.h atomicio.h monitor_fdpass.h misc.h channels.h session.h servconf.h ++monitor_wrap.o: loginrec.h auth-options.h packet.h dispatch.h log.h ssherr.h monitor.h monitor_wrap.h atomicio.h monitor_fdpass.h misc.h channels.h session.h servconf.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 +@@ -122,7 +122,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 addr.h canohost.h log.h ssherr.h misc.h srclimit.h xmalloc.h ./openbsd-compat/sys-tree.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 +@@ -157,7 +157,7 @@ sshconnect.o: kex.h mac.h crypto_api.h + sshconnect2.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 packet.h dispatch.h compat.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h sshkey.h kex.h mac.h crypto_api.h + 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.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 chacha.h +-sshd.o: 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.o: 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 addr.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 + sshkey.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 crypto_api.h ssh2.h ssherr.h misc.h sshbuf.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h digest.h sshkey.h match.h ssh-sk.h openbsd-compat/openssl-compat.h +diff --git a/misc.c b/misc.c +index 41c82a1..757b03c 100644 +--- a/misc.c ++++ b/misc.c +@@ -2931,3 +2931,19 @@ ptimeout_isset(struct timespec *pt) + { + return pt->tv_sec != -1; + } ++ ++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; ++} +diff --git a/misc.h b/misc.h +index f22e1a8..c00017d 100644 +--- a/misc.h ++++ b/misc.h +@@ -239,6 +239,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 */ +diff --git a/monitor.c b/monitor.c +index a26df8e..b67caae 100644 +--- a/monitor.c ++++ b/monitor.c +@@ -179,6 +179,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; +@@ -339,6 +340,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 && +@@ -398,6 +403,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); + +diff --git a/monitor_wrap.c b/monitor_wrap.c +index 1a3d442..0b7a7fb 100644 +--- a/monitor_wrap.c ++++ b/monitor_wrap.c +@@ -29,6 +29,7 @@ + + #include + #include ++#include + + #include + #include +@@ -73,6 +74,7 @@ + #include "channels.h" + #include "session.h" + #include "servconf.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) + { +@@ -148,6 +180,7 @@ mm_request_receive(int sock, struct sshbuf *m) + + if (atomicio(read, sock, buf, sizeof(buf)) != sizeof(buf)) { + if (errno == EPIPE) ++ mm_reap(); + cleanup_exit(255); + fatal_f("read: %s", strerror(errno)); + } +diff --git a/servconf.c b/servconf.c +index bc6fc20..8a82bae 100644 +--- a/servconf.c ++++ b/servconf.c +@@ -175,6 +175,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; +@@ -434,6 +444,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) +@@ -513,6 +541,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]); +@@ -547,6 +576,7 @@ typedef enum { + sBanner, sUseDNS, sHostbasedAuthentication, + sHostbasedUsesNameFromPacketOnly, sHostbasedAcceptedAlgorithms, + sHostKeyAlgorithms, sPerSourceMaxStartups, sPerSourceNetBlockSize, ++ sPerSourcePenalties, sPerSourcePenaltyExemptList, + sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, + sGssAuthentication, sGssCleanupCreds, sGssEnablek5users, sGssStrictAcceptor, + sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey, +@@ -696,6 +726,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 }, +@@ -2104,6 +2136,89 @@ process_server_config_line_depth(ServerOptions *options, char *line, + if (*activep) + 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; +@@ -3177,7 +3292,8 @@ dump_config(ServerOptions *o) + #if defined(__OpenBSD__) || defined(HAVE_SYS_SET_PROCESS_RDOMAIN) + dump_cfg_string(sRDomain, o->routing_domain); + #endif +- ++ ++ dump_cfg_string(sPerSourcePenaltyExemptList, o->per_source_penalty_exempt); + /* string arguments requiring a lookup */ + dump_cfg_string(sLogLevel, log_level_name(o->log_level)); + dump_cfg_string(sLogFacility, log_facility_name(o->log_facility)); +@@ -3264,4 +3380,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"); + } +diff --git a/servconf.h b/servconf.h +index ccc0181..5ad3851 100644 +--- a/servconf.h ++++ b/servconf.h +@@ -74,6 +74,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; +@@ -189,6 +203,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 */ +diff --git a/srclimit.c b/srclimit.c +index 5014ed7..1e1732f 100644 +--- a/srclimit.c ++++ b/srclimit.c +@@ -18,11 +18,13 @@ + + #include + #include ++#include + + #include + #include + #include + #include ++#include + + #include "addr.h" + #include "canohost.h" +@@ -30,8 +32,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 +45,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 +104,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 +116,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 +132,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. */ ++ if (srclimit_peer_addr(sock, &xa) != 0) ++ return 1; + 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_mask_addr(&xa, bits, &xb) != 0) + return 1; +- } + + first_unused = max_children; + /* Count matching entries and find first unused one. */ +@@ -138,3 +189,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)); ++ } ++ } ++} +diff --git a/srclimit.h b/srclimit.h +index 6e04f32..74a6f2b 100644 +--- a/srclimit.h ++++ b/srclimit.h +@@ -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); +diff --git a/sshd.c b/sshd.c +index 34c5ae4..63ecae2 100644 +--- a/sshd.c ++++ b/sshd.c +@@ -132,6 +132,7 @@ + #include "version.h" + #include "ssherr.h" + #include "sk-api.h" ++#include "addr.h" + #include "srclimit.h" + #include "dh.h" + +@@ -212,6 +213,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; + +@@ -219,8 +222,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. +@@ -229,14 +233,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 */ + + /* variables used for privilege separation */ +@@ -284,6 +305,226 @@ 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])); ++ } ++} ++ + /* + * Is this process listening for clients (i.e. not specific to any specific + * client connection?) +@@ -298,10 +539,32 @@ 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(); + } + + /* +@@ -345,6 +608,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. +@@ -356,9 +627,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; + } + +@@ -908,7 +1187,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 +@@ -918,12 +1197,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) { +@@ -953,12 +1237,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 */ +@@ -1165,8 +1453,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]); +@@ -1191,32 +1483,32 @@ static void + server_accept_loop(struct ssh *ssh, int *sock_in, int *sock_out, int *newsock, int *config_s) + { + 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); + +@@ -1239,11 +1531,19 @@ server_accept_loop(struct ssh *ssh, int *sock_in, int *sock_out, int *newsock, i + 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) { +@@ -1264,8 +1564,8 @@ server_accept_loop(struct ssh *ssh, int *sock_in, int *sock_out, int *newsock, i + 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++; + } +@@ -1283,34 +1583,46 @@ server_accept_loop(struct ssh *ssh, int *sock_in, int *sock_out, int *newsock, i + 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; + } +@@ -1339,7 +1651,8 @@ server_accept_loop(struct ssh *ssh, int *sock_in, int *sock_out, int *newsock, i + 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]); +@@ -1356,14 +1669,6 @@ server_accept_loop(struct ssh *ssh, int *sock_in, int *sock_out, int *newsock, i + 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. +@@ -1381,7 +1686,6 @@ server_accept_loop(struct ssh *ssh, int *sock_in, int *sock_out, int *newsock, i + close(startup_p[0]); + close(startup_p[1]); + startup_pipe = -1; +- pid = getpid(); + if (rexec_flag) { + send_rexec_state(config_s[0], cfg); + close(config_s[0]); +@@ -1397,7 +1701,8 @@ server_accept_loop(struct ssh *ssh, int *sock_in, int *sock_out, int *newsock, i + */ + 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 +@@ -1434,11 +1739,11 @@ server_accept_loop(struct ssh *ssh, int *sock_in, int *sock_out, int *newsock, i + } + + /* 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]); + +@@ -1464,7 +1769,6 @@ server_accept_loop(struct ssh *ssh, int *sock_in, int *sock_out, int *newsock, i + } + } + } +- + /* + * If IP options are supported, make sure there are none (log and + * return an error if any are found). Basically we are worried about +@@ -2216,6 +2520,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 + + /* + * Write out the pid file after the sigterm handler +diff --git a/sshd_config.5 b/sshd_config.5 +index a4a9e46..795505b 100644 +--- a/sshd_config.5 ++++ b/sshd_config.5 +@@ -1563,6 +1563,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 +-- +2.33.0 + diff --git a/openssh.spec b/openssh.spec index 08dcdc07f0489e0a886b84c6beea5d99685b119f..9ed782231647ecc8d43a7f438ba06466a1405485 100644 --- a/openssh.spec +++ b/openssh.spec @@ -1,4 +1,4 @@ -%define anolis_release 2 +%define anolis_release 3 %global WITH_SELINUX 1 @@ -222,6 +222,9 @@ Patch1017: add-loongarch64-support-for-openssh.patch # Fix CVE-2024-6387 Patch1018: 1018-fix-CVE-2024-6387.patch +# https://github.com/openssh/openssh-portable/commit/81c1099d22b81ebfd20a334ce986c4f753b0db29 +Patch1019: fix-cve-2024-6387.patch + License: BSD-3-Clause AND BSD-2-Clause AND ISC AND SSH-OpenSSH AND ssh-keyscan AND sprintf AND LicenseRef-Fedora-Public-Domain AND X11-distribute-modifications-variant Requires: /sbin/nologin @@ -439,6 +442,7 @@ popd %patch -P 1017 -p1 # cve fix %patch -P 1018 -p1 +%patch -P 1019 -p1 autoreconf pushd pam_ssh_agent_auth-pam_ssh_agent_auth-%{pam_ssh_agent_ver} @@ -750,7 +754,10 @@ test -f %{sysconfig_anaconda} && \ %endif %changelog -* Tue July 2 2024 Chang Gao - 9.3p2-2 +* Wed Jul 31 2024 yangxinyu - 9.3p2-3 +- Fix CVE-2024-6387 + +* Tue Jul 2 2024 Chang Gao - 9.3p2-2 - Fix CVE-2024-6387 * Fri Mar 22 2024 mgb01105731 - 9.3p2-1