[php-src] master: Merge branch 'PHP-8.5'

Arnaud Le Blanc <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Arnaud Le Blanc (arnaud-lb)
Date: 2026-08-26T16:48:14+02:00

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

Merge branch 'PHP-8.5'

* PHP-8.5:
  [ci skip] NEWS
  Fix GH-19320: Prevent FPM UID and GID overflow (#22986)

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


Diff:

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.