Skip to content

Commit 60737b3

Browse files
committed
Fix #18542: unserialize() respects class_alias for private properties
When a class with private properties is serialized, the property names are mangled as \0ClassName\0propName. If the class is later aliased via class_alias(), unserialize() fails to match the mangled class name against the actual class entry, causing properties to be treated as dynamic instead of declared. This adds a fallback check in is_property_visibility_changed() that uses zend_hash_str_find_ptr_lc() to look up the mangled class name in EG(class_table) and verify it resolves to the same zend_class_entry. This is only triggered when the direct name comparison fails, so there is zero overhead on the normal (non-alias) path. Closes GH-18542
1 parent b5fe9a1 commit 60737b3

2 files changed

Lines changed: 59 additions & 1 deletion

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
--TEST--
2+
unserialize() respects class_alias for private properties
3+
--FILE--
4+
<?php
5+
6+
// Test 1: Basic class_alias with private property
7+
class HelloAlias {
8+
public function __construct(
9+
private readonly int $answer = 0
10+
) {}
11+
12+
public function getAnswer(): int {
13+
return $this->answer;
14+
}
15+
}
16+
class_alias(HelloAlias::class, 'Hello');
17+
18+
$serialized = 'O:5:"Hello":1:{s:13:"' . "\0Hello\0" . 'answer";i:42;}';
19+
$obj = unserialize($serialized);
20+
var_dump($obj instanceof HelloAlias);
21+
var_dump($obj->getAnswer());
22+
23+
// Test 2: Protected property (should continue to work)
24+
class ProtoAlias {
25+
public function __construct(
26+
protected int $value = 0
27+
) {}
28+
public function getValue(): int { return $this->value; }
29+
}
30+
class_alias(ProtoAlias::class, 'Proto');
31+
32+
$serialized2 = 'O:5:"Proto":1:{s:8:"' . "\0*\0" . 'value";i:99;}';
33+
$obj2 = unserialize($serialized2);
34+
var_dump($obj2 instanceof ProtoAlias);
35+
var_dump($obj2->getValue());
36+
37+
// Test 3: Public property (should continue to work)
38+
class PubAlias {
39+
public int $data = 0;
40+
}
41+
class_alias(PubAlias::class, 'Pub');
42+
43+
$serialized3 = 'O:3:"Pub":1:{s:4:"data";i:77;}';
44+
$obj3 = unserialize($serialized3);
45+
var_dump($obj3 instanceof PubAlias);
46+
var_dump($obj3->data);
47+
48+
echo "Done\n";
49+
?>
50+
--EXPECT--
51+
bool(true)
52+
int(42)
53+
bool(true)
54+
int(99)
55+
bool(true)
56+
int(77)
57+
Done

ext/standard/var_unserializer.re

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,8 @@ static int is_property_visibility_changed(zend_class_entry *ce, zval *key)
546546
existing_propinfo = zend_hash_find_ptr(&ce->properties_info, Z_STR_P(key));
547547
} else {
548548
if (!strcmp(unmangled_class, "*")
549-
|| !strcasecmp(unmangled_class, ZSTR_VAL(ce->name))) {
549+
|| !strcasecmp(unmangled_class, ZSTR_VAL(ce->name))
550+
|| zend_hash_str_find_ptr_lc(EG(class_table), unmangled_class, strlen(unmangled_class)) == ce) {
550551
existing_propinfo = zend_hash_str_find_ptr(
551552
&ce->properties_info, unmangled_prop, unmangled_prop_len);
552553
}

0 commit comments

Comments
 (0)