[openssh-commits] [openssh] 01/04: upstream: make reading server banner non-blocking; prevents one
git+noreply at mindrot.org
git+noreply at mindrot.org
Tue Aug 11 09:30:16 AEST 2026
This is an automated email from the git hooks/post-receive script.
djm pushed a commit to branch master
in repository openssh.
commit 407a2b79d439b1272879a910db6c94d1165075a8
Author: djm at openbsd.org <djm at openbsd.org>
AuthorDate: Mon Aug 10 23:24:03 2026 +0000
upstream: make reading server banner non-blocking; prevents one
stuck server from blocking a many-host keyscan; from Thomas Yiu
ok dtucker@ markus@
OpenBSD-Commit-ID: 4970af00975119ebf9c1e0e132e3317b72ac0c2e
---
ssh-keyscan.c | 98 +++++++++++++++++++++++++++++++++--------------------------
1 file changed, 55 insertions(+), 43 deletions(-)
diff --git a/ssh-keyscan.c b/ssh-keyscan.c
index 8f52f7e6e..f093b0108 100644
--- a/ssh-keyscan.c
+++ b/ssh-keyscan.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-keyscan.c,v 1.169 2026/07/27 12:28:52 markus Exp $ */
+/* $OpenBSD: ssh-keyscan.c,v 1.170 2026/08/10 23:24:03 djm Exp $ */
/*
* Copyright 1995, 1996 by David Mazieres <dm at lcs.mit.edu>.
*
@@ -110,6 +110,9 @@ typedef struct Connection {
char *c_namelist; /* Pointer to other possible addresses */
char *c_output_name; /* Hostname of connection for output */
struct ssh *c_ssh; /* SSH-connection */
+ char c_banner[256]; /* Partial server greeting */
+ size_t c_banner_len; /* Length of partial server greeting */
+ int c_banner_sent; /* Client greeting has been sent */
struct timespec c_ts; /* Time at which connection gets aborted */
TAILQ_ENTRY(Connection) c_link; /* List of connections in timeout order. */
} con;
@@ -413,6 +416,8 @@ conalloc(const char *iname, const char *oname, int keytype)
fdcon[s].c_namelist = namelist;
fdcon[s].c_output_name = xstrdup(oname);
fdcon[s].c_keytype = keytype;
+ fdcon[s].c_banner_len = 0;
+ fdcon[s].c_banner_sent = 0;
monotime_ts(&fdcon[s].c_ts);
fdcon[s].c_ts.tv_sec += timeout;
TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link);
@@ -457,52 +462,48 @@ conrecycle(int s)
static void
congreet(int s)
{
- int n = 0, remote_major = 0, remote_minor = 0;
- char buf[256], *cp;
+ int remote_major = 0, remote_minor = 0;
+ ssize_t n;
+ char buf[256], ch;
char remote_version[sizeof buf];
- size_t bufsiz;
con *c = &fdcon[s];
- /* send client banner */
- n = snprintf(buf, sizeof buf, "SSH-%d.%d-OpenSSH-keyscan\r\n",
- PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2);
- if (n < 0 || (size_t)n >= sizeof(buf)) {
- error("snprintf: buffer too small");
- confree(s);
- return;
- }
- if (atomicio(vwrite, s, buf, n) != (size_t)n) {
- error("write (%s): %s", c->c_name, strerror(errno));
- confree(s);
- return;
+ if (!c->c_banner_sent) {
+ /* send client banner */
+ n = snprintf(buf, sizeof buf, "SSH-%d.%d-OpenSSH-keyscan\r\n",
+ PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2);
+ if (n < 0 || (size_t)n >= sizeof(buf)) {
+ error("snprintf: buffer too small");
+ confree(s);
+ return;
+ }
+ if (atomicio(vwrite, s, buf, n) != (size_t)n) {
+ error("write (%s): %s", c->c_name, strerror(errno));
+ confree(s);
+ return;
+ }
+ c->c_banner_sent = 1;
}
/*
* Read the server banner as per RFC4253 section 4.2. The "SSH-"
* protocol identification string may be preceded by an arbitrarily
- * large banner which we must read and ignore. Loop while reading
- * newline-terminated lines until we have one starting with "SSH-".
- * The ID string cannot be longer than 255 characters although the
- * preceding banner lines may (in which case they'll be discarded
- * in multiple iterations of the outer loop).
+ * large banner which we must read and ignore. Read a single byte
+ * at a time so that the event loop retains control of connection
+ * timeouts. Partial lines are retained in the connection state
+ * between calls.
*/
- for (;;) {
- memset(buf, '\0', sizeof(buf));
- bufsiz = sizeof(buf);
- cp = buf;
- while (bufsiz-- &&
- (n = atomicio(read, s, cp, 1)) == 1 && *cp != '\n') {
- if (*cp == '\r')
- *cp = '\n';
- cp++;
- }
- if (n != 1 || strncmp(buf, "SSH-", 4) == 0)
- break;
- }
- if (n == 0) {
+ n = read(s, &ch, sizeof(ch));
+ if (n == -1 && (errno == EAGAIN || errno == EWOULDBLOCK ||
+ errno == EINTR))
+ return;
+ if (n != 1) {
+ if (n == 0)
+ errno = EPIPE;
switch (errno) {
case EPIPE:
- error("%s: Connection closed by remote host", c->c_name);
+ error("%s: Connection closed by remote host",
+ c->c_name);
break;
case ECONNREFUSED:
break;
@@ -513,17 +514,28 @@ congreet(int s)
conrecycle(s);
return;
}
- if (cp >= buf + sizeof(buf)) {
- error("%s: greeting exceeds allowable length", c->c_name);
- confree(s);
+ c->c_banner[c->c_banner_len++] = ch == '\r' ? '\n' : ch;
+ if (ch != '\n') {
+ if (c->c_banner_len < sizeof(c->c_banner) - 1)
+ return; /* incomplete line */
+ if (strncmp(c->c_banner, "SSH-", 4) == 0) {
+ /* banner exceeds RFC 4253 s4.2 length limit */
+ error("%s: greeting exceeds allowable length",
+ c->c_name);
+ confree(s);
+ return;
+ }
+ /* Discard an oversized pre-identification banner chunk. */
+ c->c_banner_len = 0;
return;
}
- if (*cp != '\n' && *cp != '\r') {
- error("%s: bad greeting", c->c_name);
- confree(s);
+ if (strncmp(c->c_banner, "SSH-", 4) != 0) {
+ /* Ignore non-banner lines */
+ c->c_banner_len = 0;
return;
}
- *cp = '\0';
+ c->c_banner[c->c_banner_len++] = '\0';
+ memcpy(buf, c->c_banner, c->c_banner_len);
if ((c->c_ssh = ssh_packet_set_connection(NULL, s, s)) == NULL)
fatal("ssh_packet_set_connection failed");
ssh_packet_set_timeout(c->c_ssh, timeout, 1);
--
To stop receiving notification emails like this one, please contact
djm at mindrot.org.
More information about the openssh-commits
mailing list