Commit 28b643531a5 for php
commit 28b643531a5c39be1dee636023657cd45a0ad5e4
Author: Julien Voisin <jvoisin@users.noreply.github.com>
Date: Mon Nov 2 12:03:32 2026 +0100
Test that serialization state is reset before request input processing (#24479)
diff --git a/Zend/tests/arginfo_zpp_mismatch.inc b/Zend/tests/arginfo_zpp_mismatch.inc
index 9c03e719c2a..695486d2313 100644
--- a/Zend/tests/arginfo_zpp_mismatch.inc
+++ b/Zend/tests/arginfo_zpp_mismatch.inc
@@ -10,6 +10,7 @@ function skipFunction($function): bool {
|| $function === 'exit'
|| $function === 'die'
|| $function === 'zend_trigger_bailout'
+ || $function === 'zend_test_serialization_bailout'
/* intentionally violate invariants */
|| $function === 'zend_create_unterminated_string'
|| $function === 'zend_test_array_return'
diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c
index 846fd36a403..17312fca566 100644
--- a/ext/zend_test/test.c
+++ b/ext/zend_test/test.c
@@ -21,6 +21,7 @@
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
+#include "ext/standard/php_var.h"
#include "php_test.h"
#include "observer.h"
#include "fiber.h"
@@ -212,6 +213,38 @@ static ZEND_FUNCTION(zend_leak_bytes)
emalloc(leakbytes);
}
+static ZEND_FUNCTION(zend_test_serialization_bailout)
+{
+ bool unserialize, locked;
+
+ ZEND_PARSE_PARAMETERS_START(2, 2)
+ Z_PARAM_BOOL(unserialize)
+ Z_PARAM_BOOL(locked)
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (unserialize) {
+ (void) php_var_unserialize_init();
+ } else {
+ (void) php_var_serialize_init();
+ }
+ if (locked) {
+ BG(serialize_lock)++;
+ }
+ zend_bailout();
+}
+
+static ZEND_FUNCTION(zend_test_serialization_state)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ array_init(return_value);
+ add_assoc_long(return_value, "lock", BG(serialize_lock));
+ add_assoc_long(return_value, "serialize_level", BG(serialize).level);
+ add_assoc_bool(return_value, "serialize_data", BG(serialize).data != NULL);
+ add_assoc_long(return_value, "unserialize_level", BG(unserialize).level);
+ add_assoc_bool(return_value, "unserialize_data", BG(unserialize).data != NULL);
+}
+
/* Reallocate a block of old_size bytes to new_size bytes and return its block
* size, along with the block size of a fresh allocation of new_size bytes. */
static ZEND_FUNCTION(zend_test_erealloc_block_size)
diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php
index 8719c6935a3..62cc78cbe24 100644
--- a/ext/zend_test/test.stub.php
+++ b/ext/zend_test/test.stub.php
@@ -303,6 +303,10 @@ function zend_delref(mixed $variable): void {}
function zend_test_erealloc_block_size(int $old_size, int $new_size): array {}
+ function zend_test_serialization_bailout(bool $unserialize, bool $locked): never {}
+
+ function zend_test_serialization_state(): array {}
+
function zend_bool(bool $param): bool {}
function zend_bool_or_null(bool|null $param): bool|null {}
function zend_bool_slow_zpp(bool $param): bool {}
diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h
index c2509001e45..4d7bced1402 100644
Binary files a/ext/zend_test/test_arginfo.h and b/ext/zend_test/test_arginfo.h differ
diff --git a/ext/zend_test/test_decl.h b/ext/zend_test/test_decl.h
index 291f7ab87af..8fc579dd687 100644
Binary files a/ext/zend_test/test_decl.h and b/ext/zend_test/test_decl.h differ
diff --git a/ext/zend_test/test_legacy_arginfo.h b/ext/zend_test/test_legacy_arginfo.h
index d6e1f758e20..f654203e2a6 100644
Binary files a/ext/zend_test/test_legacy_arginfo.h and b/ext/zend_test/test_legacy_arginfo.h differ
diff --git a/sapi/cli/tests/serialization_context_request_reset.phpt b/sapi/cli/tests/serialization_context_request_reset.phpt
new file mode 100644
index 00000000000..caaf086ab17
--- /dev/null
+++ b/sapi/cli/tests/serialization_context_request_reset.phpt
@@ -0,0 +1,172 @@
+--TEST--
+Serialization contexts abandoned by a bailout are reset before processing the next request
+--EXTENSIONS--
+session
+zend_test
+--INI--
+allow_url_fopen=1
+--ENV--
+PHP_CLI_SERVER_WORKERS=
+--SKIPIF--
+<?php
+include "skipif.inc";
+if (!function_exists("proc_open")) {
+ die("skip proc_open is required");
+}
+?>
+--FILE--
+<?php
+include "php_cli_server.inc";
+
+$sessionDir = __DIR__ . "/serialization_context_request_reset-sessions-" . getmypid();
+mkdir($sessionDir);
+register_shutdown_function(function () use ($sessionDir) {
+ $path = $sessionDir . "/sess_serialization-reset";
+ if (file_exists($path)) {
+ unlink($path);
+ }
+ rmdir($sessionDir);
+});
+$args = [
+ "-d", "session.upload_progress.enabled=1",
+ "-d", "session.upload_progress.cleanup=0",
+ "-d", "session.upload_progress.freq=0",
+ "-d", "session.upload_progress.min_freq=0",
+ "-d", "session.use_strict_mode=0",
+ "-d", "session.save_handler=files",
+ "-d", "session.save_path=$sessionDir",
+];
+foreach (["session", "zend_test"] as $extension) {
+ $path = ini_get("extension_dir") . DIRECTORY_SEPARATOR . $extension . "." . PHP_SHLIB_SUFFIX;
+ if (file_exists($path)) {
+ $args[] = "-d";
+ $args[] = "extension=$path";
+ }
+}
+$server = php_cli_server_start(<<<'PHP'
+session_id("serialization-reset");
+session_start();
+echo getmypid(), "\n";
+if (isset($_GET["bailout"])) {
+ $_SESSION = ["value" => ["nested" => 42]];
+ session_write_close();
+ echo "bailout\n";
+ zend_test_serialization_bailout((bool) $_GET["unserialize"], (bool) $_GET["locked"]);
+}
+var_dump(zend_test_serialization_state());
+var_dump($_SESSION["value"]);
+var_dump($_SESSION["upload_progress_test"]["done"]);
+var_dump($_FILES["file"]["error"]);
+session_destroy();
+PHP, cmd_args: $args);
+
+$body = "--boundary\r\n"
+ . "Content-Disposition: form-data; name=\"PHP_SESSION_UPLOAD_PROGRESS\"\r\n\r\n"
+ . "test\r\n"
+ . "--boundary\r\n"
+ . "Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\n"
+ . "Content-Type: text/plain\r\n\r\n"
+ . "contents\r\n"
+ . "--boundary--\r\n";
+
+foreach ([0, 1] as $unserialize) {
+ foreach ([0, 1] as $locked) {
+ [$pid, $response] = explode("\n", file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS
+ . "/?bailout=1&unserialize=$unserialize&locked=$locked"), 2);
+ echo $response;
+ $context = stream_context_create(["http" => [
+ "method" => "POST",
+ "header" => "Content-Type: multipart/form-data; boundary=boundary\r\n"
+ . "Cookie: PHPSESSID=serialization-reset\r\n",
+ "content" => $body,
+ ]]);
+ $response = file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS, false, $context);
+ if ($response === false) {
+ echo file_get_contents($server->outputFile);
+ break 2;
+ }
+ [$nextPid, $response] = explode("\n", $response, 2);
+ if ($nextPid !== $pid) {
+ throw new RuntimeException("Requests must use the same worker");
+ }
+ echo $response;
+ }
+}
+?>
+--EXPECT--
+bailout
+array(5) {
+ ["lock"]=>
+ int(0)
+ ["serialize_level"]=>
+ int(0)
+ ["serialize_data"]=>
+ bool(false)
+ ["unserialize_level"]=>
+ int(0)
+ ["unserialize_data"]=>
+ bool(false)
+}
+array(1) {
+ ["nested"]=>
+ int(42)
+}
+bool(true)
+int(0)
+bailout
+array(5) {
+ ["lock"]=>
+ int(0)
+ ["serialize_level"]=>
+ int(0)
+ ["serialize_data"]=>
+ bool(false)
+ ["unserialize_level"]=>
+ int(0)
+ ["unserialize_data"]=>
+ bool(false)
+}
+array(1) {
+ ["nested"]=>
+ int(42)
+}
+bool(true)
+int(0)
+bailout
+array(5) {
+ ["lock"]=>
+ int(0)
+ ["serialize_level"]=>
+ int(0)
+ ["serialize_data"]=>
+ bool(false)
+ ["unserialize_level"]=>
+ int(0)
+ ["unserialize_data"]=>
+ bool(false)
+}
+array(1) {
+ ["nested"]=>
+ int(42)
+}
+bool(true)
+int(0)
+bailout
+array(5) {
+ ["lock"]=>
+ int(0)
+ ["serialize_level"]=>
+ int(0)
+ ["serialize_data"]=>
+ bool(false)
+ ["unserialize_level"]=>
+ int(0)
+ ["unserialize_data"]=>
+ bool(false)
+}
+array(1) {
+ ["nested"]=>
+ int(42)
+}
+bool(true)
+int(0)