Do not call poll(2) or ppoll(2) with nfds > RLIMIT_NOFILE.

Both poll(2) and ppoll(2) will return EINVAL if the nfds function
argument is larger than the max files per process resource limit.
Prevent this by limiting the max number entries in the pfds[] array
to the RLIMIT_NOFILE soft limit.
This commit is contained in:
Todd C. Miller
2020-04-30 15:54:34 -06:00
parent 85fe30e49b
commit 2b1e986572

View File

@@ -24,6 +24,7 @@
#include <config.h> #include <config.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/resource.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#ifdef HAVE_STDBOOL_H #ifdef HAVE_STDBOOL_H
@@ -81,29 +82,49 @@ sudo_ev_base_free_impl(struct sudo_event_base *base)
int int
sudo_ev_add_impl(struct sudo_event_base *base, struct sudo_event *ev) sudo_ev_add_impl(struct sudo_event_base *base, struct sudo_event *ev)
{ {
static int nofile_max = -1;
struct pollfd *pfd; struct pollfd *pfd;
debug_decl(sudo_ev_add_impl, SUDO_DEBUG_EVENT); debug_decl(sudo_ev_add_impl, SUDO_DEBUG_EVENT);
if (nofile_max == -1) {
struct rlimit rlim;
if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) {
nofile_max = rlim.rlim_cur;
}
}
/* If out of space in pfds array, realloc. */ /* If out of space in pfds array, realloc. */
if (base->pfd_free == base->pfd_max) { if (base->pfd_free == base->pfd_max) {
struct pollfd *pfds; struct pollfd *pfds;
int i; int i, new_max;
pfds = /* Don't allow pfd_max to go over RLIM_NOFILE */
reallocarray(base->pfds, base->pfd_max, 2 * sizeof(struct pollfd)); new_max = base->pfd_max * 2;
if (new_max > nofile_max)
new_max = nofile_max;
if (base->pfd_free == new_max) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"%s: out of fds (max %d)", __func__, nofile_max);
debug_return_int(-1);
}
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
"%s: pfd_max %d -> %d", __func__, base->pfd_max, new_max);
pfds = reallocarray(base->pfds, new_max, sizeof(struct pollfd));
if (pfds == NULL) { if (pfds == NULL) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"%s: unable to allocate %d pollfds", __func__, base->pfd_max * 2); "%s: unable to allocate %d pollfds", __func__, new_max);
debug_return_int(-1); debug_return_int(-1);
} }
base->pfds = pfds; base->pfds = pfds;
base->pfd_max *= 2; base->pfd_max = new_max;
for (i = base->pfd_free; i < base->pfd_max; i++) { for (i = base->pfd_free; i < base->pfd_max; i++) {
base->pfds[i].fd = -1; base->pfds[i].fd = -1;
} }
} }
/* Fill in pfd entry. */ /* Fill in pfd entry. */
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"%s: choosing free slot %d", __func__, base->pfd_free);
ev->pfd_idx = base->pfd_free; ev->pfd_idx = base->pfd_free;
pfd = &base->pfds[ev->pfd_idx]; pfd = &base->pfds[ev->pfd_idx];
pfd->fd = ev->fd; pfd->fd = ev->fd;
@@ -133,8 +154,11 @@ sudo_ev_del_impl(struct sudo_event_base *base, struct sudo_event *ev)
/* Mark pfd entry unused, add to free list and adjust high slot. */ /* Mark pfd entry unused, add to free list and adjust high slot. */
base->pfds[ev->pfd_idx].fd = -1; base->pfds[ev->pfd_idx].fd = -1;
if (ev->pfd_idx < base->pfd_free) if (ev->pfd_idx < base->pfd_free) {
base->pfd_free = ev->pfd_idx; base->pfd_free = ev->pfd_idx;
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"%s: new free slot %d", __func__, base->pfd_free);
}
while (base->pfd_high >= 0 && base->pfds[base->pfd_high].fd == -1) while (base->pfd_high >= 0 && base->pfds[base->pfd_high].fd == -1)
base->pfd_high--; base->pfd_high--;
@@ -182,16 +206,20 @@ sudo_ev_scan_impl(struct sudo_event_base *base, int flags)
} }
nready = sudo_ev_poll(base->pfds, base->pfd_high + 1, timeout); nready = sudo_ev_poll(base->pfds, base->pfd_high + 1, timeout);
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d fds ready", __func__, nready);
switch (nready) { switch (nready) {
case -1: case -1:
/* Error or interrupted by signal. */ /* Error: EINTR (signal) or EINVAL (nfds > RLIMIT_NOFILE) */
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"sudo_ev_poll");
debug_return_int(-1); debug_return_int(-1);
case 0: case 0:
/* Front end will activate timeout events. */ /* Front end will activate timeout events. */
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: timeout", __func__);
break; break;
default: default:
/* Activate each I/O event that fired. */ /* Activate each I/O event that fired. */
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d fds ready", __func__,
nready);
TAILQ_FOREACH(ev, &base->events, entries) { TAILQ_FOREACH(ev, &base->events, entries) {
if (ev->pfd_idx != -1 && base->pfds[ev->pfd_idx].revents) { if (ev->pfd_idx != -1 && base->pfds[ev->pfd_idx].revents) {
int what = 0; int what = 0;