From bcab1c4b8ecf7b239dd9e08fc07c5458cf80cb0a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 29 Apr 2024 11:41:33 +0200 Subject: [PATCH] Fix Composer autoloader being hijackable by script/plugin event handlers (#11955) --- phpstan/baseline-8.1.neon | 5 --- phpstan/baseline.neon | 29 ++++++-------- .../EventDispatcher/EventDispatcher.php | 38 +++++++++++++++++++ 3 files changed, 50 insertions(+), 22 deletions(-) diff --git a/phpstan/baseline-8.1.neon b/phpstan/baseline-8.1.neon index 2090b8e2b..1a2a74b81 100644 --- a/phpstan/baseline-8.1.neon +++ b/phpstan/baseline-8.1.neon @@ -180,11 +180,6 @@ parameters: count: 2 path: ../src/Composer/Util/Hg.php - - - message: "#^Only booleans are allowed in &&, int\\<0, 2097152\\> given on the right side\\.$#" - count: 1 - path: ../src/Composer/Util/Http/CurlDownloader.php - - message: "#^Parameter \\#1 \\$multi_handle of function curl_multi_add_handle expects CurlMultiHandle, resource\\|null given\\.$#" count: 1 diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 801dd2336..d207f39d1 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -332,7 +332,7 @@ parameters: - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 4 + count: 3 path: ../src/Composer/Command/DiagnoseCommand.php - @@ -1850,6 +1850,11 @@ parameters: count: 1 path: ../src/Composer/Downloader/ZipDownloader.php + - + message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" + count: 2 + path: ../src/Composer/EventDispatcher/EventDispatcher.php + - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" count: 1 @@ -4000,7 +4005,7 @@ parameters: - message: "#^Cannot access offset 'features' on array\\|false\\.$#" - count: 2 + count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php - @@ -4010,11 +4015,6 @@ parameters: - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 3 - path: ../src/Composer/Util/Http/CurlDownloader.php - - - - message: "#^Only booleans are allowed in &&, int given on the right side\\.$#" count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php @@ -4188,11 +4188,6 @@ parameters: count: 1 path: ../src/Composer/Util/Http/CurlDownloader.php - - - message: "#^Method Composer\\\\Util\\\\Http\\\\RequestProxy::getCurlOptions\\(\\) should return array\\ but returns array\\.$#" - count: 1 - path: ../src/Composer/Util/Http/RequestProxy.php - - message: "#^Constant CURLOPT_PROXY_CAINFO not found\\.$#" count: 1 @@ -4203,6 +4198,11 @@ parameters: count: 1 path: ../src/Composer/Util/Http/RequestProxy.php + - + message: "#^Method Composer\\\\Util\\\\Http\\\\RequestProxy\\:\\:getCurlOptions\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: ../src/Composer/Util/Http/RequestProxy.php + - message: "#^Cannot call method abortRequest\\(\\) on Composer\\\\Util\\\\Http\\\\CurlDownloader\\|null\\.$#" count: 1 @@ -4618,11 +4618,6 @@ parameters: count: 1 path: ../src/Composer/Util/StreamContextFactory.php - - - message: "#^Only booleans are allowed in an if condition, array given\\.$#" - count: 1 - path: ../src/Composer/Util/StreamContextFactory.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 2 diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index b91b4e8ed..2fabbbead 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -194,6 +194,8 @@ class EventDispatcher $this->pushEvent($event); + $autoloadersBefore = spl_autoload_functions(); + try { $returnMax = 0; foreach ($listeners as $callable) { @@ -411,6 +413,26 @@ class EventDispatcher } } finally { $this->popEvent(); + + $knownIdentifiers = []; + foreach ($autoloadersBefore as $key => $cb) { + $knownIdentifiers[$this->getCallbackIdentifier($cb)] = ['key' => $key, 'callback' => $cb]; + } + foreach (spl_autoload_functions() as $cb) { + // once we get to the first known autoloader, we can leave any appended autoloader without problems + if (isset($knownIdentifiers[$this->getCallbackIdentifier($cb)]) && $knownIdentifiers[$this->getCallbackIdentifier($cb)]['key'] === 0) { + break; + } + + // other newly appeared prepended autoloaders should be appended instead to ensure Composer loads its classes first + if ($cb instanceof ClassLoader) { + $cb->unregister(); + $cb->register(false); + } else { + spl_autoload_unregister($cb); + spl_autoload_register($cb); + } + } } return $returnMax; @@ -638,4 +660,20 @@ class EventDispatcher } } } + + private function getCallbackIdentifier(callable $cb): string + { + if (is_string($cb)) { + return 'fn:'.$cb; + } + if (is_object($cb)) { + return 'obj:'.spl_object_hash($cb); + } + if (is_array($cb)) { + return 'array:'.(is_string($cb[0]) ? $cb[0] : get_class($cb[0]) .'#'.spl_object_hash($cb[0])).'::'.$cb[1]; + } + + // not great but also do not want to break everything here + return 'unsupported'; + } }