Commit f0d244ab3e9 for php.net
commit f0d244ab3e97edf168d73a16da631823612b2789
Author: David Carlier <devnexen@gmail.com>
Date: Fri Jul 31 22:37:36 2026 +0100
ext/sysvshm: do not trust $size when opening an existing segment
shm_attach() wrote the requested size into the header of a segment it
did not create, so a foreign segment framed as larger than it is let
shm_put_var() write past the mapping. Take the size from shmctl()
IPC_STAT instead, and reject a segment too small to hold the header.
Noticed while reviewing GH-22959.
diff --git a/NEWS b/NEWS
index d9c71758bf2..491d71681ff 100644
--- a/NEWS
+++ b/NEWS
@@ -29,6 +29,10 @@ PHP NEWS
. Fixed bug GH-15836 (Use-after-free when a user stream filter accesses
$this->stream during the close flush). (iliaal)
+- Sysvshm:
+ . Fixed out-of-bounds write when shm_attach() opens an existing segment with
+ a size larger than the segment actually is. (David Carlier)
+
- Opcache:
. Fixed bug GH-22857 (Function JIT emits wrong code for FETCH_OBJ_FUNC_ARG on a
property hook getter, losing register-held variables). (Zhao Hao)
diff --git a/ext/sysvshm/sysvshm.c b/ext/sysvshm/sysvshm.c
index 84c10add85e..9c68c09147d 100644
--- a/ext/sysvshm/sysvshm.c
+++ b/ext/sysvshm/sysvshm.c
@@ -130,6 +130,7 @@ PHP_FUNCTION(shm_attach)
sysvshm_shm *shm_list_ptr;
char *shm_ptr;
sysvshm_chunk_head *chunk_ptr;
+ struct shmid_ds shm_desc;
zend_long shm_key, shm_id, shm_size, shm_flag = 0666;
bool shm_size_is_null = 1;
bool created = false;
@@ -168,6 +169,25 @@ PHP_FUNCTION(shm_attach)
RETURN_FALSE;
}
+ if (shmctl(shm_id, IPC_STAT, &shm_desc) < 0) {
+ php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", shm_key, strerror(errno));
+ shmdt(shm_ptr);
+ if (created) {
+ shmctl(shm_id, IPC_RMID, NULL);
+ }
+ RETURN_FALSE;
+ }
+ shm_size = (zend_long)shm_desc.shm_segsz;
+
+ if (shm_size < (zend_long) sizeof(sysvshm_chunk_head)) {
+ php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": segment too small", shm_key);
+ shmdt(shm_ptr);
+ if (created) {
+ shmctl(shm_id, IPC_RMID, NULL);
+ }
+ RETURN_FALSE;
+ }
+
/* check if shm is already initialized */
chunk_ptr = (sysvshm_chunk_head *) shm_ptr;
if (strcmp((char*) &(chunk_ptr->magic), "PHP_SM") != 0) {
diff --git a/ext/sysvshm/tests/shm_attach_existing_segment_size.phpt b/ext/sysvshm/tests/shm_attach_existing_segment_size.phpt
new file mode 100644
index 00000000000..8f7f233e0d5
--- /dev/null
+++ b/ext/sysvshm/tests/shm_attach_existing_segment_size.phpt
@@ -0,0 +1,36 @@
+--TEST--
+shm_attach() takes the size of an existing segment from the kernel, not from $size
+--EXTENSIONS--
+sysvshm
+shmop
+--FILE--
+<?php
+$key = 0x53484D31;
+
+$raw = shmop_open($key, 'n', 0600, 4096);
+
+$shm = shm_attach($key, 10 * 1024 * 1024);
+var_dump($shm instanceof SysvSharedMemory);
+
+var_dump(shm_put_var($shm, 1, str_repeat('A', 1024 * 1024)));
+
+var_dump(shm_put_var($shm, 2, 'ok'));
+var_dump(shm_get_var($shm, 2));
+
+var_dump(shm_remove($shm));
+?>
+--EXPECTF--
+bool(true)
+
+Warning: shm_put_var(): Not enough shared memory left in %s on line %d
+bool(false)
+bool(true)
+string(2) "ok"
+bool(true)
+--CLEAN--
+<?php
+$raw = @shmop_open(0x53484D31, 'w', 0, 0);
+if ($raw) {
+ shmop_delete($raw);
+}
+?>
diff --git a/ext/sysvshm/tests/shm_attach_existing_segment_too_small.phpt b/ext/sysvshm/tests/shm_attach_existing_segment_too_small.phpt
new file mode 100644
index 00000000000..eec56c8f091
--- /dev/null
+++ b/ext/sysvshm/tests/shm_attach_existing_segment_too_small.phpt
@@ -0,0 +1,25 @@
+--TEST--
+shm_attach() rejects an existing segment too small to hold its header
+--EXTENSIONS--
+sysvshm
+shmop
+--FILE--
+<?php
+$key = 0x53484D32;
+
+$raw = shmop_open($key, 'n', 0600, 8);
+
+var_dump(shm_attach($key, 1024));
+
+shmop_delete($raw);
+?>
+--EXPECTF--
+Warning: shm_attach(): Failed for key 0x%x: segment too small in %s on line %d
+bool(false)
+--CLEAN--
+<?php
+$raw = @shmop_open(0x53484D32, 'w', 0, 0);
+if ($raw) {
+ shmop_delete($raw);
+}
+?>