Skip to content

php_readpipe() calls FD_SET without an FD_SETSIZE check, overflowing fd_set on a busy poller #605

Description

@somethingwithproof

php_readpipe() builds an fd_set from the script server's read descriptor without checking it against FD_SETSIZE:

php.c:196 (develop):

FD_ZERO(&fds);
FD_SET(php_processes[php_process].php_read_fd, &fds);

switch (select(php_processes[php_process].php_read_fd + 1, &fds, NULL, NULL, &timeout)) {

FD_SET does not range check. A descriptor at or above FD_SETSIZE (1024 on glibc) sets a bit past the end of the fd_set, which lives on php_readpipe()'s stack. Where the C library implements the __FD_SETSIZE fortification check the process aborts instead; where it does not, this is a stack write at an attacker-influenced offset in a binary that is installed setuid root.

Observed while adding tests for this file. With the descriptor set above the limit the whole test binary dies:

*** bit out of range 0 - FD_SETSIZE on fd_set ***: terminated

ping_icmp() already guards its own select() the same way, at ping.c:478, so the pattern is established in the tree:

if (icmp_socket >= FD_SETSIZE) {
    SPINE_LOG(("ERROR: Device[%i] ICMP socket %d exceeds FD_SETSIZE %d", host->id, icmp_socket, FD_SETSIZE));
    ...
}

On reachability: the script server pipes are created by pipe() at poller startup, so on a small installation they sit low. Two things push them up. Cacti's own sizing guidance has operators raise RLIMIT_NOFILE well past 1024, and spine is thread-per-device with SNMP sockets, database connections and script pipes open concurrently. php_readpipe() also restarts a script server from inside itself on timeout and on select failure, calling php_close() then php_init(), so a replacement pipe is allocated mid cycle when the descriptor table is at its fullest rather than at startup when it is empty.

Suggested fix is the ping_icmp() treatment: refuse the descriptor, log which one and the limit, and restart the server so it gets a fresh allocation.

Present on develop and 1.2.x. Fix and a regression test are in #597.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions