-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Zend: Fix GH-22257 type confusion in Exception::getTraceAsString(). #22263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
05e9698
303ee1b
fd6d35d
2ec1f96
2b8c0a0
eb42872
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| --TEST-- | ||
| GH-22257 (Type confusion / OOB read unserializing an Exception with a non-array trace) | ||
| --CREDITS-- | ||
| Igor Sak-Sakovskiy (Positive Technologies) | ||
| --FILE-- | ||
| <?php | ||
| /* A crafted, deliberately truncated payload makes the nested value of the typed | ||
| * "array $trace" property fail to unserialize. On that failure path the slot used | ||
| * to keep the half-built (non-array) value, and the partially-built Exception was | ||
| * then exposed to getTraceAsString() through SplHeap's delayed __unserialize(), | ||
| * type-confusing the object as a HashTable. The slot is now reset to the property | ||
| * default, so the run completes without an out-of-bounds read. */ | ||
| $n = "\x00"; | ||
| try { | ||
| unserialize( | ||
| 'O:9:"Exception":1:{s:16:"' . $n . 'Exception' . $n . 'trace";' . | ||
| 'O:8:"stdClass":2:{s:1:"0";O:10:"SplMaxHeap":2:{i:0;a:0:{}i:1;a:2:{' . | ||
| 's:5:"flags";i:0;s:13:"heap_elements";a:2:{i:0;s:0:"";i:1;R:1;}}}z}}' | ||
| ); | ||
| } catch (\Throwable $e) { | ||
| for (; $e; $e = $e->getPrevious()) { | ||
| printf("%s: %s\n", $e::class, $e->getMessage()); | ||
| } | ||
| } | ||
| echo "OK\n"; | ||
| ?> | ||
| --EXPECTF-- | ||
| Warning: unserialize(): Error at offset %d of %d bytes in %s on line %d | ||
| OK |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -677,10 +677,17 @@ second_try: | |
| } | ||
|
|
||
| if (!php_var_unserialize_internal(data, p, max, var_hash)) { | ||
| if (info && Z_ISREF_P(data)) { | ||
| /* Add type source even if we failed to unserialize. | ||
| * The data is still stored in the property. */ | ||
| ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(data), info); | ||
| if (info) { | ||
| if (Z_ISREF_P(data)) { | ||
| ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(data), info); | ||
| } else { | ||
| /* "partially unserialized" value might violate the property | ||
| * declared type so we restore the default | ||
| */ | ||
| zval *tmp = &obj->ce->default_properties_table[OBJ_PROP_TO_NUM(info->offset)]; | ||
| zval_ptr_dtor(data); | ||
| ZVAL_COPY_OR_DUP(data, tmp); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: could use
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think |
||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In theory the same problem happens after Edit: the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the if (Z_ISREF_P(data)) case, left it for now since it's gated by EG(exception) too, feels like follow up material, wdyt ?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's fine |
||
| } | ||
| goto failure; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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_valuerather than the inlinezval garbage, it moves the old value into the deferred-dtor list. So nothing is destroyed during the unwind or the delayed calls wdyt ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense