Skip to content

Fix GH-25263: Use-after-free of __call trampoline when cloning CurlHandle - #25273

Open
lazerg wants to merge 1 commit into
php:PHP-8.4from
lazerg:fix/gh-25263-curl-clone-trampoline
Open

lazerg wants to merge 1 commit into
php:PHP-8.4from
lazerg:fix/gh-25263-curl-clone-trampoline

Conversation

@lazerg

@lazerg lazerg commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

When a callback is a __call trampoline, zend_fcc_addref() moves it into a heap copy that the handle's FCC owns. Cloning the handle used zend_fcc_dup(), which only copies the pointer, so both handles shared that one copy: freeing the first handle freed it, and the second one then used it after free and freed it again. The clone now gets its own copy of the trampoline, the same way zend_call_known_fcc() does it.

Fixes GH-25263

@ndossche

ndossche commented Nov 8, 2026

Copy link
Copy Markdown
Member

@Girgias This will work but I wonder if this is a more general problem?

@lazerg

lazerg commented Nov 8, 2026

Copy link
Copy Markdown
Contributor Author

I checked and it is specific to curl: the copy handlers here are the only zend_fcc_dup() caller that copies from an FCC that another object still owns. All the other callers (sqlite3, pdo_sqlite, xml, libxml, dom xpath, pdo_pgsql, lazy objects, and on master also readline, zip, ldap and SAPI) dup a fresh FCC from ZPP and never release it, so for them the dup is a move. That is why I left zend_fcc_dup() alone. I tried making it copy heap trampolines, and then SQLite3::createAggregate() and xml_set_element_handler() with two __call callbacks each leak the second trampoline, because the first one already took EG(trampoline).

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