Skip to content

Fix GH-24674: Share the unserialize context only with Serializable - #443

Closed
iliaal wants to merge 1 commit into
masterfrom
fix/gh-24674-unserialize-context
Closed

iliaal wants to merge 1 commit into
masterfrom
fix/gh-24674-unserialize-context

Conversation

@iliaal

@iliaal iliaal commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

php_var_unserialize_init() hands the context of a parse in progress to any caller, so user code that runs mid-parse and calls unserialize() (a destructor from a failed __unserialize() payload or the GC, an error handler, an allowed_classes __toString(), a session_decode() overwrite) resolves R: and r: into the outer value, including values being destroyed.

The context is now shared only with the unserialize handler of a C: payload, and only while serialize_lock is not held, as serialize() does; under the lock the nested call gets its own context with the same allowed_classes and max_depth, which also closes an allowed_classes bypass from __wakeup(). Fiber switches inside that handler now throw FiberError, so this targets master; it applies to PHP-8.4 if a backport is wanted.

php#24433 fixes the reported __unserialize() case on PHP-8.4. php#24551, php#24693 and php#24538 change var_destroy(), where 08f4b64 already fixed phpGH-24481.

php_var_unserialize_init() joined the context of any parse in progress, so
user code that runs mid-parse and calls unserialize() resolved
back-references into the outer value, including values being destroyed: a
destructor run when an __unserialize() payload fails to parse or by the
garbage collector, an error handler for a parser warning, an
allowed_classes __toString(), a session_decode() overwrite. Only the
unserialize handler of a C: payload is meant to share it. object_custom()
now publishes the context for the duration of that call, a nested init
shares it only while serialize_lock is not held, as serialize() does, and a
parse hides it for its own duration. Fiber switches are blocked and a
bailout restores the previous context while it is published. A nested init
under serialize_lock, as in a C: payload unserialized from __wakeup() or
__unserialize(), now keeps the allowed_classes and max_depth of the payload.

Fixes phpGH-24674
@iliaal

iliaal commented Oct 26, 2026

Copy link
Copy Markdown
Owner Author

Promoted upstream: php#24751

@iliaal iliaal closed this Oct 26, 2026
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.

Use-after-free in var_destroy() during unserialize() re-entrancy allows __destruct to read a freed var_entries chunk

1 participant