Skip to content

Commit a5d030e

Browse files
committed
Fix GH-9186 @strict-properties can be bypassed using unserialization
1 parent 5f8993b commit a5d030e

20 files changed

+140
-35
lines changed

Zend/tests/gc_043.phpt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ STR;
88
var_dump(unserialize($s));
99
gc_collect_cycles();
1010
?>
11-
--EXPECT--
11+
--EXPECTF--
12+
Deprecated: Creation of dynamic property RegexIterator::$5 is deprecated in %s on line %d
1213
object(stdClass)#1 (2) {
1314
["5"]=>
1415
object(SplStack)#2 (2) {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Fix GH-9186 Readonly classes can have dynamic properties created by unserialize()
3+
--FILE--
4+
<?php
5+
6+
readonly class C {}
7+
8+
try {
9+
$readonly = unserialize('O:1:"C":1:{s:1:"x";b:1;}');
10+
} catch (Error $exception) {
11+
echo $exception->getMessage() . "\n";
12+
}
13+
14+
?>
15+
--EXPECT--
16+
Cannot create dynamic property C::$x

Zend/zend_API.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1586,6 +1586,28 @@ ZEND_API void object_properties_init_ex(zend_object *object, HashTable *properti
15861586
}
15871587
/* }}} */
15881588

