Version in base suite: 2.0.9-2 Base version: composer_2.0.9-2 Target version: composer_2.0.9-2+deb11u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/c/composer/composer_2.0.9-2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/c/composer/composer_2.0.9-2+deb11u1.dsc changelog | 8 patches/0010-Merge-pull-request-from-GHSA-x7cr-6qr6-2hh6.patch | 230 ++++++++++ patches/0011-Update-GitHub-token-pattern.patch | 26 + patches/0012-Checkout-ProcessExecutorMock.php-needed-for-updated-.patch | 188 ++++++++ patches/series | 3 5 files changed, 455 insertions(+) diff -Nru composer-2.0.9/debian/changelog composer-2.0.9/debian/changelog --- composer-2.0.9/debian/changelog 2021-04-27 22:20:52.000000000 +0000 +++ composer-2.0.9/debian/changelog 2022-05-29 09:55:56.000000000 +0000 @@ -1,3 +1,11 @@ +composer (2.0.9-2+deb11u1) bullseye; urgency=medium + + * Fix code injection vulnerability [CVE-2022-24828] (Closes: #1009960) + * Update GitHub token pattern (Closes: #989315) + * Checkout ProcessExecutorMock.php needed for updated tests + + -- David Prévot Sun, 29 May 2022 11:55:56 +0200 + composer (2.0.9-2) unstable; urgency=medium * Use debian/bullseye branch diff -Nru composer-2.0.9/debian/patches/0010-Merge-pull-request-from-GHSA-x7cr-6qr6-2hh6.patch composer-2.0.9/debian/patches/0010-Merge-pull-request-from-GHSA-x7cr-6qr6-2hh6.patch --- composer-2.0.9/debian/patches/0010-Merge-pull-request-from-GHSA-x7cr-6qr6-2hh6.patch 1970-01-01 00:00:00.000000000 +0000 +++ composer-2.0.9/debian/patches/0010-Merge-pull-request-from-GHSA-x7cr-6qr6-2hh6.patch 2022-05-29 09:02:54.000000000 +0000 @@ -0,0 +1,230 @@ +From: Stephan +Date: Wed, 13 Apr 2022 14:54:58 +0100 +Subject: Merge pull request from GHSA-x7cr-6qr6-2hh6 + +* GitDriver: filter branch names starting with a - character + +* GitDriver: getFileContent prevent identifiers starting with a - + +* HgDriver: prevent invalid identifiers and prevent file from running commands + +* HgDriver: filter branches starting with a - character + +Origin: backport, https://github.com/composer/composer/commit/2c40c53637c5c7e43fff7c09d3d324d632734709 +Bug-Debian: https://bugs.debian.org/1009960 +Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2022-24828 +--- + src/Composer/Repository/Vcs/GitDriver.php | 6 +- + src/Composer/Repository/Vcs/HgDriver.php | 10 ++- + .../Composer/Test/Repository/Vcs/GitDriverTest.php | 81 ++++++++++++++++++++++ + .../Composer/Test/Repository/Vcs/HgDriverTest.php | 46 ++++++++++++ + 4 files changed, 139 insertions(+), 4 deletions(-) + create mode 100644 tests/Composer/Test/Repository/Vcs/GitDriverTest.php + +diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php +index 70400e8..577477d 100644 +--- a/src/Composer/Repository/Vcs/GitDriver.php ++++ b/src/Composer/Repository/Vcs/GitDriver.php +@@ -130,6 +130,10 @@ class GitDriver extends VcsDriver + */ + public function getFileContent($file, $identifier) + { ++ if (isset($identifier[0]) && $identifier[0] === '-') { ++ throw new \RuntimeException('Invalid git identifier detected. Identifier must not start with a -, given: ' . $identifier); ++ } ++ + $resource = sprintf('%s:%s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); + $this->process->execute(sprintf('git show %s', $resource), $content, $this->repoDir); + +@@ -183,7 +187,7 @@ class GitDriver extends VcsDriver + $this->process->execute('git branch --no-color --no-abbrev -v', $output, $this->repoDir); + foreach ($this->process->splitLines($output) as $branch) { + if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) { +- if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match)) { ++ if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match) && $match[1][0] !== '-') { + $branches[$match[1]] = $match[2]; + } + } +diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php +index f3b0e6c..b154653 100644 +--- a/src/Composer/Repository/Vcs/HgDriver.php ++++ b/src/Composer/Repository/Vcs/HgDriver.php +@@ -121,7 +121,11 @@ class HgDriver extends VcsDriver + */ + public function getFileContent($file, $identifier) + { +- $resource = sprintf('hg cat -r %s %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); ++ if (isset($identifier[0]) && $identifier[0] === '-') { ++ throw new \RuntimeException('Invalid hg identifier detected. Identifier must not start with a -, given: ' . $identifier); ++ } ++ ++ $resource = sprintf('hg cat -r %s -- %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); + $this->process->execute($resource, $content, $this->repoDir); + + if (!trim($content)) { +@@ -181,14 +185,14 @@ class HgDriver extends VcsDriver + + $this->process->execute('hg branches', $output, $this->repoDir); + foreach ($this->process->splitLines($output) as $branch) { +- if ($branch && preg_match('(^([^\s]+)\s+\d+:([a-f0-9]+))', $branch, $match)) { ++ if ($branch && preg_match('(^([^\s]+)\s+\d+:([a-f0-9]+))', $branch, $match) && $match[1][0] !== '-') { + $branches[$match[1]] = $match[2]; + } + } + + $this->process->execute('hg bookmarks', $output, $this->repoDir); + foreach ($this->process->splitLines($output) as $branch) { +- if ($branch && preg_match('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)', $branch, $match)) { ++ if ($branch && preg_match('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)', $branch, $match) && $match[1][0] !== '-') { + $bookmarks[$match[1]] = $match[2]; + } + } +diff --git a/tests/Composer/Test/Repository/Vcs/GitDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitDriverTest.php +new file mode 100644 +index 0000000..eebe5da +--- /dev/null ++++ b/tests/Composer/Test/Repository/Vcs/GitDriverTest.php +@@ -0,0 +1,81 @@ ++home = self::getUniqueTmpDirectory(); ++ $this->config = new Config(); ++ $this->config->merge(array( ++ 'config' => array( ++ 'home' => $this->home, ++ ), ++ )); ++ } ++ ++ public function testGetBranchesFilterInvalidBranchNames() ++ { ++ $process = new ProcessExecutorMock; ++ $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); ++ ++ $driver = new GitDriver(array('url' => 'https://example.org/acme.git'), $io, $this->config, $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), $process); ++ $this->setRepoDir($driver, $this->home); ++ ++ // Branches starting with a - character are not valid git branches names ++ // Still assert that they get filtered to prevent issues later on ++ $stdout = <<expects(array(array( ++ 'cmd' => 'git branch --no-color --no-abbrev -v', ++ 'stdout' => $stdout, ++ ))); ++ ++ $branches = $driver->getBranches(); ++ $this->assertSame(array( ++ 'main' => '089681446ba44d6d9004350192486f2ceb4eaa06', ++ '2.2' => '12681446ba44d6d9004350192486f2ceb4eaa06', ++ ), $branches); ++ } ++ ++ public function testFileGetContentInvalidIdentifier() ++ { ++ $this->expectException('\RuntimeException'); ++ ++ $process = new ProcessExecutorMock; ++ $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); ++ $driver = new GitDriver(array('url' => 'https://example.org/acme.git'), $io, $this->config, $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), $process); ++ ++ $this->assertNull($driver->getFileContent('file.txt', 'h')); ++ ++ $driver->getFileContent('file.txt', '-h'); ++ } ++ ++ /** ++ * @param GitDriver $driver ++ * @param string $path ++ */ ++ private function setRepoDir($driver, $path) ++ { ++ $reflectionClass = new \ReflectionClass($driver); ++ $reflectionProperty = $reflectionClass->getProperty('repoDir'); ++ $reflectionProperty->setAccessible(true); ++ $reflectionProperty->setValue($driver, $path); ++ } ++} +diff --git a/tests/Composer/Test/Repository/Vcs/HgDriverTest.php b/tests/Composer/Test/Repository/Vcs/HgDriverTest.php +index c22439b..50138f0 100644 +--- a/tests/Composer/Test/Repository/Vcs/HgDriverTest.php ++++ b/tests/Composer/Test/Repository/Vcs/HgDriverTest.php +@@ -13,6 +13,7 @@ + namespace Composer\Test\Repository\Vcs; + + use Composer\Repository\Vcs\HgDriver; ++use Composer\Test\Mock\ProcessExecutorMock; + use Composer\Test\TestCase; + use Composer\Util\Filesystem; + use Composer\Config; +@@ -64,4 +65,49 @@ class HgDriverTest extends TestCase + array('https://user@bitbucket.org/user/repo'), + ); + } ++ ++ public function testGetBranchesFilterInvalidBranchNames() ++ { ++ $process = new ProcessExecutorMock; ++ ++ $driver = new HgDriver(array('url' => 'https://example.org/acme.git'), $this->io, $this->config, $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), $process); ++ ++ $stdout = <<expects(array(array( ++ 'cmd' => 'hg branches', ++ 'stdout' => $stdout, ++ ), array( ++ 'cmd' => 'hg bookmarks', ++ 'stdout' => $stdout1, ++ ))); ++ ++ $branches = $driver->getBranches(); ++ $this->assertSame(array( ++ 'help' => 'dbf6c8acb641', ++ 'default' => 'dbf6c8acb640', ++ ), $branches); ++ } ++ ++ public function testFileGetContentInvalidIdentifier() ++ { ++ $this->expectException('\RuntimeException'); ++ ++ $process = new ProcessExecutorMock; ++ $driver = new HgDriver(array('url' => 'https://example.org/acme.git'), $this->io, $this->config, $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), $process); ++ ++ $this->assertNull($driver->getFileContent('file.txt', 'h')); ++ ++ $driver->getFileContent('file.txt', '-h'); ++ } + } diff -Nru composer-2.0.9/debian/patches/0011-Update-GitHub-token-pattern.patch composer-2.0.9/debian/patches/0011-Update-GitHub-token-pattern.patch --- composer-2.0.9/debian/patches/0011-Update-GitHub-token-pattern.patch 1970-01-01 00:00:00.000000000 +0000 +++ composer-2.0.9/debian/patches/0011-Update-GitHub-token-pattern.patch 2022-05-29 09:02:54.000000000 +0000 @@ -0,0 +1,26 @@ +From: Ayesh Karunaratne +Date: Sun, 7 Mar 2021 00:40:32 +0700 +Subject: Update GitHub token pattern + +GitHub is updating the format of auth tokens from `a-z0-9` to `A-Za-z0-9` ([notice](https://github.blog/changelog/2021-03-04-authentication-token-format-updates/)). +I'm not sure why `.` is allowed, but I dare not to remove it. In this PR, the token validation regex is updated to allow `A-Za-z0-9` instead of the current all lower-case `a-z` and disallowed `_`. + +Origin: upstream, https://github.com/composer/composer/commit/dc83ba93f3d8a35629f9a387632e8cd373a144d0 +Bug-Debian: https://bugs.debian.org/989315 +--- + src/Composer/IO/BaseIO.php | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/Composer/IO/BaseIO.php b/src/Composer/IO/BaseIO.php +index e66360e..9c8bf70 100644 +--- a/src/Composer/IO/BaseIO.php ++++ b/src/Composer/IO/BaseIO.php +@@ -124,7 +124,7 @@ abstract class BaseIO implements IOInterface + } + + foreach ($githubOauth as $domain => $token) { +- if (!preg_match('{^[.a-z0-9]+$}', $token)) { ++ if (!preg_match('{^[.A-Za-z0-9_]+$}', $token)) { + throw new \UnexpectedValueException('Your github oauth token for '.$domain.' contains invalid characters: "'.$token.'"'); + } + $this->checkAndSetAuthentication($domain, $token, 'x-oauth-basic'); diff -Nru composer-2.0.9/debian/patches/0012-Checkout-ProcessExecutorMock.php-needed-for-updated-.patch composer-2.0.9/debian/patches/0012-Checkout-ProcessExecutorMock.php-needed-for-updated-.patch --- composer-2.0.9/debian/patches/0012-Checkout-ProcessExecutorMock.php-needed-for-updated-.patch 1970-01-01 00:00:00.000000000 +0000 +++ composer-2.0.9/debian/patches/0012-Checkout-ProcessExecutorMock.php-needed-for-updated-.patch 2022-05-29 09:03:24.000000000 +0000 @@ -0,0 +1,188 @@ +From: =?utf-8?q?David_Pr=C3=A9vot?= +Date: Sun, 29 May 2022 11:02:37 +0200 +Subject: Checkout ProcessExecutorMock.php needed for updated tests + +git checkout 2c40c53637c5c7e43fff7c09d3d324d632734709 tests/Composer/Test/Mock/ProcessExecutorMock.php +--- + tests/Composer/Test/Mock/ProcessExecutorMock.php | 172 +++++++++++++++++++++++ + 1 file changed, 172 insertions(+) + create mode 100644 tests/Composer/Test/Mock/ProcessExecutorMock.php + +diff --git a/tests/Composer/Test/Mock/ProcessExecutorMock.php b/tests/Composer/Test/Mock/ProcessExecutorMock.php +new file mode 100644 +index 0000000..247f815 +--- /dev/null ++++ b/tests/Composer/Test/Mock/ProcessExecutorMock.php +@@ -0,0 +1,172 @@ ++ ++ * Jordi Boggiano ++ * ++ * For the full copyright and license information, please view the LICENSE ++ * file that was distributed with this source code. ++ */ ++ ++namespace Composer\Test\Mock; ++ ++use Composer\Util\ProcessExecutor; ++use Composer\Util\Platform; ++use PHPUnit\Framework\TestCase; ++use PHPUnit\Framework\AssertionFailedError; ++use Symfony\Component\Process\Process; ++use React\Promise\Promise; ++ ++/** ++ * @author Jordi Boggiano ++ */ ++class ProcessExecutorMock extends ProcessExecutor ++{ ++ /** ++ * @var array ++ */ ++ private $expectations = array(); ++ /** ++ * @var bool ++ */ ++ private $strict = false; ++ /** ++ * @var array{return: int, stdout: string, stderr: string} ++ */ ++ private $defaultHandler = array('return' => 0, 'stdout' => '', 'stderr' => ''); ++ /** ++ * @var string[] ++ */ ++ private $log = array(); ++ ++ /** ++ * @param array $expectations ++ * @param bool $strict set to true if you want to provide *all* expected commands, and not just a subset you are interested in testing ++ * @param array{return: int, stdout?: string, stderr?: string} $defaultHandler default command handler for undefined commands if not in strict mode ++ * ++ * @return void ++ */ ++ public function expects(array $expectations, $strict = false, array $defaultHandler = array('return' => 0, 'stdout' => '', 'stderr' => '')) ++ { ++ $default = array('cmd' => '', 'return' => 0, 'stdout' => '', 'stderr' => '', 'callback' => null); ++ $this->expectations = array_map(function ($expect) use ($default) { ++ if (is_string($expect)) { ++ $expect = array('cmd' => $expect); ++ } elseif ($diff = array_diff_key(array_merge($default, $expect), $default)) { ++ throw new \UnexpectedValueException('Unexpected keys in process execution step: '.implode(', ', array_keys($diff))); ++ } ++ ++ return array_merge($default, $expect); ++ }, $expectations); ++ $this->strict = $strict; ++ $this->defaultHandler = array_merge($this->defaultHandler, $defaultHandler); ++ } ++ ++ /** @return void */ ++ public function assertComplete(TestCase $testCase) ++ { ++ if ($this->expectations) { ++ $expectations = array_map(function ($expect) { ++ return $expect['cmd']; ++ }, $this->expectations); ++ throw new AssertionFailedError( ++ 'There are still '.count($this->expectations).' expected process calls which have not been consumed:'.PHP_EOL. ++ implode(PHP_EOL, $expectations).PHP_EOL.PHP_EOL. ++ 'Received calls:'.PHP_EOL.implode(PHP_EOL, $this->log) ++ ); ++ } ++ ++ $testCase->assertTrue(true); ++ } ++ ++ public function execute($command, &$output = null, $cwd = null) ++ { ++ if (func_num_args() > 1) { ++ return $this->doExecute($command, $cwd, false, $output); ++ } ++ ++ return $this->doExecute($command, $cwd, false); ++ } ++ ++ public function executeTty($command, $cwd = null) ++ { ++ if (Platform::isTty()) { ++ return $this->doExecute($command, $cwd, true); ++ } ++ ++ return $this->doExecute($command, $cwd, false); ++ } ++ ++ /** ++ * @param string $command ++ * @param string $cwd ++ * @param bool $tty ++ * @param callable $output ++ * @return mixed ++ */ ++ private function doExecute($command, $cwd, $tty, &$output = null) ++ { ++ $this->captureOutput = func_num_args() > 3; ++ $this->errorOutput = ''; ++ ++ $callback = is_callable($output) ? $output : array($this, 'outputHandler'); ++ ++ $this->log[] = $command; ++ ++ if ($this->expectations && $command === $this->expectations[0]['cmd']) { ++ $expect = array_shift($this->expectations); ++ $stdout = $expect['stdout']; ++ $stderr = $expect['stderr']; ++ $return = $expect['return']; ++ if (isset($expect['callback'])) { ++ call_user_func($expect['callback']); ++ } ++ } elseif (!$this->strict) { ++ $stdout = $this->defaultHandler['stdout']; ++ $stderr = $this->defaultHandler['stderr']; ++ $return = $this->defaultHandler['return']; ++ } else { ++ throw new AssertionFailedError( ++ 'Received unexpected command "'.$command.'" in "'.$cwd.'"'.PHP_EOL. ++ ($this->expectations ? 'Expected "'.$this->expectations[0]['cmd'].'" at this point.' : 'Expected no more calls at this point.').PHP_EOL. ++ 'Received calls:'.PHP_EOL.implode(PHP_EOL, array_slice($this->log, 0, -1)) ++ ); ++ } ++ ++ if ($stdout) { ++ call_user_func($callback, Process::STDOUT, $stdout); ++ } ++ if ($stderr) { ++ call_user_func($callback, Process::ERR, $stderr); ++ } ++ ++ if ($this->captureOutput && !is_callable($output)) { ++ $output = $stdout; ++ } ++ ++ $this->errorOutput = $stderr; ++ ++ return $return; ++ } ++ ++ public function executeAsync($command, $cwd = null) ++ { ++ $resolver = function ($resolve, $reject) { ++ // TODO strictly speaking this should resolve with a mock Process instance here ++ $resolve(); ++ }; ++ ++ $canceler = function () { ++ throw new \RuntimeException('Aborted process'); ++ }; ++ ++ return new Promise($resolver, $canceler); ++ } ++ ++ public function getErrorOutput() ++ { ++ return $this->errorOutput; ++ } ++} diff -Nru composer-2.0.9/debian/patches/series composer-2.0.9/debian/patches/series --- composer-2.0.9/debian/patches/series 2021-04-27 22:18:57.000000000 +0000 +++ composer-2.0.9/debian/patches/series 2022-05-29 09:03:24.000000000 +0000 @@ -7,3 +7,6 @@ 0007-Use-expectException-instead-of-setExpectedException.patch 0008-Compatibility-with-recent-PHPUnit-8.patch 0009-Merge-pull-request-from-GHSA-h5h8-pc6h-jvvx.patch +0010-Merge-pull-request-from-GHSA-x7cr-6qr6-2hh6.patch +0011-Update-GitHub-token-pattern.patch +0012-Checkout-ProcessExecutorMock.php-needed-for-updated-.patch