[php-src] master: Fix GH-24373: serialize() context reused by destructors during teardown
| From: | David Carlier | Date: | Thu, 22 Oct 2026 19:28:56 +0000 |
| Subject: | [php-src] master: Fix GH-24373: serialize() context reused by destructors during teardown | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142609@lists.php.net to get a copy of this message | ||
Author: David Carlier (devnexen)
Date: 2026-10-22T20:24:43+01:00
Commit: https://github.com/php/php-src/commit/08f4b64e0ae69e51ca95b54717dc74405ba5f0f9
Raw diff: https://github.com/php/php-src/commit/08f4b64e0ae69e51ca95b54717dc74405ba5f0f9.diff
Fix GH-24373: serialize() context reused by destructors during teardown
The context was destroyed while still registered. Unregister it first.
Close GH-24452
Changed paths:
A ext/standard/tests/serialize/gh24373.phpt
A ext/standard/tests/serialize/gh24373_unserialize.phpt
M NEWS
M ext/standard/var.c
M ext/standard/var_unserializer.re
Diff:
diff --git a/NEWS b/NEWS
index a35e4ce8132c..22760f8d7d1b 100644
--- a/NEWS
+++ b/NEWS
@@ -133,6 +133,8 @@ PHP NEWS
(Jorg Sowa)
. Don't share the unserialize() context with enum autoloaders.
(Nicolas Grekas)
+ . Fixed bug GH-24373 (serialize() called from a destructor during var_hash
+ destruction reuses the outer var_hash). (David Carlier)
- Streams:
. Fixed bug GH-24508 (HTTP wrapper leaks the dechunk filter when header
diff --git a/ext/standard/tests/serialize/gh24373.phpt b/ext/standard/tests/serialize/gh24373.phpt
new file mode 100644
index 000000000000..14da98ade0d0
--- /dev/null
+++ b/ext/standard/tests/serialize/gh24373.phpt
@@ -0,0 +1,40 @@
+--TEST--
+GH-24373 (serialize() called from a destructor during var_hash destruction reuses the outer
var_hash)
+--CREDITS--
+janhelke
+--FILE--
+<?php
+class D {
+ public function __serialize(): array { return []; }
+ public function __destruct() {
+ $o = new stdClass();
+ $GLOBALS['inner'] = serialize([$o, $o]);
+ }
+}
+class Outer {
+ public function __serialize(): array { return ["d" => new D()]; }
+}
+serialize([new Outer(), new stdClass()]);
+echo $GLOBALS['inner'], PHP_EOL;
+
+class D2 {
+ public function __serialize(): array { return []; }
+ public function __destruct() {
+ $a = [];
+ for ($i = 0; $i < 2000; $i++) { $o = new stdClass(); $a[] = $o; $a[] = $o; }
+ serialize($a);
+ }
+}
+class Outer2 {
+ public function __serialize(): array { return ["d" => new D2()]; }
+}
+$shared = [];
+for ($i = 0; $i < 200; $i++) { $shared[] = new stdClass(); }
+$list = [new Outer2()];
+foreach ($shared as $o) { $list[] = $o; $list[] = $o; }
+serialize($list);
+echo "survived", PHP_EOL;
+?>
+--EXPECT--
+a:2:{i:0;O:8:"stdClass":0:{}i:1;r:2;}
+survived
diff --git a/ext/standard/tests/serialize/gh24373_unserialize.phpt
b/ext/standard/tests/serialize/gh24373_unserialize.phpt
new file mode 100644
index 000000000000..91bd6ee6bca0
--- /dev/null
+++ b/ext/standard/tests/serialize/gh24373_unserialize.phpt
@@ -0,0 +1,14 @@
+--TEST--
+GH-24373 (unserialize() called from a destructor during var_hash destruction reuses the outer
var_hash)
+--FILE--
+<?php
+class D {
+ public function __destruct() {
+ $GLOBALS['r'] =
unserialize('a:2:{i:0;O:8:"stdClass":0:{}i:1;r:2;}');
+ }
+}
+unserialize('a:2:{i:0;O:1:"D":0:{}i:0;i:1;}');
+var_dump($GLOBALS['r'][0] === $GLOBALS['r'][1]);
+?>
+--EXPECT--
+bool(true)
diff --git a/ext/standard/var.c b/ext/standard/var.c
index a9ef4950af22..8906d914d5dc 100644
--- a/ext/standard/var.c
+++ b/ext/standard/var.c
@@ -1425,12 +1425,13 @@ PHPAPI void
php_var_serialize_set_incomplete_classes_only(php_serialize_data_t d
PHPAPI void php_var_serialize_destroy(php_serialize_data_t d) {
/* fprintf(stderr, "SERIALIZE_DESTROY == lock: %u, level: %u\n", BG(serialize_lock),
BG(serialize).level); */
- if (BG(serialize_lock) || BG(serialize).level == 1) {
+ if (BG(serialize_lock)) {
zend_hash_destroy(&d->ht);
efree(d);
- }
- if (!BG(serialize_lock) && !--BG(serialize).level) {
+ } else if (!--BG(serialize).level) {
BG(serialize).data = NULL;
+ zend_hash_destroy(&d->ht);
+ efree(d);
}
}
diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re
index 43968dd40c9e..e5cc1acd758c 100644
--- a/ext/standard/var_unserializer.re
+++ b/ext/standard/var_unserializer.re
@@ -89,12 +89,13 @@ PHPAPI php_unserialize_data_t php_var_unserialize_init(void) {
PHPAPI void php_var_unserialize_destroy(php_unserialize_data_t d) {
/* fprintf(stderr, "UNSERIALIZE_DESTROY == lock: %u, level: %u\n", BG(serialize_lock),
BG(unserialize).level); */
- if (BG(serialize_lock) || BG(unserialize).level == 1) {
+ if (BG(serialize_lock)) {
var_destroy(&d);
efree(d);
- }
- if (!BG(serialize_lock) && !--BG(unserialize).level) {
+ } else if (!--BG(unserialize).level) {
BG(unserialize).data = NULL;
+ var_destroy(&d);
+ efree(d);
}
}