[php-src] PHP-8.4: Fix GH-19320: Prevent FPM UID and GID overflow (#22986)

Pratik Bhujel via GitHub <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Pratik Bhujel (prateekbhujel)
Committer: GitHub (web-flow)
Pusher: arnaud-lb
Date: 2026-08-26T16:45:48+02:00

Commit: https://github.com/php/php-src/commit/1ec3d60fab6017f30dd1761d51a5fc6ce3d666a5
Raw diff: https://github.com/php/php-src/commit/1ec3d60fab6017f30dd1761d51a5fc6ce3d666a5.diff

Fix GH-19320: Prevent FPM UID and GID overflow (#22986)

Changed paths:
  A  sapi/fpm/tests/gh19320-id-overflow.phpt
  M  NEWS
  M  sapi/fpm/fpm/fpm_unix.c
  M  sapi/fpm/fpm/fpm_worker_pool.h


Diff:

diff --git a/NEWS b/NEWS
index c6ca2040030d..df8a5c5c51c9 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 b2f0e71d8331..a58e248b6666 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 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 <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 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--
+<?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();
+?>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.