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);
+}
+?>