Bind free list shadow pointers to the slot holding them - #23376
Open
jvoisin wants to merge 1 commit into
Open
Conversation
The shadow of a free list pointer is currently BSWAP(next) ^ shadow_key,
which does not depend on where it is stored, meaning that:
- zend_mm_free_small() encodes whatever heap->free_slot[bin] happens to
be, including NULL when the bin has been drained, meaning
the slot ends up holding shadow_key verbatim.
- A (next, shadow) pair harvested from one free slot is valid in every
other slot of every bin. An attacker who can read one free slot can
therefore forge a link anywhere in the heap without needing the key.
This commit adds the address of the slot that holds the shadow into the mix:
shadow = BSWAP(next) ^ shadow_key ^ (uintptr_t)holder
The holder term cancels on decode, so this is one extra xor on a register
that is already live, with no branch. Encoding NULL now yields
shadow_key ^ holder rather than the key, and a shadow only verifies in the
slot it was written for.
This was verified under GDB: Freeing into a drained bin used to store shadow_key
exactly; it now stores shadow_key ^ holder (xoring the two back gives the
slot address). Naïvely replaying a valid (next, shadow) pair from one slot into
another and traversing from it is accepted before this change and aborts
with "zend_mm_heap corrupted" after.
Performance-wise, the impact is in the noise level, which is expected as it more
or less adds a single `xor` instruction per `zend_mm_set_next_free_slot()`.
This commit is a follow up on 25360ef and c561f7d.
Contributor
Author
|
Part of #14083 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The shadow of a free list pointer is currently BSWAP(next) ^ shadow_key, which does not depend on where it is stored, meaning that:
This commit adds the address of the slot that holds the shadow into the mix:
The holder term cancels on decode, so this is one extra xor on a register that is already live, with no branch. Encoding NULL now yields shadow_key ^ holder rather than the key, and a shadow only verifies in the slot it was written for.
This was verified under GDB: Freeing into a drained bin used to store shadow_key exactly; it now stores shadow_key ^ holder (xoring the two back gives the slot address). Naïvely replaying a valid (next, shadow) pair from one slot into another and traversing from it is accepted before this change and aborts with "zend_mm_heap corrupted" after.
Performance-wise, the impact is in the noise level, which is expected as it more or less adds a single
xorinstruction perzend_mm_set_next_free_slot().This commit is a follow up on 25360ef and c561f7d.