Skip to content

Commit b4a63ec

Browse files
nicolas-grekasndossche
authored andcommitted
ext/standard: Don't share the unserialize() context with enum autoloaders
Unlike the O: branch, the E: branch looked the class up and evaluated the case value without BG(serialize_lock). An unserialize() call made by the autoloader then reused the outer context: the back-references of the outer payload resolved to the values it created, and its __unserialize() calls were delayed until the outer call returned. Closes GH-23981.
1 parent e31268f commit b4a63ec

3 files changed

Lines changed: 46 additions & 1 deletion

File tree

‎NEWS‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ PHP NEWS
108108
bcrypt costs. (Ilia Alshanetsky)
109109
. Fixed proc_open() null descriptor not being inheritable on Windows.
110110
(Jorg Sowa)
111+
. Don't share the unserialize() context with enum autoloaders.
112+
(Nicolas Grekas)
111113

112114
- Tidy:
113115
. Fixed tidy_repair_string(), tidy_repair_file(), tidy::repairString() and
@@ -360,6 +362,8 @@ PHP NEWS
360362
(Nicolas Grekas)
361363
. Fixed convert.quoted-printable-decode decoding lowercase hex digits to
362364
the wrong byte. (Ilia Alshanetsky)
365+
. Fixed unserialize() of enums sharing its state with the unserialize()
366+
calls made by autoloaders. (nicolas-grekas)
363367

364368
- Tidy:
365369
. Fixed a use-after-free when a tidyNode is used after its document is
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
Enum unserialize runs the autoloader outside of the current unserialize() context
3+
--FILE--
4+
<?php
5+
6+
class Init {
7+
public bool $ready = false;
8+
9+
public function __unserialize(array $data): void {
10+
$this->ready = true;
11+
}
12+
}
13+
14+
enum Backed: string {
15+
case Foo = Value::FOO;
16+
}
17+
18+
spl_autoload_register(function ($class) {
19+
var_dump(unserialize('O:4:"Init":0:{}')->ready);
20+
eval(match ($class) {
21+
'Pure' => 'enum Pure { case Foo; }',
22+
'Value' => 'class Value { const FOO = "foo"; }',
23+
});
24+
});
25+
26+
foreach (['E:8:"Pure:Foo";', 'E:10:"Backed:Foo";'] as $enum) {
27+
$data = unserialize('a:3:{i:0;' . $enum . 'i:1;O:8:"stdClass":0:{}i:2;r:3;}');
28+
var_dump($data[1] === $data[2]);
29+
}
30+
31+
?>
32+
--EXPECT--
33+
bool(true)
34+
bool(true)
35+
bool(true)
36+
bool(true)

‎ext/standard/var_unserializer.re‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1384,7 +1384,9 @@ object ":" uiv ":" ["] {
13841384
goto fail;
13851385
}
13861386
1387+
BG(serialize_lock)++;
13871388
zend_class_entry *ce = zend_lookup_class(enum_name);
1389+
BG(serialize_lock)--;
13881390
if (!ce) {
13891391
php_error_docref(NULL, E_WARNING, "Class '%s' not found", ZSTR_VAL(enum_name));
13901392
goto fail;
@@ -1413,7 +1415,10 @@ object ":" uiv ":" ["] {
14131415
14141416
zval *value = &c->value;
14151417
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
1416-
if (zval_update_constant_ex(value, c->ce) == FAILURE) {
1418+
BG(serialize_lock)++;
1419+
zend_result result = zval_update_constant_ex(value, c->ce);
1420+
BG(serialize_lock)--;
1421+
if (result == FAILURE) {
14171422
return 0;
14181423
}
14191424
}

0 commit comments

Comments
 (0)