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 <sys/types.h>
#include <sys/resource.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STDBOOL_H
@@ -81,29 +82,49 @@ sudo_ev_base_free_impl(struct sudo_event_base *base)
int
sudo_ev_add_impl(struct sudo_event_base *base, struct sudo_event *ev)
{
static int nofile_max = -1;
struct pollfd *pfd;
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 (base->pfd_free == base->pfd_max) {
struct pollfd *pfds;
int i;
int i, new_max;
pfds =
reallocarray(base->pfds, base->pfd_max, 2 * sizeof(struct pollfd));
/* Don't allow pfd_max to go over RLIM_NOFILE */
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) {
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);
}
base->pfds = pfds;
base->pfd_max *= 2;
base->pfd_max = new_max;
for (i = base->pfd_free; i < base->pfd_max; i++) {
base->pfds[i].fd = -1;
}
}
/* 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;
pfd = &base->pfds[ev->pfd_idx];
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. */
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;
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)
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);
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d fds ready", __func__, nready);
switch (nready) {
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);
case 0:
/* Front end will activate timeout events. */
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: timeout", __func__);
break;
default:
/* 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) {
if (ev->pfd_idx != -1 && base->pfds[ev->pfd_idx].revents) {
int what = 0;