Commit 1ec3d60fab6 for php.net
commit 1ec3d60fab6017f30dd1761d51a5fc6ce3d666a5
Author: Pratik Bhujel <prateekbhujelpb@gmail.com>
Date: Wed Aug 26 20:30:48 2026 +0545
Fix GH-19320: Prevent FPM UID and GID overflow (#22986)
diff --git a/NEWS b/NEWS
index c6ca2040030..df8a5c5c51c 100644
--- a/NEWS
+++ b/NEWS
@@ -88,6 +88,9 @@ PHP NEWS
- LibXML:
. Fixed bug GH-22752 (Build failure with libxml 2.15). (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 b2f0e71d833..a58e248b666 100644
--- a/sapi/fpm/fpm/fpm_unix.c
+++ b/sapi/fpm/fpm/fpm_unix.c
@@ -2,6 +2,9 @@
#include "fpm_config.h"
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
#include <string.h>
#include <sys/time.h>
#include <sys/resource.h>
@@ -53,6 +56,42 @@ 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 sentinel = (uintmax_t) ((uid_t) -1);
+ uintmax_t max = (uid_t) -1 > (uid_t) 0
+ ? (uintmax_t) ((uid_t) -1)
+ : (UINTMAX_C(1) << (sizeof(uid_t) * CHAR_BIT - 1)) - 1;
+
+ errno = 0;
+ uintmax_t value = strtoumax(name, NULL, 10);
+ if (errno == ERANGE || value > max || value == sentinel) {
+ 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 sentinel = (uintmax_t) ((gid_t) -1);
+ uintmax_t max = (gid_t) -1 > (gid_t) 0
+ ? (uintmax_t) ((gid_t) -1)
+ : (UINTMAX_C(1) << (sizeof(gid_t) * CHAR_BIT - 1)) - 1;
+
+ errno = 0;
+ uintmax_t value = strtoumax(name, NULL, 10);
+ if (errno == ERANGE || value > max || value == sentinel) {
+ 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 +132,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 +155,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 +186,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 +305,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 +323,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 +382,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 +411,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 +437,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 +537,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 efb8640cd32..aa06f6109bc 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 <sys/types.h>
+
#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 00000000000..fa0c708dd3f
--- /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--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$id = '18446744073709551615';
+$settings = [
+ "user = $id",
+ "user = 1\ngroup = $id",
+ "user = 1\nlisten.owner = $id",
+ "user = 1\nlisten.group = $id",
+];
+
+foreach ($settings as $setting) {
+ $cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR:UDS}}
+$setting
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+ $tester = new FPM\Tester($cfg);
+ $tester->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--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>