-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Fix GH-22060 and GH-22122: pin $this in zend_call_function #22151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
iliaal
merged 1 commit into
php:master
from
iliaal:fix/gh-22060-22122-uaf-pin-fcc-object
Sep 21, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| --TEST-- | ||
| GH-22122 (Use-after-free in Pdo\Sqlite authorizer when callback releases the authorizer) | ||
| --EXTENSIONS-- | ||
| pdo_sqlite | ||
| --FILE-- | ||
| <?php | ||
| $db = Pdo\Sqlite::connect('sqlite::memory:'); | ||
|
|
||
| class Auth { | ||
| public string $state = "alive"; | ||
|
|
||
| public function authorize(int $action, ...$args): int { | ||
| global $db; | ||
| $db->setAuthorizer(null); | ||
| echo "method: ", $this->state, "\n"; | ||
| return Pdo\Sqlite::OK; | ||
| } | ||
| } | ||
| $auth = new Auth(); | ||
| $db->setAuthorizer([$auth, 'authorize']); | ||
| unset($auth); | ||
| $db->exec('SELECT 1'); | ||
|
|
||
| $capture = "closure-alive"; | ||
| $closure = function (int $action, ...$args) use (&$capture, $db): int { | ||
| $db->setAuthorizer(null); | ||
| echo "closure: ", $capture, "\n"; | ||
| return Pdo\Sqlite::OK; | ||
| }; | ||
| $db->setAuthorizer($closure); | ||
| unset($closure); | ||
| $db->exec('SELECT 2'); | ||
|
|
||
| $db->exec('SELECT 3'); | ||
| echo "post-disable query ok\n"; | ||
| ?> | ||
| --EXPECT-- | ||
| method: alive | ||
| closure: closure-alive | ||
| post-disable query ok |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| --TEST-- | ||
| GH-22060 (Class autoloader $this freed via spl_autoload_unregister during dispatch) | ||
| --FILE-- | ||
| <?php | ||
|
|
||
| class Loader { | ||
| public string $data = "loader-data"; | ||
|
|
||
| public function load(string $class): void { | ||
| spl_autoload_unregister([$this, 'load']); | ||
| echo $this->data, "\n"; | ||
| } | ||
| } | ||
|
|
||
| $obj = new Loader(); | ||
| spl_autoload_register([$obj, 'load']); | ||
| unset($obj); | ||
|
|
||
| try { | ||
| new NonExistentClass42(); | ||
| } catch (\Throwable $e) { | ||
| echo $e::class, ": ", $e->getMessage(), "\n"; | ||
| } | ||
| ?> | ||
| --EXPECT-- | ||
| loader-data | ||
| Error: Class "NonExistentClass42" not found |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| --TEST-- | ||
| GH-22060 (Autoloader $this freed by the error handler of its own deprecation notice) | ||
| --FILE-- | ||
| <?php | ||
|
|
||
| class Loader { | ||
| public string $data = "loader-data"; | ||
|
|
||
| #[\Deprecated] | ||
| public function load(string $class): void { | ||
| echo $this->data, "\n"; | ||
| } | ||
| } | ||
|
|
||
| $obj = new Loader(); | ||
| spl_autoload_register([$obj, 'load']); | ||
| unset($obj); | ||
|
|
||
| set_error_handler(function (int $no, string $str): bool { | ||
| echo $str, "\n"; | ||
| foreach (spl_autoload_functions() as $loader) { | ||
| spl_autoload_unregister($loader); | ||
| } | ||
| return true; | ||
| }); | ||
|
|
||
| try { | ||
| new NonExistentClass42(); | ||
| } catch (\Throwable $e) { | ||
| echo $e::class, ": ", $e->getMessage(), "\n"; | ||
| } | ||
| ?> | ||
| --EXPECT-- | ||
| Method Loader::load() is deprecated | ||
| loader-data | ||
| Error: Class "NonExistentClass42" not found |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| --TEST-- | ||
| GH-22122 (Use-after-free in SQLite3 authorizer when callback releases the authorizer) | ||
| --EXTENSIONS-- | ||
| sqlite3 | ||
| --FILE-- | ||
| <?php | ||
| $db = new SQLite3(':memory:'); | ||
|
|
||
| class Auth { | ||
| public string $state = "alive"; | ||
|
|
||
| public function authorize(int $action, ...$args): int { | ||
| global $db; | ||
| $db->setAuthorizer(null); | ||
| echo "method: ", $this->state, "\n"; | ||
| return SQLite3::OK; | ||
| } | ||
| } | ||
| $auth = new Auth(); | ||
| $db->setAuthorizer([$auth, 'authorize']); | ||
| unset($auth); | ||
| $db->exec('SELECT 1'); | ||
|
|
||
| $capture = "closure-alive"; | ||
| $closure = function (int $action, ...$args) use (&$capture, $db): int { | ||
| $db->setAuthorizer(null); | ||
| echo "closure: ", $capture, "\n"; | ||
| return SQLite3::OK; | ||
| }; | ||
| $db->setAuthorizer($closure); | ||
| unset($closure); | ||
| $db->exec('SELECT 2'); | ||
|
|
||
| $db->exec('SELECT 3'); | ||
| echo "post-disable query ok\n"; | ||
| ?> | ||
| --EXPECT-- | ||
| method: alive | ||
| closure: closure-alive | ||
| post-disable query ok |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aside: it seems
zend_release_fcall_info_cachemight be missing here if__call()has a deprecate attribute?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and the same is missing from the
zend_handle_undef_args()return further down. Both predate this PR: 256 bytes a call on master, with a#[\Deprecated] __call()for the first and[Closure::fromCallable('strcmp'), '__invoke']called with only$string2for the second.Separate PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so, as those probably should be backported as its a mem leak