[PHP-CVS] [php-src] master: sysvmsg/sysvsem: fix IPC_PRIVATE and out-of-range key handling (#22956)

[email protected] (Ilia Alshanetsky via GitHub) Fri, 31 Jul 2026 20:06:58 +0000
Newsgroups php.cvs
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Committer: GitHub (web-flow)
Pusher: iliaal
Date: 2026-07-31T16:06:55-04:00

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

sysvmsg/sysvsem: fix IPC_PRIVATE and out-of-range key handling (#22956)

msgget() always creates a queue for IPC_PRIVATE, so the existence probe
in msg_queue_exists() leaks one per call, and the same probe in
msg_get_queue() shadows the IPC_CREAT branch and hands back a queue with
no permission bits. Skip the probe for IPC_PRIVATE. msg_queue_exists(),
msg_get_queue() and sem_get() also passed the zend_long key straight to
msgget()/semget(), where it truncates to key_t; they now reject
out-of-range keys as shmop_open() and shm_attach() have since GH-9945.

Closes GH-22956

Changed paths:
  A  ext/sysvmsg/tests/gh9945.phpt
  A  ext/sysvmsg/tests/msg_queue_ipc_private.phpt
  A  ext/sysvsem/tests/gh9945.phpt
  M  ext/sysvmsg/sysvmsg.c
  M  ext/sysvsem/sysvsem.c


Diff:

diff --git a/ext/sysvmsg/sysvmsg.c b/ext/sysvmsg/sysvmsg.c
index 0c2b3dcf183b..965e6a66fb31 100644
--- a/ext/sysvmsg/sysvmsg.c
+++ b/ext/sysvmsg/sysvmsg.c
@@ -192,12 +192,23 @@ PHP_FUNCTION(msg_stat_queue)
 /* {{{ Check whether a message queue exists */
 PHP_FUNCTION(msg_queue_exists)
 {
-	zend_long key;
+	zend_long key_arg;
+	key_t key;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &key_arg) == FAILURE)	{
+		RETURN_THROWS();
+	}
 
-	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &key) == FAILURE)	{
+	key = (key_t) key_arg;
+	if ((zend_long) key != key_arg) {
+		zend_argument_value_error(1, "is out of range");
 		RETURN_THROWS();
 	}
 
+	if (key == IPC_PRIVATE) {
+		RETURN_FALSE;
+	}
+
 	RETURN_BOOL(msgget(key, 0) >= 0);
 }
 /* }}} */
@@ -205,11 +216,18 @@ PHP_FUNCTION(msg_queue_exists)
 /* {{{ Attach to a message queue */
 PHP_FUNCTION(msg_get_queue)
 {
-	zend_long key;
+	zend_long key_arg;
 	zend_long perms = 0666;
+	key_t key;
 	sysvmsg_queue_t *mq;
 
-	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", &key, &perms) == FAILURE)	{
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", &key_arg, &perms) == FAILURE)	{
+		RETURN_THROWS();
+	}
+
+	key = (key_t) key_arg;
+	if ((zend_long) key != key_arg) {
+		zend_argument_value_error(1, "is out of range");
 		RETURN_THROWS();
 	}
 
@@ -217,12 +235,16 @@ PHP_FUNCTION(msg_get_queue)
 	mq = Z_SYSVMSG_QUEUE_P(return_value);
 
 	mq->key = key;
