Skip to content

FPM uid and gid overflow #19320

Closed
Closed

Description

@bukka

Description

This was originally reported in the security report but because it requires access to the FPM configuration (which means basically access to start FPM which is often the root user in typical setup), it wasn't considered as a security issue - such users has already permission to do whatever they want.

The supplied UID and GID to be used by PHP-FPM workers are converted from an unsigned
long type, which could be either 64 or 32 bits unsigned integer depending on the platform,
but are stored in a signed 32 bits integer. This could create confusion with the actual used
UID/GID. Also, a bad UID or GID can make repeatedly crash the workers when setting
UID or GID through setuid or setgid function, because the saved UID and GID are not
verified to be valid before forking.

PHP Version

PHP 8.3+

Operating System

No response

Activity

  1. bukka commented on Jul 30, 2025

    @bukka
    MemberAuthor

    Here is an additional info from the security report:

    Background

    For security purposes and because they don't need high privileges, PHP-FPM workers shouldn't run using root privileges. In order to specify the user and group to use, one has to fill the fields user and group in the dedicated worker pool configuration file.

    During startup, worker pool configuration is parsed, and stored as a struct fpm_worker_pool_s defined below:

    struct fpm_worker_pool_s {
        struct fpm_worker_pool_s *next;
        struct fpm_worker_pool_s *shared;
        struct fpm_worker_pool_config_s *config;
        char *user, *home;
        /* for setting env USER and HOME */
        enum fpm_address_domain listen_address_domain;
        int listening_socket;
        int set_uid, set_gid;
        /* config uid and gid */
        char *set_user;
        /* config user name */
        int socket_uid, socket_gid, socket_mode;
        /* runtime */
        struct fpm_child_s *children;
        int running_children;
        int idle_spawn_rate;
        int warn_max_children;
    #if 0
        int warn_lq;
    #endif
        struct fpm_scoreboard_s *scoreboard;
        int log_fd;
        char **limit_extensions;
        /* for ondemand PM */
        struct fpm_event_s *ondemand_event;
        int socket_event_set;
    #ifdef HAVE_FPM_ACL
        void *socket_acl;
    #endif
    };

    Worker Pool Configuration

    This structure is then later used in order to configure workers during startup phase. The function fpm_unix_conf_wp, defined in fpm_unix.c, handles the worker pool configuration regarding users and groups. It is partially defined below:

    static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp)
    {
        int is_root = !geteuid();
        ...
        struct passwd *pwd;
        int is_root = !geteuid();
        
        if (is_root) {
            if (wp->config->user && *wp->config->user) {
                if (fpm_unix_is_id(wp->config->user)) {
                    wp->set_uid = strtoul(wp->config->user, 0, 10);
                    pwd = getpwuid(wp->set_uid);
                    if (pwd) {
                        wp->set_gid = pwd->pw_gid;
                        wp->set_user = strdup(pwd->pw_name);
                    }
                }
            }
        }
    }

    Integer Overflow Vulnerability

    Those fields either accept user names and group names, or UID and GID. When UID or GID are supplied, they are converted from string to an unsigned long type, which is, for example, always an unsigned 64 bits integer on 64 bits Unix platforms. However, it is stored in wp->set_uid, which is a signed 32 bits integer.

    This value is then used during worker initialisation, after it has forked, in the fpm_unix_init_child function. The interesting code part is defined below:

    if (is_root) {
        if (wp->config->process_priority != 64) {
            if (setpriority(PRIO_PROCESS, 0, wp->config->process_priority) < 0) {
                zlog(ZLOG_SYSERROR, "[pool %s] Unable to set priority for this new process", 
                     wp->config->name);
                return -1;
            }
        }
        
        if (wp->set_gid) {
            if (0 > setgid(wp->set_gid)) {
                zlog(ZLOG_SYSERROR, "[pool %s] failed to setgid(%d)", 
                     wp->config->name, wp->set_gid);
                return -1;
            }
        }
        
        if (wp->set_uid) {
            if (0 > initgroups(wp->set_user ? wp->set_user : wp->config->user, 
                              wp->set_gid)) {
                zlog(ZLOG_SYSERROR, "[pool %s] failed to initgroups(%s, %d)", 
                     wp->config->name, wp->config->user, wp->set_gid);
                return -1;
            }
            if (0 > setuid(wp->set_uid)) {
                zlog(ZLOG_SYSERROR, "[pool %s] failed to setuid(%d)", 
                     wp->config->name, wp->set_uid);
                return -1;
            }
        }
    }

    Impact

    Fields wp->set_uid and wp->set_gid are given as argument to setuid and setgid functions which expects uid_t and gid_t types.

    POSIX® documents those type as integers and doesn't mention whether they are signed or unsigned. However the GNU libc defines them as unsigned integers.

    At this point, the values can be the one supplied in the configuration file, or another values because they have overflowed during the 64 bits to 32 bits cast. The values also can be invalid regarding the UID or GID of the current running system: the function will return -1 and the worker will shutdown.

    Depending on the configuration of the process manager, workers may repeatedly start and crash because of bad UID/GID values.

  2. bukka commented on Jul 30, 2025

    @bukka
    MemberAuthor

    Just to add this configuration would always require root user because it doesn't process user and group otherwise...

  3. changed the title [-]FPM uid and gid overflow issue[/-] [+]FPM uid and gid overflow[/+] on Jul 30, 2025
  4. added a commit that references this issue on Aug 26, 2026
    1ec3d60
  5. added 2 commits that reference this issue on Aug 26, 2026
    333ac22
    b3b31d9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions