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