1589+
ZEND_API zend_never_inline void zend_forbidden_dynamic_property(zend_class_entry *ce, zend_string *member) {
1590+
zend_throw_error(NULL, "Cannot create dynamic property %s::$%s",
1591+
ZSTR_VAL(ce->name), ZSTR_VAL(member));
1592+
}
1593+
1594+
ZEND_API zend_never_inline bool zend_deprecated_dynamic_property(zend_object *obj, zend_string *member) {
1595+
GC_ADDREF(obj);
1596+
zend_error(E_DEPRECATED, "Creation of dynamic property %s::$%s is deprecated",
1597+
ZSTR_VAL(obj->ce->name), ZSTR_VAL(member));
1598+
if (UNEXPECTED(GC_DELREF(obj) == 0)) {
1599+
zend_class_entry *ce = obj->ce;
1600+
zend_objects_store_del(obj);
1601+
if (!EG(exception)) {
1602+
/* We cannot continue execution and have to throw an exception */
1603+
zend_throw_error(NULL, "Cannot create dynamic property %s::$%s",
1604+
ZSTR_VAL(ce->name), ZSTR_VAL(member));
1605+
}
1606+
return 0;
1607+
}
1608+
return 1;
1609+
}
1610+
15891611
ZEND_API void object_properties_load(zend_object *object, HashTable *properties) /* {{{ */
15901612
{
15911613
zval *prop, tmp;
@@ -1627,13 +1649,31 @@ ZEND_API void object_properties_load(zend_object *object, HashTable *properties)
16271649
zend_hash_update(object->properties, key, &tmp);
16281650
}
16291651
} else {
1652+
if (UNEXPECTED(object->ce->ce_flags & ZEND_ACC_NO_DYNAMIC_PROPERTIES)) {
1653+
zend_forbidden_dynamic_property(object->ce, key);
1654+
return;
1655+
} else if (!(object->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) {
1656+
if (!zend_deprecated_dynamic_property(object, key)) {
1657+
return;
1658+
}
1659+
}
1660+
16301661
if (!object->properties) {
16311662
rebuild_object_properties(object);
16321663
}
16331664
prop = zend_hash_update(object->properties, key, prop);
16341665
zval_add_ref(prop);
16351666
}
16361667
} else {
1668+
if (UNEXPECTED(object->ce->ce_flags & ZEND_ACC_NO_DYNAMIC_PROPERTIES)) {
1669+
zend_forbidden_dynamic_property(object->ce, key);
1670+
return;
1671+
} else if (!(object->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) {
1672+
if (!zend_deprecated_dynamic_property(object, key)) {
1673+
return;
1674+
}
1675+
}
1676+
16371677
if (!object->properties) {
16381678
rebuild_object_properties(object);
16391679
}

Zend/zend_API.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,8 @@ ZEND_API zend_result object_init_ex(zval *arg, zend_class_entry *ce);
522522
ZEND_API zend_result object_and_properties_init(zval *arg, zend_class_entry *ce, HashTable *properties);
523523
ZEND_API void object_properties_init(zend_object *object, zend_class_entry *class_type);
524524
ZEND_API void object_properties_init_ex(zend_object *object, HashTable *properties);
525+
ZEND_API zend_never_inline void zend_forbidden_dynamic_property(zend_class_entry *ce, zend_string *member);
526+
ZEND_API zend_never_inline bool zend_deprecated_dynamic_property(zend_object *obj, zend_string *member);
525527
ZEND_API void object_properties_load(zend_object *object, HashTable *properties);
526528

527529
ZEND_API void zend_merge_properties(zval *obj, HashTable *properties);

Zend/zend_object_handlers.c

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -275,30 +275,6 @@ static ZEND_COLD zend_never_inline void zend_bad_property_name(void) /* {{{ */
275275
}
276276
/* }}} */
277277

278-
static ZEND_COLD zend_never_inline void zend_forbidden_dynamic_property(
279-
zend_class_entry *ce, zend_string *member) {
280-
zend_throw_error(NULL, "Cannot create dynamic property %s::$%s",
281-
ZSTR_VAL(ce->name), ZSTR_VAL(member));
282-
}
283-
284-
static ZEND_COLD zend_never_inline bool zend_deprecated_dynamic_property(
285-
zend_object *obj, zend_string *member) {
286-
GC_ADDREF(obj);
287-
zend_error(E_DEPRECATED, "Creation of dynamic property %s::$%s is deprecated",
288-
ZSTR_VAL(obj->ce->name), ZSTR_VAL(member));
289-
if (UNEXPECTED(GC_DELREF(obj) == 0)) {
290-
zend_class_entry *ce = obj->ce;
291-
zend_objects_store_del(obj);
292-
if (!EG(exception)) {
293-
/* We cannot continue execution and have to throw an exception */
294-
zend_throw_error(NULL, "Cannot create dynamic property %s::$%s",
295-
ZSTR_VAL(ce->name), ZSTR_VAL(member));
296-
}
297-
return 0;
298-
}
299-
return 1;
300-
}
301-
302278
static ZEND_COLD zend_never_inline void zend_readonly_property_modification_scope_error(
303279
zend_class_entry *ce, zend_string *member, zend_class_entry *scope, const char *operation) {
304280
zend_throw_error(NULL, "Cannot %s readonly property %s::$%s from %s%s",

ext/random/engine_mt19937.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,9 @@ PHP_METHOD(Random_Engine_Mt19937, __unserialize)
365365
RETURN_THROWS();
366366
}
367367
object_properties_load(&engine->std, Z_ARRVAL_P(t));
368+
if (EG(exception)) {
369+
RETURN_THROWS();
370+
}
368371

369372
/* state */
370373
t = zend_hash_index_find(d, 1);

ext/random/randomizer.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ PHP_METHOD(Random_Randomizer, __construct)
9090

9191
/* {{{ Generate positive random number */
9292
PHP_METHOD(Random_Randomizer, nextInt)
93-
{
93+
{
9494
php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
9595
uint64_t result;
9696

@@ -104,7 +104,7 @@ PHP_METHOD(Random_Randomizer, nextInt)
104104
zend_throw_exception(random_ce_Random_RandomException, "Generated value exceeds size of int", 0);
105105
RETURN_THROWS();
106106
}
107-
107+
108108
RETURN_LONG((zend_long) (result >> 1));
109109
}
110110
/* }}} */
@@ -278,6 +278,9 @@ PHP_METHOD(Random_Randomizer, __unserialize)
278278
RETURN_THROWS();
279279
}
280280
object_properties_load(&randomizer->std, Z_ARRVAL_P(members_zv));
281+
if (EG(exception)) {
282+
RETURN_THROWS();
283+
}
281284

282285
zengine = zend_read_property(randomizer->std.ce, &randomizer->std, "engine", strlen("engine"), 1, NULL);
283286
if (Z_TYPE_P(zengine) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(zengine), random_ce_Random_Engine)) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Fix GH-9186 @strict-properties can be bypassed using unserialization
3+
--FILE--
4+
<?php
5+
6+
try {
7+
unserialize('O:17:"Random\Randomizer":1:{i:0;a:2:{s:3:"foo";N;s:6:"engine";O:32:"Random\Engine\Xoshiro256StarStar":2:{i:0;a:0:{}i:1;a:4:{i:0;s:16:"7520fbc2d6f8de46";i:1;s:16:"84d2d2b9d7ba0a34";i:2;s:16:"d975f36db6490b32";i:3;s:16:"c19991ee16785b94";}}}}');
8+
} catch (Error $error) {
9+
echo $error->getMessage() . "\n";
10+
}
11+
12+
?>
13+
--EXPECT--
14+
Cannot create dynamic property Random\Randomizer::$foo

ext/spl/spl_array.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,6 +1740,9 @@ PHP_METHOD(ArrayObject, __unserialize)
17401740
}
17411741

17421742
object_properties_load(&intern->std, Z_ARRVAL_P(members_zv));
1743+
if (EG(exception)) {
1744+
RETURN_THROWS();
1745+
}
17431746

