Commit a0d06f1c296 for php.net

commit a0d06f1c29665805ea20bff8dc41b918bc5cdee2
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Fri Jul 31 16:06:55 2026 -0400

    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

diff --git a/ext/sysvmsg/sysvmsg.c b/ext/sysvmsg/sysvmsg.c
index 0c2b3dcf183..965e6a66fb3 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 00000000000..e346bde9fc2
--- /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 00000000000..951cf62f2ab
--- /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 e506bd5bb37..96efd060273 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 00000000000..a7959eb2156
--- /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