upstream: implement EMFILE mitigation for ssh-agent: remember the

fd rlimit and stop accepting new connections when it is exceeded (with some
grace). Accept is resumed when enough connections are closed.

bz#2576. feedback deraadt; ok dtucker@

OpenBSD-Commit-ID: 6a85d9cec7b85741961e7116a49f8dae777911ea
This commit is contained in:
djm@openbsd.org 2018-05-11 03:38:51 +00:00 committed by Damien Miller
parent fdba503fdf
commit b2140a739b

View File

@ -1,4 +1,4 @@
/* $OpenBSD: ssh-agent.c,v 1.230 2018/04/10 00:10:49 djm Exp $ */ /* $OpenBSD: ssh-agent.c,v 1.231 2018/05/11 03:38:51 djm Exp $ */
/* /*
* Author: Tatu Ylonen <ylo@cs.hut.fi> * Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -886,10 +886,10 @@ handle_conn_write(u_int socknum)
} }
static void static void
after_poll(struct pollfd *pfd, size_t npfd) after_poll(struct pollfd *pfd, size_t npfd, u_int maxfds)
{ {
size_t i; size_t i;
u_int socknum; u_int socknum, activefds = npfd;
for (i = 0; i < npfd; i++) { for (i = 0; i < npfd; i++) {
if (pfd[i].revents == 0) if (pfd[i].revents == 0)
@ -909,18 +909,30 @@ after_poll(struct pollfd *pfd, size_t npfd)
/* Process events */ /* Process events */
switch (sockets[socknum].type) { switch (sockets[socknum].type) {
case AUTH_SOCKET: case AUTH_SOCKET:
if ((pfd[i].revents & (POLLIN|POLLERR)) != 0) if ((pfd[i].revents & (POLLIN|POLLERR)) == 0)
handle_socket_read(socknum); break;
if (npfd > maxfds) {
debug3("out of fds (active %u >= limit %u); "
"skipping accept", activefds, maxfds);
break;
}
if (handle_socket_read(socknum) == 0)
activefds++;
break; break;
case AUTH_CONNECTION: case AUTH_CONNECTION:
if ((pfd[i].revents & (POLLIN|POLLERR)) != 0 && if ((pfd[i].revents & (POLLIN|POLLERR)) != 0 &&
handle_conn_read(socknum) != 0) { handle_conn_read(socknum) != 0) {
close_socket(&sockets[socknum]); goto close_sock;
break;
} }
if ((pfd[i].revents & (POLLOUT|POLLHUP)) != 0 && if ((pfd[i].revents & (POLLOUT|POLLHUP)) != 0 &&
handle_conn_write(socknum) != 0) handle_conn_write(socknum) != 0) {
close_sock:
if (activefds == 0)
fatal("activefds == 0 at close_sock");
close_socket(&sockets[socknum]); close_socket(&sockets[socknum]);
activefds--;
break;
}
break; break;
default: default:
break; break;
@ -929,7 +941,7 @@ after_poll(struct pollfd *pfd, size_t npfd)
} }
static int static int
prepare_poll(struct pollfd **pfdp, size_t *npfdp, int *timeoutp) prepare_poll(struct pollfd **pfdp, size_t *npfdp, int *timeoutp, u_int maxfds)
{ {
struct pollfd *pfd = *pfdp; struct pollfd *pfd = *pfdp;
size_t i, j, npfd = 0; size_t i, j, npfd = 0;
@ -958,6 +970,16 @@ prepare_poll(struct pollfd **pfdp, size_t *npfdp, int *timeoutp)
for (i = j = 0; i < sockets_alloc; i++) { for (i = j = 0; i < sockets_alloc; i++) {
switch (sockets[i].type) { switch (sockets[i].type) {
case AUTH_SOCKET: case AUTH_SOCKET:
if (npfd > maxfds) {
debug3("out of fds (active %zu >= limit %u); "
"skipping arming listener", npfd, maxfds);
break;
}
pfd[j].fd = sockets[i].fd;
pfd[j].revents = 0;
pfd[j].events = POLLIN;
j++;
break;
case AUTH_CONNECTION: case AUTH_CONNECTION:
pfd[j].fd = sockets[i].fd; pfd[j].fd = sockets[i].fd;
pfd[j].revents = 0; pfd[j].revents = 0;
@ -1058,6 +1080,7 @@ main(int ac, char **av)
int timeout = -1; /* INFTIM */ int timeout = -1; /* INFTIM */
struct pollfd *pfd = NULL; struct pollfd *pfd = NULL;
size_t npfd = 0; size_t npfd = 0;
u_int maxfds;
ssh_malloc_init(); /* must be called before any mallocs */ ssh_malloc_init(); /* must be called before any mallocs */
/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
@ -1069,6 +1092,9 @@ main(int ac, char **av)
platform_disable_tracing(0); /* strict=no */ platform_disable_tracing(0); /* strict=no */
if (getrlimit(RLIMIT_NOFILE, &rlim) == -1)
fatal("%s: getrlimit: %s", __progname, strerror(errno));
#ifdef WITH_OPENSSL #ifdef WITH_OPENSSL
OpenSSL_add_all_algorithms(); OpenSSL_add_all_algorithms();
#endif #endif
@ -1165,6 +1191,18 @@ main(int ac, char **av)
printf("echo Agent pid %ld killed;\n", (long)pid); printf("echo Agent pid %ld killed;\n", (long)pid);
exit(0); exit(0);
} }
/*
* Minimum file descriptors:
* stdio (3) + listener (1) + syslog (1 maybe) + connection (1) +
* a few spare for libc / stack protectors / sanitisers, etc.
*/
#define SSH_AGENT_MIN_FDS (3+1+1+1+4)
if (rlim.rlim_cur < SSH_AGENT_MIN_FDS)
fatal("%s: file descriptior rlimit %lld too low (minimum %u)",
__progname, (long long)rlim.rlim_cur, SSH_AGENT_MIN_FDS);
maxfds = rlim.rlim_cur - SSH_AGENT_MIN_FDS;
parent_pid = getpid(); parent_pid = getpid();
if (agentsocket == NULL) { if (agentsocket == NULL) {
@ -1284,7 +1322,7 @@ skip:
platform_pledge_agent(); platform_pledge_agent();
while (1) { while (1) {
prepare_poll(&pfd, &npfd, &timeout); prepare_poll(&pfd, &npfd, &timeout, maxfds);
result = poll(pfd, npfd, timeout); result = poll(pfd, npfd, timeout);
saved_errno = errno; saved_errno = errno;
if (parent_alive_interval != 0) if (parent_alive_interval != 0)
@ -1295,7 +1333,7 @@ skip:
continue; continue;
fatal("poll: %s", strerror(saved_errno)); fatal("poll: %s", strerror(saved_errno));
} else if (result > 0) } else if (result > 0)
after_poll(pfd, npfd); after_poll(pfd, npfd, maxfds);
} }
/* NOTREACHED */ /* NOTREACHED */
} }