Skip to content

Zend: Fix GH-22257 type confusion in Exception::getTraceAsString().#22263

Open
devnexen wants to merge 4 commits into
php:masterfrom
devnexen:gh22257
Open

Zend: Fix GH-22257 type confusion in Exception::getTraceAsString().#22263
devnexen wants to merge 4 commits into
php:masterfrom
devnexen:gh22257

Conversation

@devnexen

@devnexen devnexen commented Jun 9, 2026

Copy link
Copy Markdown
Member

A crafted, deliberately truncated unserialize() payload can leave Exception::$trace holding a non-array value, since the typed-property check is skipped on the parse failure path. getTraceAsString() then reinterpreted the object as a HashTable, causing an out-of-bounds read. Guard against a non-array trace and return an empty string instead.

Fix #22257

A crafted, deliberately truncated unserialize() payload can leave
Exception::$trace holding a non-array value, since the typed-property
check is skipped on the parse failure path. getTraceAsString() then
reinterpreted the object as a HashTable, causing an out-of-bounds read.
Guard against a non-array trace and return an empty string instead.

Fix php#22257
@devnexen devnexen changed the title Zend: Fix type confusion in Exception::getTraceAsString(). Zend: Fix GH-22257 type confusion in Exception::getTraceAsString(). Jun 9, 2026
@devnexen devnexen marked this pull request as ready for review June 9, 2026 15:35
@ndossche

ndossche commented Jun 9, 2026

Copy link
Copy Markdown
Member

Shouldn't this throw instead? I don't think it makes sense trying to support/continue operating on maliciously-crafted code.

@devnexen

devnexen commented Jun 9, 2026

Copy link
Copy Markdown
Member Author

yes, you re not wrong.

@012git012

Copy link
Copy Markdown

Hello @ndossche @devnexen
Could you please update the credits section here? (https://github.com/php/php-src/pull/22263/changes)
Instead of this account (012git012) please note the researcher:
Igor Sak-Sakovskiy (Positive Technologies)

Thank you in advance!

@ndossche ndossche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, $trace is defined with type array, so it should not be possible to break that constraint. This should be blocked instead of adding a check in getTraceAsString

@devnexen

Copy link
Copy Markdown
Member Author

To clarify, if I reset to the
property default ([]) rather than undef, the slot stays a valid array and the getTraceAsString check becomes unnecessary. wdyt ?

@ndossche

Copy link
Copy Markdown
Member

To clarify, if I reset to the property default ([]) rather than undef, the slot stays a valid array and the getTraceAsString check becomes unnecessary. wdyt ?

Yeah it should not be allowed to violate the property type so that seems better.

* declared type so we restore the default
*/
zval *tmp = &obj->ce->default_properties_table[OBJ_PROP_TO_NUM(info->offset)];
zval_ptr_dtor(data);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume evil destructors can cause issues here, so a temporary copy may be necessary (i.e. the zval garbage; trick)
I wonder though if "restoring after violation" is the right approach, but that may be a much bigger change

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went with var_push_dtor_value rather than the inline zval garbage, it moves the old value into the deferred-dtor list. So nothing is destroyed during the unwind or the delayed calls wdyt ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Heap Out-of-Bounds Read in SplHeap Unserialize

3 participants