Skip to content

Fix GH-22878: Use-after-free of callable via autoloader#22881

Open
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/gh-22878-callable-uaf
Open

Fix GH-22878: Use-after-free of callable via autoloader#22881
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/gh-22878-callable-uaf

Conversation

@iliaal

@iliaal iliaal commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

A ['Class','method'] or 'Class::method' callable is validated by borrowing the method name and object out of the callable, then resolving the class. Resolving a string class can run an autoloader, and a compound name emits a deprecation that reaches a user error handler; either can free or mutate the callable mid-validation, so the borrowed method string and $this dangle when the method is resolved and the call frame is built. This copies the borrowed string before the reentrant lookup and holds the array across INIT_USER_CALL so the object survives, covering call_user_func(), call_user_func_array(), $cb(), and referenced/reference-wrapped array callables.

Fixes #22878

Validating an array or string callable runs user code before its borrowed
method name and object are used: a string class name can trigger an
autoloader, and a compound "Class::method" name emits an E_DEPRECATED that
reaches a user error handler. Either can free or mutate the callable,
leaving the method string and $this dangling. Copy the method string
before the reentrant lookup, and hold the callable array across
INIT_USER_CALL's validation and frame build so the object survives to the
call. This also covers call_user_func_array(), $cb(), and referenced or
reference-wrapped array members.

Fixes phpGH-22878
@ndossche

Copy link
Copy Markdown
Member

Aren't there already similar workarounds for these kinds of things? I recall a PR to add refcounting to closure objects etc. It's unfortunate that all these protections are:

  • Only necessary for obviously malicious code
  • Spread all across the codebase

@iliaal

iliaal commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

You are not wrong, it is a bit of whack-a-mole.

Same direction as the GH-20001 work, but this one can't fold into it. #22515/#22743 defer the reentrant code (error handlers, destructors); the trigger here is the autoloader, whose result class resolution needs synchronously, so there's nothing to defer, only the borrowed method string and receiver to hold across the lookup. The deprecation variant does overlap #22515. INIT_USER_CALL also builds its frame inline, so #22151's zend_call_function pin doesn't reach it either.

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.

Use-after-free of call_user_func() function name via autoloader

2 participants