-	mq->id = msgget(key, 0);
+	if (key == IPC_PRIVATE) {
+		mq->id = -1;
+	} else {
+		mq->id = msgget(key, 0);
+	}
 	if (mq->id < 0)	{
 		/* doesn't already exist; create it */
 		mq->id = msgget(key, IPC_CREAT | IPC_EXCL | perms);
 		if (mq->id < 0)	{
-			php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", key, strerror(errno));
+			php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", key_arg, strerror(errno));
 			zval_ptr_dtor(return_value);
 			RETURN_FALSE;
 		}
diff --git a/ext/sysvmsg/tests/gh9945.phpt b/ext/sysvmsg/tests/gh9945.phpt
new file mode 100644
index 000000000000..e346bde9fc29
--- /dev/null
+++ b/ext/sysvmsg/tests/gh9945.phpt
@@ -0,0 +1,26 @@
+--TEST--
+GH-9945: sysvmsg must reject keys outside the key_t range
+--EXTENSIONS--
+sysvmsg
+--SKIPIF--
+<?php
+if (PHP_INT_SIZE !== 8) die('skip only for 64-bit');
+if (PHP_OS_FAMILY !== 'Linux') die('skip only for platforms with 32-bit key_t');
+?>
+--FILE--
+<?php
+try {
+    msg_queue_exists(0x100000000);
+} catch (ValueError $exception) {
+    echo $exception::class, ": ", $exception->getMessage(), "\n";
+}
+
+try {
+    msg_get_queue(0x100000000);
+} catch (ValueError $exception) {
+    echo $exception::class, ": ", $exception->getMessage(), "\n";
+}
+?>
+--EXPECT--
+ValueError: msg_queue_exists(): Argument #1 ($key) is out of range
+ValueError: msg_get_queue(): Argument #1 ($key) is out of range
diff --git a/ext/sysvmsg/tests/msg_queue_ipc_private.phpt b/ext/sysvmsg/tests/msg_queue_ipc_private.phpt
new file mode 100644
index 000000000000..951cf62f2ab9
--- /dev/null
+++ b/ext/sysvmsg/tests/msg_queue_ipc_private.phpt
@@ -0,0 +1,28 @@
+--TEST--
+msg_queue_exists() and msg_get_queue() with IPC_PRIVATE
+--EXTENSIONS--
+sysvmsg
+--FILE--
+<?php
+var_dump(msg_queue_exists(0));
+
+$queue = msg_get_queue(0, 0600);
+
+try {
+    var_dump(msg_queue_exists(0));
+    printf("%o\n", msg_stat_queue($queue)['msg_perm.mode']);
+    var_dump(msg_send($queue, 1, 'hello'));
+    var_dump(msg_receive($queue, 1, $type, 1024, $message));
+    var_dump($message);
+} finally {
+    var_dump(msg_remove_queue($queue));
+}
+?>
+--EXPECT--
+bool(false)
+bool(false)
+600
+bool(true)
+bool(true)
+string(5) "hello"
+bool(true)
diff --git a/ext/sysvsem/sysvsem.c b/ext/sysvsem/sysvsem.c
index e506bd5bb37c..96efd060273a 100644
--- a/ext/sysvsem/sysvsem.c
+++ b/ext/sysvsem/sysvsem.c
@@ -167,14 +167,21 @@ PHP_MINFO_FUNCTION(sysvsem)
 /* {{{ Return an id for the semaphore with the given key, and allow max_acquire (default 1) processes to acquire it simultaneously */
 PHP_FUNCTION(sem_get)
 {
-	zend_long key, max_acquire = 1, perm = 0666;
+	zend_long key_arg, max_acquire = 1, perm = 0666;
 	bool auto_release = true;
+	key_t key;
 	int semid;
 	struct sembuf sop[3];
 	int count;
 	sysvsem_sem *sem_ptr;
 
-	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "l|llb", &key, &max_acquire, &perm, &auto_release)) {
+	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "l|llb", &key_arg, &max_acquire, &perm, &auto_release)) {
+		RETURN_THROWS();
+	}
+
+	key = (key_t) key_arg;
+	if ((zend_long) key != key_arg) {
+		zend_argument_value_error(1, "is out of range");
 		RETURN_THROWS();
 	}
 
@@ -186,7 +193,7 @@ PHP_FUNCTION(sem_get)
 
 	semid = semget(key, 3, perm|IPC_CREAT);
 	if (semid == -1) {
-		php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", key, strerror(errno));
+		php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", key_arg, strerror(errno));
 		RETURN_FALSE;
 	}
 
@@ -218,7 +225,7 @@ PHP_FUNCTION(sem_get)
 	sop[2].sem_flg = SEM_UNDO;
 	while (semop(semid, sop, 3) == -1) {
 		if (errno != EINTR) {
-			php_error_docref(NULL, E_WARNING, "Failed acquiring SYSVSEM_SETVAL for key 0x" ZEND_XLONG_FMT ": %s", key, strerror(errno));
+			php_error_docref(NULL, E_WARNING, "Failed acquiring SYSVSEM_SETVAL for key 0x" ZEND_XLONG_FMT ": %s", key_arg, strerror(errno));
 			break;
 		}
 	}
@@ -226,7 +233,7 @@ PHP_FUNCTION(sem_get)
 	/* Get the usage count. */
 	count = semctl(semid, SYSVSEM_USAGE, GETVAL, NULL);
 	if (count == -1) {
-		php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", key, strerror(errno));
+		php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", key_arg, strerror(errno));
 	}
 
 	/* If we are the only user, then take this opportunity to set the max. */
@@ -235,7 +242,7 @@ PHP_FUNCTION(sem_get)
 		union semun semarg;
 		semarg.val = max_acquire;
 		if (semctl(semid, SYSVSEM_SEM, SETVAL, semarg) == -1) {
-			php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", key, strerror(errno));
+			php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", key_arg, strerror(errno));
 		}
 	}
 
@@ -246,7 +253,7 @@ PHP_FUNCTION(sem_get)
 	sop[0].sem_flg = SEM_UNDO;
 	while (semop(semid, sop, 1) == -1) {
 		if (errno != EINTR) {
-			php_error_docref(NULL, E_WARNING, "Failed releasing SYSVSEM_SETVAL for key 0x" ZEND_XLONG_FMT ": %s", key, strerror(errno));
+			php_error_docref(NULL, E_WARNING, "Failed releasing SYSVSEM_SETVAL for key 0x" ZEND_XLONG_FMT ": %s", key_arg, strerror(errno));
 			break;
 		}
 	}
diff --git a/ext/sysvsem/tests/gh9945.phpt b/ext/sysvsem/tests/gh9945.phpt
new file mode 100644
index 000000000000..a7959eb21563
--- /dev/null
+++ b/ext/sysvsem/tests/gh9945.phpt
@@ -0,0 +1,19 @@
+--TEST--
+GH-9945: sem_get() must reject keys outside the key_t range
+--EXTENSIONS--
+sysvsem
+--SKIPIF--
+<?php
+if (PHP_INT_SIZE !== 8) die('skip only for 64-bit');
+if (PHP_OS_FAMILY !== 'Linux') die('skip only for platforms with 32-bit key_t');
+?>
+--FILE--
+<?php
+try {
+    sem_get(0x100000000);
+} catch (ValueError $exception) {
+    echo $exception::class, ": ", $exception->getMessage(), "\n";
+}
+?>
+--EXPECT--
+ValueError: sem_get(): Argument #1 ($key) is out of range