It appears that openssh has inherited the dos attack that ssh is
susceptible to. This has been discussed on Bugtraq (see
http://securityportal.com/list-archive/bugtraq/1999/Sep/0124.html
for the thread). There does not appear to be an official for ssh.
Attached below is a simple, proof of concept, patch that adds a
MaxConnections to sshd_config that sets the maximum number of
simultaneous connections sshd will allow. I was not careful to
verify that the changes don't have any side effects. I am posting
this here in the hopes that a good solution can be found that can
be passed on to the openbsd people.
There is a related problem I noticed when doing this. If I open a
bunch of connections to ssh (say in a perl script) and then close
them all at once only some of the children get reaped on the server.
The reason for this seems to be that in the SIGCHLD handler,
main_sigchld_handler, uses a wait call to reap one child. If you
have multiple children dying at roughly the same time some do not
get caught by the handler and thus not reaped. They remain as
zombies until sshd is restarted (at which time the get reaped). To
circumvent this I have replaced the wait call with a loop over
waitpid to reap all the children we can each time we hit the handler.
Again, there may be a better solution.
Craig
------------------------------------------------------------
--- openssh-1.2pre13/servconf.c.orig Fri Nov 19 23:30:33 1999
+++ openssh-1.2pre13/servconf.c Fri Nov 19 23:36:56 1999
@@ -62,6 +62,7 @@
options->num_deny_users = 0;
options->num_allow_groups = 0;
options->num_deny_groups = 0;
+ options->max_connections = -1;
}
void fill_default_server_options(ServerOptions *options)
@@ -161,7 +162,7 @@
sPrintMotd, sIgnoreRhosts, sX11Forwarding, sX11DisplayOffset,
sStrictModes, sEmptyPasswd, sRandomSeedFile, sKeepAlives, sCheckMail,
sUseLogin, sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups,
- sIgnoreUserKnownHosts
+ sIgnoreUserKnownHosts, sMaxConnections
} ServerOpCodes;
/* Textual representation of the tokens. */
@@ -211,6 +212,7 @@
{ "denyusers", sDenyUsers },
{ "allowgroups", sAllowGroups },
{ "denygroups", sDenyGroups },
+ { "maxconnections", sMaxConnections },
{ NULL, 0 }
};
@@ -587,6 +589,10 @@
options->deny_groups[options->num_deny_groups++] = xstrdup(cp);
}
break;
+
+ case sMaxConnections:
+ intptr = &options->max_connections;
+ goto parse_int;
default:
fprintf(stderr, "%s line %d: Missing handler for opcode %s
(%d)\n",
--- openssh-1.2pre13/servconf.h.orig Fri Nov 19 23:31:16 1999
+++ openssh-1.2pre13/servconf.h Fri Nov 19 23:32:08 1999
@@ -70,6 +70,7 @@
char *allow_groups[MAX_ALLOW_GROUPS];
unsigned int num_deny_groups;
char *deny_groups[MAX_DENY_GROUPS];
+ int max_connections; /* Maximum number of simultaneous connections.
*/
} ServerOptions;
/* Initializes the server options to special values that indicate that they
--- openssh-1.2pre13/sshd.c.orig Fri Nov 19 21:00:51 1999
+++ openssh-1.2pre13/sshd.c Sat Nov 20 00:01:48 1999
@@ -117,6 +117,9 @@
the private key. */
RSA *public_key;
+/* Number of connections open at present. */
+int current_connections = 0;
+
/* Prototypes for various functions defined later in this file. */
void do_connection();
void do_authentication(char *user);
@@ -316,7 +319,12 @@
{
int save_errno = errno;
int status;
- wait(&status);
+
+ /* Reap all the children that are dead. */
+ while (waitpid (0, &status, WNOHANG) > 0) {
+ if (current_connections > 0) --current_connections;
+ }
+
signal(SIGCHLD, main_sigchld_handler);
errno = save_errno;
}
@@ -687,27 +695,36 @@
}
else
{
- /* Normal production daemon. Fork, and have the child process
- the connection. The parent continues listening. */
- if ((pid = fork()) == 0)
- {
- /* Child. Close the listening socket, and start using
- the accepted socket. Reinitialize logging (since our
- pid has changed). We break out of the loop to handle
- the connection. */
- close(listen_sock);
- sock_in = newsock;
- sock_out = newsock;
- log_init(av0, options.log_level, options.log_facility,
log_stderr);
- break;
+ /* Make sure we don't have too many connections. */
+ if (options.max_connections > 0
+ && current_connections >= options.max_connections)
+ error ("Maximum number of connections (%d) reached",
+ options.max_connections);
+ else {
+ /* Normal production daemon. Fork, and have the child process
+ the connection. The parent continues listening. */
+ if ((pid = fork()) == 0)
+ {
+ /* Child. Close the listening socket, and start using
+ the accepted socket. Reinitialize logging (since our
+ pid has changed). We break out of the loop to handle
+ the connection. */
+ close(listen_sock);
+ sock_in = newsock;
+ sock_out = newsock;
+ log_init(av0, options.log_level, options.log_facility, log_stderr);
+ break;
+ }
+
+ /* Parent. Stay in the loop. */
+ if (pid < 0)
+ error("fork: %.100s", strerror(errno));
+ else {
+ debug("Forked child %d.", pid);
+ ++current_connections;
}
+ }
}
-
- /* Parent. Stay in the loop. */
- if (pid < 0)
- error("fork: %.100s", strerror(errno));
- else
- debug("Forked child %d.", pid);
/* Mark that the key has been used (it was "given" to the child).
*/
key_used = 1;