Commit e50b8b05be8 for php

commit e50b8b05be8881072f29e9311000390d9fef2468
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Fri Sep 25 06:11:38 2026 -0400

    TSRM: Fix Windows shared memory payload offset

    shmat() returned shm->descriptor + sizeof(shm->descriptor), which scales the
    pointer-size offset by sizeof(struct shmid_ds), while shmget() reserved only
    pointer-size bytes for the descriptor and stored that inflated size in
    shm_segsz. shmop and sysvshm therefore read and wrote past the end of the
    mapping. Reserve sizeof(struct shmid_ds), return the address right after the
    descriptor, and keep shm_segsz at the requested size.

    Closes GH-24042

diff --git a/NEWS b/NEWS
index 439f7c83f2c..edeca96eaeb 100644
--- a/NEWS
+++ b/NEWS
@@ -28,6 +28,8 @@ PHP                                                                        NEWS
     throwing deprecation). (lazerg)
   . Fixed use-after-free when a dl()-loaded extension declares a frameless
     function and a later request calls it. (Ilia Alshanetsky)
+  . Fixed System V shared memory emulation for Windows attaching segments past
+    the end of their mapping. (Ilia Alshanetsky)

 - DOM:
   . Fixed use-after-free when re-constructing a DOMXPath whose php:function
diff --git a/TSRM/tsrm_win32.c b/TSRM/tsrm_win32.c
index 1fe2a47a87c..e49aac76121 100644
--- a/TSRM/tsrm_win32.c
+++ b/TSRM/tsrm_win32.c
@@ -635,6 +635,7 @@ static key_t tsrm_choose_random_shm_key(key_t prev_key) {
 TSRM_API int shmget(key_t key, size_t size, int flags)
 {/*{{{*/
 	shm_pair *shm;
+	size_t mapping_size;
 	char shm_segment[sizeof(SEGMENT_PREFIX INT_MIN_AS_STRING)];
 	HANDLE shm_handle = NULL, info_handle = NULL;
 	BOOL created = FALSE;
@@ -650,16 +651,16 @@ TSRM_API int shmget(key_t key, size_t size, int flags)

 	if (!shm_handle) {
 		if (flags & IPC_CREAT) {
-			if (size == 0 || size > SIZE_MAX - sizeof(shm->descriptor)) {
+			if (size == 0 || size > SIZE_MAX - sizeof(*shm->descriptor)) {
 				return -1;
 			}
-			size += sizeof(shm->descriptor);
+			mapping_size = size + sizeof(*shm->descriptor);
 #if SIZEOF_SIZE_T == 8
-			DWORD high = size >> 32;
-			DWORD low = (DWORD)size;
+			DWORD high = mapping_size >> 32;
+			DWORD low = (DWORD)mapping_size;
 #else
 			DWORD high = 0;
-			DWORD low = size;
+			DWORD low = mapping_size;
 #endif
 			shm_handle	= CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, high, low, key == IPC_PRIVATE ? NULL : shm_segment);
 			created		= TRUE;
@@ -730,7 +731,7 @@ TSRM_API void *shmat(int key, const void *shmaddr, int flags)
 		return (void*)-1;
 	}

-	shm->addr = shm->descriptor + sizeof(shm->descriptor);
+	shm->addr = shm->descriptor + 1;
 	shm->descriptor->shm_atime = time(NULL);
 	shm->descriptor->shm_lpid  = getpid();
 	shm->descriptor->shm_nattch++;
diff --git a/ext/shmop/tests/tsrm_shmat.phpt b/ext/shmop/tests/tsrm_shmat.phpt
new file mode 100644
index 00000000000..3a5b92efcf8
--- /dev/null
+++ b/ext/shmop/tests/tsrm_shmat.phpt
@@ -0,0 +1,24 @@
+--TEST--
+TSRM Windows shmop keeps the payload within its mapping
+--EXTENSIONS--
+shmop
+--SKIPIF--
+<?php
+if (PHP_OS_FAMILY !== 'Windows') die('skip only for Windows');
+?>
+--FILE--
+<?php
+$shm = shmop_open(0, 'c', 0644, 65472);
+$payload = str_repeat('x', 65471) . 'z';
+var_dump($shm !== false);
+var_dump(shmop_size($shm));
+var_dump(shmop_write($shm, $payload, 0));
+var_dump(shmop_read($shm, 65471, 1));
+var_dump(shmop_delete($shm));
+?>
+--EXPECT--
+bool(true)
+int(65472)
+int(65472)
+string(1) "z"
+bool(true)