Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
returning `null`.
- On PHP 8.4+, `deepclone_from_array()` checks the shape of the named closures
that lazy objects hold right away, instead of when these are first used.
- `deepclone_from_array()` rejects property names that start with a NUL byte,
which aren't legal PHP, instead of creating them. An array cast can give
such names to a `stdClass`, which `deepclone_to_array()` exports as they
are, like `serialize()`.

## [0.8.6] - 2026-09-29

Expand Down
8 changes: 7 additions & 1 deletion deepclone.c
Original file line number Diff line number Diff line change
Expand Up @@ -4577,7 +4577,7 @@ static bool dc_lazy_index_build(dc_lazy_ctx *ctx, HashTable *properties_ht, Hash
zend_ulong prop_idx;
zval *id_values;
ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(scope_props), prop_idx, prop_name, id_values) {
if (Z_TYPE_P(id_values) != IS_ARRAY) {
if (Z_TYPE_P(id_values) != IS_ARRAY || (prop_name && ZSTR_LEN(prop_name) && ZSTR_VAL(prop_name)[0] == '\0')) {
continue; /* phase 8 reports it */
}
bool prop_is_numeric = (prop_name == NULL);
Expand Down Expand Up @@ -5658,6 +5658,12 @@ PHP_FUNCTION(deepclone_from_array)
EG(fake_scope) = old_scope;
DC_INVALID("deepclone_from_array(): Argument #1 ($data) \"properties\" value for \"%s::%s\" must be of type array, %s given", ZSTR_VAL(scope_name), ZSTR_VAL(prop_name), zend_zval_value_name(id_values));
}
if (UNEXPECTED(ZSTR_LEN(prop_name) && ZSTR_VAL(prop_name)[0] == '\0')) {
/* Not a legal name, which the engine refuses to access,
* even if an array cast can give it to a stdClass */
EG(fake_scope) = old_scope;
DC_INVALID("deepclone_from_array(): Argument #1 ($data) \"properties\" names of scope \"%s\" cannot start with \"\\0\"", ZSTR_VAL(scope_name));
}

/* Get resolve markers for this property */
HashTable *resolve_ids = NULL;
Expand Down
31 changes: 31 additions & 0 deletions tests/deepclone_nul_property_names.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
deepclone_from_array() rejects property names that start with a NUL byte
--EXTENSIONS--
deepclone
--FILE--
<?php

class A
{
public $a;
}

$payloads = [
['classes' => 'stdClass', 'objectMeta' => 1, 'prepared' => 0, 'properties' => ['stdClass' => ["\0x" => [1]]]],
['classes' => 'A', 'objectMeta' => 1, 'prepared' => 0, 'properties' => ['stdClass' => ["\0A\0a" => [1]]]],
// An array cast can give such names to a stdClass
deepclone_to_array((object) ["\0*\0p" => 1]),
];

foreach ($payloads as $payload) {
try {
deepclone_from_array($payload);
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
}
?>
--EXPECT--
deepclone_from_array(): Argument #1 ($data) "properties" names of scope "stdClass" cannot start with "\0"
deepclone_from_array(): Argument #1 ($data) "properties" names of scope "stdClass" cannot start with "\0"
deepclone_from_array(): Argument #1 ($data) "properties" names of scope "stdClass" cannot start with "\0"
Loading