From 3f8f9c1fa8401ab7316ea8144fbed38fd197c6bb Mon Sep 17 00:00:00 2001 From: Pratik Bhujel Date: Sat, 1 Aug 2026 13:54:53 +0545 Subject: [PATCH] Fix GH-19320: Prevent FPM UID and GID overflow --- NEWS | 3 + sapi/fpm/fpm/fpm_unix.c | 83 +++++++++++++++++++++---- sapi/fpm/fpm/fpm_worker_pool.h | 9 ++- sapi/fpm/tests/gh19320-id-overflow.phpt | 54 ++++++++++++++++ 4 files changed, 135 insertions(+), 14 deletions(-) create mode 100644 sapi/fpm/tests/gh19320-id-overflow.phpt diff --git a/NEWS b/NEWS index d9c71758bf28..9f3d7e5d0a78 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,9 @@ PHP NEWS attribute node that collides by local name with a namespaced attribute). (David Carlier) +- FPM: + . Fixed bug GH-19320 (FPM UID and GID overflow). (Pratik Bhujel) + - MBString: . Fixed bug GH-22779 (mb_strrpos() returns the wrong position for a negative offset in a non-UTF-8 encoding). (Eyüp Can Akman) diff --git a/sapi/fpm/fpm/fpm_unix.c b/sapi/fpm/fpm/fpm_unix.c index b2f0e71d8331..a72326244ee8 100644 --- a/sapi/fpm/fpm/fpm_unix.c +++ b/sapi/fpm/fpm/fpm_unix.c @@ -2,6 +2,9 @@ #include "fpm_config.h" +#include +#include +#include #include #include #include @@ -53,6 +56,40 @@ static inline bool fpm_unix_is_id(const char* name) return strlen(name) == strspn(name, "0123456789"); } +static bool fpm_unix_parse_uid(struct fpm_worker_pool_s *wp, const char *name, uid_t *uid) +{ + uintmax_t max = (uid_t) -1 > (uid_t) 0 + ? (uintmax_t) ((uid_t) -2) + : (UINTMAX_C(1) << (sizeof(uid_t) * CHAR_BIT - 1)) - 1; + + errno = 0; + uintmax_t value = strtoumax(name, NULL, 10); + if (errno == ERANGE || value > max) { + zlog(ZLOG_ERROR, "[pool %s] user ID '%s' is out of range", wp->config->name, name); + return false; + } + + *uid = (uid_t) value; + return true; +} + +static bool fpm_unix_parse_gid(struct fpm_worker_pool_s *wp, const char *name, gid_t *gid) +{ + uintmax_t max = (gid_t) -1 > (gid_t) 0 + ? (uintmax_t) ((gid_t) -2) + : (UINTMAX_C(1) << (sizeof(gid_t) * CHAR_BIT - 1)) - 1; + + errno = 0; + uintmax_t value = strtoumax(name, NULL, 10); + if (errno == ERANGE || value > max) { + zlog(ZLOG_ERROR, "[pool %s] group ID '%s' is out of range", wp->config->name, name); + return false; + } + + *gid = (gid_t) value; + return true; +} + static struct passwd *fpm_unix_get_passwd(struct fpm_worker_pool_s *wp, const char *name, int flags) { struct passwd *pwd = getpwnam(name); @@ -93,7 +130,14 @@ static inline bool fpm_unix_check_listen_address(struct fpm_worker_pool_s *wp, c static inline bool fpm_unix_check_passwd(struct fpm_worker_pool_s *wp, const char *name, int flags) { - return !name || fpm_unix_is_id(name) || fpm_unix_get_passwd(wp, name, flags); + if (!name || !*name) { + return true; + } + if (fpm_unix_is_id(name)) { + uid_t uid; + return fpm_unix_parse_uid(wp, name, &uid); + } + return fpm_unix_get_passwd(wp, name, flags) != NULL; } static struct group *fpm_unix_get_group(struct fpm_worker_pool_s *wp, const char *name, int flags) @@ -109,7 +153,14 @@ static struct group *fpm_unix_get_group(struct fpm_worker_pool_s *wp, const char static inline bool fpm_unix_check_group(struct fpm_worker_pool_s *wp, const char *name, int flags) { - return !name || fpm_unix_is_id(name) || fpm_unix_get_group(wp, name, flags); + if (!name || !*name) { + return true; + } + if (fpm_unix_is_id(name)) { + gid_t gid; + return fpm_unix_parse_gid(wp, name, &gid); + } + return fpm_unix_get_group(wp, name, flags) != NULL; } bool fpm_unix_test_config(struct fpm_worker_pool_s *wp) @@ -133,8 +184,8 @@ int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */ /* uninitialized */ wp->socket_acl = NULL; #endif - wp->socket_uid = -1; - wp->socket_gid = -1; + wp->socket_uid = (uid_t) -1; + wp->socket_gid = (gid_t) -1; wp->socket_mode = 0660; if (!c) { @@ -252,7 +303,9 @@ int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */ if (c->listen_owner && *c->listen_owner) { if (fpm_unix_is_id(c->listen_owner)) { - wp->socket_uid = strtoul(c->listen_owner, 0, 10); + if (!fpm_unix_parse_uid(wp, c->listen_owner, &wp->socket_uid)) { + return -1; + } } else { struct passwd *pwd; @@ -268,7 +321,9 @@ int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */ if (c->listen_group && *c->listen_group) { if (fpm_unix_is_id(c->listen_group)) { - wp->socket_gid = strtoul(c->listen_group, 0, 10); + if (!fpm_unix_parse_gid(wp, c->listen_group, &wp->socket_gid)) { + return -1; + } } else { struct group *grp; @@ -325,7 +380,7 @@ int fpm_unix_set_socket_permissions(struct fpm_worker_pool_s *wp, const char *pa /* When listen.users and listen.groups not configured, continue with standard right */ #endif - if (wp->socket_uid != -1 || wp->socket_gid != -1) { + if (wp->socket_uid != (uid_t) -1 || wp->socket_gid != (gid_t) -1) { if (0 > chown(path, wp->socket_uid, wp->socket_gid)) { zlog(ZLOG_SYSERROR, "[pool %s] failed to chown() the socket '%s'", wp->config->name, wp->config->listen_address); return -1; @@ -354,7 +409,9 @@ static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */ 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); + if (!fpm_unix_parse_uid(wp, wp->config->user, &wp->set_uid)) { + return -1; + } pwd = getpwuid(wp->set_uid); if (pwd) { wp->set_gid = pwd->pw_gid; @@ -378,7 +435,9 @@ static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */ if (wp->config->group && *wp->config->group) { if (fpm_unix_is_id(wp->config->group)) { - wp->set_gid = strtoul(wp->config->group, 0, 10); + if (!fpm_unix_parse_gid(wp, wp->config->group, &wp->set_gid)) { + return -1; + } } else { struct group *grp; @@ -476,17 +535,17 @@ int fpm_unix_init_child(struct fpm_worker_pool_s *wp) /* {{{ */ 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); + zlog(ZLOG_SYSERROR, "[pool %s] failed to setgid(%" PRIuMAX ")", wp->config->name, (uintmax_t) 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); + zlog(ZLOG_SYSERROR, "[pool %s] failed to initgroups(%s, %" PRIuMAX ")", wp->config->name, wp->config->user, (uintmax_t) 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); + zlog(ZLOG_SYSERROR, "[pool %s] failed to setuid(%" PRIuMAX ")", wp->config->name, (uintmax_t) wp->set_uid); return -1; } } diff --git a/sapi/fpm/fpm/fpm_worker_pool.h b/sapi/fpm/fpm/fpm_worker_pool.h index efb8640cd32f..aa06f6109bc7 100644 --- a/sapi/fpm/fpm/fpm_worker_pool.h +++ b/sapi/fpm/fpm/fpm_worker_pool.h @@ -3,6 +3,8 @@ #ifndef FPM_WORKER_POOL_H #define FPM_WORKER_POOL_H 1 +#include + #include "fpm_conf.h" #include "fpm_shm.h" @@ -23,9 +25,12 @@ struct fpm_worker_pool_s { 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 */ + uid_t set_uid; + gid_t set_gid; /* config uid and gid */ char *set_user; /* config user name */ - int socket_uid, socket_gid, socket_mode; + uid_t socket_uid; + gid_t socket_gid; + int socket_mode; /* runtime */ struct fpm_child_s *children; diff --git a/sapi/fpm/tests/gh19320-id-overflow.phpt b/sapi/fpm/tests/gh19320-id-overflow.phpt new file mode 100644 index 000000000000..fa0c708dd3f2 --- /dev/null +++ b/sapi/fpm/tests/gh19320-id-overflow.phpt @@ -0,0 +1,54 @@ +--TEST-- +FPM: Reject out-of-range numeric user and group IDs +--SKIPIF-- + +--FILE-- +testConfig(); +} + +?> +Done +--EXPECT-- +ERROR: [pool unconfined] user ID '18446744073709551615' is out of range +ERROR: FPM initialization failed +ERROR: [pool unconfined] group ID '18446744073709551615' is out of range +ERROR: FPM initialization failed +ERROR: [pool unconfined] user ID '18446744073709551615' is out of range +ERROR: FPM initialization failed +ERROR: [pool unconfined] group ID '18446744073709551615' is out of range +ERROR: FPM initialization failed +Done +--CLEAN-- +