Skip to content

Refuse to free, parse into, or re-extract a part during extraction - #78

Closed
iliaal wants to merge 2 commits into
php:masterfrom
iliaal:harden-free-during-extract
Closed

iliaal wants to merge 2 commits into
php:masterfrom
iliaal:harden-free-during-extract

Conversation

@iliaal

@iliaal iliaal commented Oct 31, 2026

Copy link
Copy Markdown
Member

Extraction calls back into userland while it still works on the part: the extract callback, a user stream wrapper used as source or destination, and output handlers all run mid-extraction. From there, mailparse_msg_free() on the part left extract_part() and MimeMessage::extract_uue() reading freed memory. Extracting the same part again replaced the outer call's decoder state and callback context. mailparse_msg_parse() into the part reallocated parsedata.workbuf while a chunked stream write was still reading out of it.

A part is now marked busy for the duration of extract_part(), extract_uue() and enum_uue(); freeing it, parsing into it, or extracting it again now warns and returns false. Freeing an ancestor or extracting a different part from a callback still works. A fatal error raised mid-extraction leaves the flag set, so a shutdown function's mailparse_msg_free() on that part is refused; the resource destructor still frees it. fclose() on the source or destination stream from userland mid-extraction is a stream-layer issue and is not addressed here. This depends on #75 and #77; only the last commit is new here.

@iliaal
iliaal requested a review from remicollet October 31, 2026 15:53
@remicollet

Copy link
Copy Markdown
Member

Please rebase after #75

mailparse_msg_free() passed any resource to zend_list_close(), so
mailparse_msg_free($stream) closed the stream. Fetch the resource as a
mailparse message first, as the other mailparse_msg_*() functions do. An
already closed resource still returns true.
Extraction calls back into userland (the extract callback, user stream
wrappers, output handlers) while it still uses the part. Freeing the part
from there with mailparse_msg_free() left extract_part() and
MimeMessage::extract_uue() reading the freed part, and extracting the same
part again replaced its decoder state and callback context under the outer
call. Parsing into the part from there reallocated parsedata.workbuf while
a stream write was still reading the decoded data out of it.

Mark a part as busy for the duration of extract_part(), extract_uue() and
enum_uue(). mailparse_msg_free(), mailparse_msg_parse() and a nested
extraction of that part now warn and return false.
@iliaal
iliaal force-pushed the harden-free-during-extract branch from abb0ab7 to e8d33eb Compare November 6, 2026 13:20
@iliaal

iliaal commented Nov 6, 2026

Copy link
Copy Markdown
Member Author

Rebased

@remicollet

Copy link
Copy Markdown
Member

Amended and merged
Thanks

Will see to publish a 3.2.1 soon
(PR #52 will be a API change, so will be a 3.3.0 or 4.0.0 later)

@remicollet remicollet closed this Nov 6, 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.

2 participants