Skip to content

ext/xml: reject changing handlers while parsing - #23818

Merged
Girgias merged 1 commit into
php:masterfrom
jvoisin:xml_uaf
Sep 21, 2026
Merged

Girgias merged 1 commit into
php:masterfrom
jvoisin:xml_uaf

Conversation

@jvoisin

@jvoisin jvoisin commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

The SAX handler setters store their callback as a zend_fcall_info_cache on the parser and xml_set_handler() releases the previous one with zend_fcc_dtor(). None of the setters checked parser->isparsing, so calling xml_set_element_handler() (or any xml_set_*_handler()) from inside a running handler freed the fcc that was still executing. For a non-closure [$object, 'method'] handler whose object is only referenced by that fcc, the zend_fcc_dtor() releases the last reference and frees $this while its method is still on the stack, leading to a use-after-free.

Guard xml_set_element_handler() and the XML_SET_HANDLER_PHP_FUNCTION setters with the existing isparsing flag and throw "Cannot change handlers while parsing", mirroring the checks already used by xml_parser_set_option() and xml_parse(). The parsed fcc is released on the error path like the other failure branches.

@Girgias Girgias 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.

Can you add a test case?

I agree that this is the correct solution.

The SAX handler setters store their callback as a zend_fcall_info_cache on
the parser and xml_set_handler() releases the previous one with
zend_fcc_dtor(). None of the setters checked parser->isparsing, so calling
xml_set_element_handler() (or any xml_set_*_handler()) from inside a running
handler freed the fcc that was still executing. For a non-closure
[$object, 'method'] handler whose object is only referenced by that fcc, the
zend_fcc_dtor() releases the last reference and frees $this while its method
is still on the stack, leading to a use-after-free.

Guard xml_set_element_handler() and the XML_SET_HANDLER_PHP_FUNCTION setters
with the existing isparsing flag and throw "Cannot change handlers while
parsing", mirroring the checks already used by xml_parser_set_option() and
xml_parse(). The parsed fcc is released on the error path like the other
failure branches.
@jvoisin

jvoisin commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Sure :)

@Girgias
Girgias merged commit 522b1be into php:master Sep 21, 2026
18 checks passed
LamentXU123 added a commit that referenced this pull request Sep 21, 2026
Record the backport of PR #23818 as merged. The original fix is already
in master, so this merge leaves its tree unchanged.

* PHP-8.5:
  Backport: reject XML handler changes while parsing (#23818)
jorgsowa pushed a commit to jorgsowa/php-src that referenced this pull request Sep 21, 2026
Backport PR php#23818 from master to PHP-8.4.
Original commit: 522b1be

The SAX handler setters store their callback as a zend_fcall_info_cache on
the parser and xml_set_handler() releases the previous one with
zend_fcc_dtor(). None of the setters checked parser->isparsing, so calling
xml_set_element_handler() (or any xml_set_*_handler()) from inside a running
handler freed the fcc that was still executing. For a non-closure
[$object, 'method'] handler whose object is only referenced by that fcc, the
zend_fcc_dtor() releases the last reference and frees $this while its method
is still on the stack, leading to a use-after-free.

Guard xml_set_element_handler() and the XML_SET_HANDLER_PHP_FUNCTION setters
with the existing isparsing flag and throw "Cannot change handlers while
parsing", mirroring the checks already used by xml_parser_set_option() and
xml_parse(). The parsed fcc is released on the error path like the other
failure branches.

Closes php#23818
jorgsowa pushed a commit to jorgsowa/php-src that referenced this pull request Sep 21, 2026
Propagate the backport of PR php#23818 from PHP-8.4 to PHP-8.5.

* PHP-8.4:
  Backport: reject XML handler changes while parsing (php#23818)
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.

2 participants