Skip to content

deepclone_from_array: reject building an uninitialized object#22

Merged
nicolas-grekas merged 1 commit into
mainfrom
from-array-reject-uninitialized
Jun 10, 2026
Merged

deepclone_from_array: reject building an uninitialized object#22
nicolas-grekas merged 1 commit into
mainfrom
from-array-reject-uninitialized

Conversation

@nicolas-grekas

Copy link
Copy Markdown
Member

deepclone_to_array() always emits a class that has __unserialize() as a negative-wakeup state replay. A hand-crafted from_array() payload that instead flags such a class for plain creation (wakeup >= 0, no replay) made the decoder build a bare object_init_ex() shell that __unserialize() never initialized. For BcMath\Number that shell has a NULL bc_num, so (string), clone, arithmetic, etc. crash — a crafted-payload segfault, surfaced by the v0.6.1 BcMath\Number test on PHP 8.4+ (where there is no engine guard).

from_array() now rejects such a payload with a \ValueError before the object is created, so no uninitialized shell is ever built. Well-formed payloads (which always carry the negative-wakeup replay for an __unserialize class) are unaffected.

Context: the original test asserted a php-src-level guard message, but that guard (php/php-src#22259) was declined — an uninitialized BcMath\Number is unreachable from userland, so php-src won't add code to work around an extension creating one. The fix belongs here: the extension must not produce invalid objects. The test now asserts the \ValueError rejection, which holds on every PHP version with no guard dependency.

Verified the full suite against a stock (unguarded) PHP build. Companion to #20 / symfony/symfony#64323.

deepclone_to_array() always emits a class that has __unserialize() as a
negative-wakeup state replay. A hand-crafted payload that instead flags
such a class for plain creation (wakeup >= 0, no replay) made from_array()
build a bare object_init_ex() shell that __unserialize() never
initialized — for BcMath\Number that shell has a NULL bc_num, so any
operation on it crashed.

Reject such a payload with a ValueError before creating the object, so no
uninitialized shell is ever built. A php-src guard for the bcmath case
(php/php-src#22259) was declined: an uninitialized BcMath\Number cannot be
reached from userland, so the extension must not produce one. The test
that exercised the crash path now asserts the rejection instead.
@nicolas-grekas nicolas-grekas merged commit 4cfcc8e into main Jun 10, 2026
19 checks passed
@nicolas-grekas nicolas-grekas deleted the from-array-reject-uninitialized branch June 10, 2026 17:54
nicolas-grekas added a commit to symfony/polyfill that referenced this pull request Jun 10, 2026
…ninitialized object (nicolas-grekas)

This PR was merged into the 1.x branch.

Discussion
----------

[DeepClone] Reject from_array payloads that would build an uninitialized object

| Q             | A
| ------------- | ---
| Branch?       | 1.x
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Issues        | -
| License       | MIT

Brings the polyfill in line with the extension's symfony/php-ext-deepclone#22.

`deepclone_to_array()` always emits a class that has `__unserialize()` as a negative-wakeup state replay. A crafted `deepclone_from_array()` payload that flags such a class for plain creation (`wakeup >= 0`, no replay) was reconstructed as an uninitialized object: the polyfill returned `null`, while the extension (before #22) built a bare shell, e.g. a `BcMath\Number` whose `bc_num` stays `NULL` and crashes on use.

`reconstruct()` now rejects such a payload with a `\ValueError` before building the object, using the same message as the extension. Well-formed payloads, which always carry the negative-wakeup replay for an `__unserialize` class, are unaffected.

Commits
-------

3791449 [DeepClone] Reject from_array payloads that would build an uninitialized object
symfony-splitter pushed a commit to symfony/polyfill-deepclone that referenced this pull request Jun 10, 2026
…zed object

deepclone_to_array() always emits a class that has __unserialize() as a
negative-wakeup state replay. A crafted deepclone_from_array() payload
that flags such a class for plain creation (wakeup >= 0, no replay) was
reconstructed as an uninitialized object: the polyfill returned null.

reconstruct() now rejects such a payload with a \ValueError before
building the object, matching the extension (symfony/php-ext-deepclone#22).
Well-formed payloads always carry the negative-wakeup replay for an
__unserialize class, so they are unaffected.
IonBazan pushed a commit to IonBazan/polyfill that referenced this pull request Jun 16, 2026
…zed object

deepclone_to_array() always emits a class that has __unserialize() as a
negative-wakeup state replay. A crafted deepclone_from_array() payload
that flags such a class for plain creation (wakeup >= 0, no replay) was
reconstructed as an uninitialized object: the polyfill returned null.

reconstruct() now rejects such a payload with a \ValueError before
building the object, matching the extension (symfony/php-ext-deepclone#22).
Well-formed payloads always carry the negative-wakeup replay for an
__unserialize class, so they are unaffected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant