Repository navigation
Conversation
A part keeps raw pointers to its parent (part->parent) and to its last child (parsedata.lastpart). Since bd4a675 dropped the detaching child destructor, freeing a root while userland still holds a child leaves child->parent dangling, and MimeMessage::get_parent(), further mailparse_msg_parse() calls on the child, and position updates read or write the freed parent. Freeing in the other order is also unsafe: mailparse_msg_free() on a child force-closes it, leaving the parent's lastpart dangling and a closed resource in its children table, so continued parsing hits freed memory and mailparse_msg_get_structure() dereferences NULL. MimeMessage::remove() leaves the same dangling lastpart once the removed part is freed. php_mimepart_free() now clears the parent pointer of every surviving child, php_mimepart_remove_from_parent() clears the parent's lastpart, and mailparse_msg_free() returns true without freeing a part that still has a parent; that part is freed with its root. A child that outlives its parent is reported as a top-level part, as it was before bd4a675. Reported by Firas Fatnassi and Oz Kaiser. The child-detach change and the two outlives-parent tests are based on Oz Kaiser's patch.
This was referenced Oct 31, 2026
Member
|
Merged, thanks |
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.
This fixes a use-after-free reported privately by Firas Fatnassi and Oz Kaiser. Each part keeps a raw
parentpointer, and each parent a rawparsedata.lastpartpointer to its last child. Since bd4a675 dropped the destructor that detached children, a child kept alive bymailparse_msg_get_part()orMimeMessage::get_child()outlives its root with a dangling parent pointer, whichget_parent()reads and furthermailparse_msg_parse()calls on the child write through. Freeing in the other order was broken before 3.1.5 too:mailparse_msg_free()on a child closed it while the parent still pointed at it, andMimeMessage::remove()leaves the same danglinglastpart.php_mimepart_free()now detaches surviving children (Oz Kaiser's patch, together with two of the tests),remove()clears the parent'slastpart, andmailparse_msg_free()on a part that still belongs to a parent returns true without freeing it; the part is freed with its root. Code that frees each part and then the root keeps working, but a child's memory is now held until its root is freed. A child that outlives its parent reports top-level offsets, as it did before 3.1.5. Checked with ASan on PHP 8.4 under both allocators, and against the pre-bd4a675 tree to confirm where the regression came in.