17441747
if (iterator_class_zv && Z_TYPE_P(iterator_class_zv) == IS_STRING) {
17451748
zend_class_entry *ce = zend_lookup_class(Z_STR_P(iterator_class_zv));

ext/standard/basic_functions.stub.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99
const M_E = 2.718281828459045;
1010

11+
#[AllowDynamicProperties]
1112
final class __PHP_Incomplete_Class
1213
{
1314
}

ext/standard/basic_functions_arginfo.h

Lines changed: 6 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/standard/tests/serialize/bug49649.phpt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ class Foo
3333
$class = unserialize(base64_decode($serialized));
3434
var_dump($class);
3535
?>
36-
--EXPECT--
36+
--EXPECTF--
37+
Deprecated: Creation of dynamic property Foo::$ is deprecated in %s on line %d
38+
39+
Deprecated: Creation of dynamic property Foo::$ is deprecated in %s on line %d
40+
41+
Deprecated: Creation of dynamic property Foo::$notThere is deprecated in %s on line %d
3742
object(Foo)#1 (4) {
3843
["public"]=>
3944
int(3)

ext/standard/tests/serialize/bug49649_1.phpt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ class Foo
3333
$class = unserialize(base64_decode($serialized));
3434
var_dump($class);
3535
?>
36-
--EXPECT--
36+
--EXPECTF--
37+
Deprecated: Creation of dynamic property Foo::$ is deprecated in %s on line %d
38+
39+
Deprecated: Creation of dynamic property Foo::$public is deprecated in %s on line %d
40+
41+
Deprecated: Creation of dynamic property Foo::$notThere is deprecated in %s on line %d
3742
object(Foo)#1 (4) {
3843
["public":protected]=>
3944
int(3)

ext/standard/tests/serialize/bug49649_2.phpt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ class Foo
3333
$class = unserialize(base64_decode($serialized));
3434
var_dump($class);
3535
?>
36-
--EXPECT--
36+
--EXPECTF--
37+
Deprecated: Creation of dynamic property Foo::$ is deprecated in %s on line %d
38+
39+
Deprecated: Creation of dynamic property Foo::$public is deprecated in %s on line %d
40+
41+
Deprecated: Creation of dynamic property Foo::$notThere is deprecated in %s on line %d
3742
object(Foo)#1 (4) {
3843
["public":"Foo":private]=>
3944
int(3)

ext/standard/tests/serialize/bug62836_1.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Bug #62836 (Seg fault or broken object references on unserialize())
55
$serialized_object='O:1:"A":4:{s:1:"b";O:1:"B":0:{}s:2:"b1";r:2;s:1:"c";O:1:"B":0:{}s:2:"c1";r:4;}';
66
spl_autoload_register(function ($name) {
77
unserialize("i:4;");
8-
eval("class $name {} ");
8+
eval("#[AllowDynamicProperties] class $name {} ");
99
});
1010

1111
print_r(unserialize($serialized_object));

ext/standard/tests/serialize/bug62836_2.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ ini_set('unserialize_callback_func','mycallback');
88

99
function mycallback($classname) {
1010
unserialize("i:4;");
11-
eval ("class $classname {} ");
11+
eval ("#[AllowDynamicProperties] class $classname {} ");
1212
}
1313

1414
print_r(unserialize($serialized_object));

ext/standard/tests/serialize/bug72663.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Bug #72663 (1): Don't call __destruct if __wakeup not called or fails
33
--FILE--
44
<?php
55

6+
#[AllowDynamicProperties]
67
class Test1 {
78
public function __wakeup() {
89
echo "Wakeup\n";
@@ -12,6 +13,7 @@ class Test1 {
1213
}
1314
}
1415

16+
#[AllowDynamicProperties]
1517
class Test2 {
1618
public function __wakeup() {
1719
throw new Exception('Unserialization forbidden');
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Check behaviour of incomplete class
3+
--FILE--
4+
<?php
5+
$incomplete = unserialize('O:1:"C":1:{s:1:"p";i:1;}');
6+
var_dump($incomplete);
7+
8+
?>
9+
--EXPECT--
10+
object(__PHP_Incomplete_Class)#1 (2) {
11+
["__PHP_Incomplete_Class_Name"]=>
12+
string(1) "C"
13+
["p"]=>
14+
int(1)
15+
}

ext/standard/tests/serialize/unserialize_overwrite_undeclared_protected.phpt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ O:4:"Test":2:{s:4:"\0*\0x";N;s:4:"\0*\0x";N;}
1212
STR;
1313
var_dump(unserialize($str));
1414
?>
15-
--EXPECT--
15+
--EXPECTF--
16+
Deprecated: Creation of dynamic property Test::$ is deprecated in %s on line %d
1617
object(Test)#1 (2) {
1718
["foo"]=>
1819
NULL

ext/standard/var_unserializer.re

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,15 @@ declared_property:
638638
}
639639
}
640640
} else {
641+
if (UNEXPECTED(obj->ce->ce_flags & ZEND_ACC_NO_DYNAMIC_PROPERTIES)) {
642+
zend_forbidden_dynamic_property(obj->ce, Z_STR_P(&key));
643+
goto failure;
644+
} else if (!(obj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) {
645+
if (!zend_deprecated_dynamic_property(obj, Z_STR_P(&key))) {
646+
goto failure;
647+
}
648+
}
649+
641650
int ret = is_property_visibility_changed(obj->ce, &key);
642651

643652
if (EXPECTED(!ret)) {

0 commit comments

Comments
 (0)