Skip to content

ext/xsl: Fix use-after-free when an XSLT callback modifies the source document - #25358

Draft
iliaal wants to merge 3 commits into
php:masterfrom
iliaal:fix/xpath-callback-mutation-refuse-xsl
Draft

iliaal wants to merge 3 commits into
php:masterfrom
iliaal:fix/xpath-callback-mutation-refuse-xsl

Conversation

@iliaal

@iliaal iliaal commented Nov 10, 2026

Copy link
Copy Markdown
Member

Depends on #25355 and #25357, whose commits this branch carries.

A php:function() callback, entity loader or error handler runs while libxslt walks the source tree, so modifying that tree frees nodes the transformation still uses. Make the source tree read-only for every transformation, and protect callback-returned and detached trees as #25357 does for DOMXPath. ext/xsl/tests/bug69168.phpt and cloneDocument.phpt change because their callbacks modified the source document; setting cloneDocument to true keeps the original modifiable.

php_xsl_apply_stylesheet() keeps the transformation's document reference in
intern->doc and the callback node wrappers in the processor's node list, so a
second transformation on the same processor, started from a php:function()
callback or from main code while a Fiber is suspended inside one, freed that
state under the running transformation. Throw an Error for such a call, as
importStylesheet() already does, and restore transform_depth when a callback
bails out so that the processor remains usable in shutdown functions.
A php:function() callback runs while libxml2 builds an XPath node-set, so
modifying the tree or dropping the last reference to a detached subtree
freed nodes the evaluation still referenced. While DOMXPath or Dom\XPath
evaluates with PHP functions registered, the tree of the evaluated document,
the tree of every node a callback returned and the detached subtrees of the
context node and of callback arguments are read-only: modifying them throws
a DOMException with NO_MODIFICATION_ALLOWED_ERR or, through SimpleXML, an
Error, and the evaluation keeps those subtrees alive until it ends. New
nodes and document properties stay modifiable.
… document

A php:function() callback, or an entity loader or error handler, runs while
libxslt walks the source tree, so modifying the tree or dropping the last
reference to a detached subtree freed nodes the transformation still
referenced. Make the source tree read-only for the duration of every
transformation, and arm the XPath callback registry so that, as for
DOMXPath, the trees of returned nodes and the detached subtrees of the
current node and of callback arguments are read-only and kept alive until
the transformation ends.
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.

1 participant