Merge pull request from GHSA-7c6p-848j-wh5h
* Fix usage of possibly compromised installed.php/InstalledVersions.php at runtime, refs GHSA-7c6p-848j-wh5h * Fix InstalledVersionsTest regressionpull/11842/head
parent
7442981364
commit
64e4eb356b
|
@ -18,6 +18,7 @@ use Composer\IO\IOInterface;
|
|||
use Composer\Package\Archiver;
|
||||
use Composer\Package\Version\VersionGuesser;
|
||||
use Composer\Package\RootPackageInterface;
|
||||
use Composer\Repository\FilesystemRepository;
|
||||
use Composer\Repository\RepositoryManager;
|
||||
use Composer\Repository\RepositoryFactory;
|
||||
use Composer\Util\Filesystem;
|
||||
|
@ -351,8 +352,13 @@ class Factory
|
|||
$io->loadConfiguration($config);
|
||||
|
||||
// load existing Composer\InstalledVersions instance if available and scripts/plugins are allowed, as they might need it
|
||||
if (false === $disablePlugins && false === $disableScripts && !class_exists('Composer\InstalledVersions', false) && file_exists($installedVersionsPath = $config->get('vendor-dir').'/composer/InstalledVersions.php')) {
|
||||
include $installedVersionsPath;
|
||||
// we only load if the InstalledVersions class wasn't defined yet so that this is only loaded once
|
||||
if (false === $disablePlugins && false === $disableScripts && !class_exists('Composer\InstalledVersions', false) && file_exists($installedVersionsPath = $config->get('vendor-dir').'/composer/installed.php')) {
|
||||
// force loading the class at this point so it is loaded from the composer phar and not from the vendor dir
|
||||
// as we cannot guarantee integrity of that file
|
||||
if (class_exists('Composer\InstalledVersions')) {
|
||||
FilesystemRepository::safelyLoadInstalledVersions($installedVersionsPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ use Composer\Package\RootPackageInterface;
|
|||
use Composer\Package\AliasPackage;
|
||||
use Composer\Package\Dumper\ArrayDumper;
|
||||
use Composer\Installer\InstallationManager;
|
||||
use Composer\Pcre\Preg;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Platform;
|
||||
|
||||
|
@ -173,6 +174,34 @@ class FilesystemRepository extends WritableArrayRepository
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* As we load the file from vendor dir during bootstrap, we need to make sure it contains only expected code before executing it
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function safelyLoadInstalledVersions(string $path): bool
|
||||
{
|
||||
$installedVersionsData = @file_get_contents($path);
|
||||
$pattern = <<<'REGEX'
|
||||
{(?(DEFINE)
|
||||
(?<number> -? \s*+ \d++ (?:\.\d++)? )
|
||||
(?<boolean> true | false | null )
|
||||
(?<strings> (?&string) (?: \s*+ \. \s*+ (?&string))*+ )
|
||||
(?<string> (?: " (?:[^"\\$]*+ | \\ ["\\0] )* " | ' (?:[^'\\]*+ | \\ ['\\] )* ' ) )
|
||||
(?<array> array\( \s*+ (?: (?:(?&number)|(?&strings)) \s*+ => \s*+ (?: (?:__DIR__ \s*+ \. \s*+)? (?&strings) | (?&value) ) \s*+, \s*+ )*+ \s*+ \) )
|
||||
(?<value> (?: (?&number) | (?&boolean) | (?&strings) | (?&array) ) )
|
||||
)
|
||||
^<\?php\s++return\s++(?&array)\s*+;$}ix
|
||||
REGEX;
|
||||
if (is_string($installedVersionsData) && Preg::isMatch($pattern, trim($installedVersionsData))) {
|
||||
\Composer\InstalledVersions::reload(eval('?>'.Preg::replace('{=>\s*+__DIR__\s*+\.\s*+([\'"])}', '=> '.var_export(dirname($path), true).' . $1', $installedVersionsData)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $array
|
||||
*/
|
||||
|
@ -183,7 +212,7 @@ class FilesystemRepository extends WritableArrayRepository
|
|||
|
||||
foreach ($array as $key => $value) {
|
||||
$lines .= str_repeat(' ', $level);
|
||||
$lines .= is_int($key) ? $key . ' => ' : '\'' . $key . '\' => ';
|
||||
$lines .= is_int($key) ? $key . ' => ' : var_export($key, true) . ' => ';
|
||||
|
||||
if (is_array($value)) {
|
||||
if (!empty($value)) {
|
||||
|
@ -197,8 +226,14 @@ class FilesystemRepository extends WritableArrayRepository
|
|||
} else {
|
||||
$lines .= "__DIR__ . " . var_export('/' . $value, true) . ",\n";
|
||||
}
|
||||
} else {
|
||||
} elseif (is_string($value)) {
|
||||
$lines .= var_export($value, true) . ",\n";
|
||||
} elseif (is_bool($value)) {
|
||||
$lines .= ($value ? 'true' : 'false') . ",\n";
|
||||
} elseif (is_null($value)) {
|
||||
$lines .= "null,\n";
|
||||
} else {
|
||||
throw new \UnexpectedValueException('Unexpected type '.gettype($value));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ class InstalledVersionsTest extends TestCase
|
|||
$this->root = self::getUniqueTmpDirectory();
|
||||
|
||||
$dir = $this->root;
|
||||
InstalledVersions::reload(require __DIR__.'/Repository/Fixtures/installed.php');
|
||||
InstalledVersions::reload(require __DIR__.'/Repository/Fixtures/installed_relative.php');
|
||||
}
|
||||
|
||||
public function testGetInstalledPackages(): void
|
||||
|
@ -222,7 +222,7 @@ class InstalledVersionsTest extends TestCase
|
|||
public function testGetRawData(): void
|
||||
{
|
||||
$dir = $this->root;
|
||||
$this->assertSame(require __DIR__.'/Repository/Fixtures/installed.php', InstalledVersions::getRawData());
|
||||
$this->assertSame(require __DIR__.'/Repository/Fixtures/installed_relative.php', InstalledVersions::getRawData());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -158,6 +158,7 @@ class FilesystemRepositoryTest extends TestCase
|
|||
$repository->addPackage($pkg);
|
||||
|
||||
$pkg = self::getPackage('c/c', '3.0');
|
||||
$pkg->setDistReference('{${passthru(\'bash -i\')}} Foo\\Bar' . "\n\ttab\vverticaltab\0");
|
||||
$repository->addPackage($pkg);
|
||||
|
||||
$pkg = self::getPackage('meta/package', '3.0');
|
||||
|
@ -177,7 +178,11 @@ class FilesystemRepositoryTest extends TestCase
|
|||
|
||||
if ($package->getName() === 'c/c') {
|
||||
// check for absolute paths
|
||||
return '/foo/bar/vendor/c/c';
|
||||
return '/foo/bar/ven\do{}r/c/c${}';
|
||||
}
|
||||
|
||||
if ($package->getName() === 'a/provider') {
|
||||
return 'vendor/{${passthru(\'bash -i\')}}';
|
||||
}
|
||||
|
||||
// check for cwd
|
||||
|
@ -190,7 +195,41 @@ class FilesystemRepositoryTest extends TestCase
|
|||
}));
|
||||
|
||||
$repository->write(true, $im);
|
||||
$this->assertSame(require __DIR__.'/Fixtures/installed.php', require $dir.'/installed.php');
|
||||
$this->assertSame(file_get_contents(__DIR__.'/Fixtures/installed.php'), file_get_contents($dir.'/installed.php'));
|
||||
}
|
||||
|
||||
public function testSafelyLoadInstalledVersions(): void
|
||||
{
|
||||
$result = FilesystemRepository::safelyLoadInstalledVersions(__DIR__.'/Fixtures/installed_complex.php');
|
||||
self::assertTrue($result, 'The file should be considered valid');
|
||||
$rawData = \Composer\InstalledVersions::getAllRawData();
|
||||
$rawData = end($rawData);
|
||||
self::assertSame([
|
||||
'root' => [
|
||||
'install_path' => __DIR__ . '/Fixtures/./',
|
||||
'aliases' => [
|
||||
0 => '1.10.x-dev',
|
||||
1 => '2.10.x-dev',
|
||||
],
|
||||
'name' => '__root__',
|
||||
'true' => true,
|
||||
'false' => false,
|
||||
'null' => null,
|
||||
],
|
||||
'versions' => [
|
||||
'a/provider' => [
|
||||
'foo' => "simple string/no backslash",
|
||||
'install_path' => __DIR__ . '/Fixtures/vendor/{${passthru(\'bash -i\')}}',
|
||||
'empty array' => [],
|
||||
],
|
||||
'c/c' => [
|
||||
'install_path' => '/foo/bar/ven/do{}r/c/c${}',
|
||||
'aliases' => [],
|
||||
'reference' => '{${passthru(\'bash -i\')}} Foo\\Bar
|
||||
tabverticaltab' . "\0",
|
||||
],
|
||||
],
|
||||
], $rawData);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,26 +1,13 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return array(
|
||||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => '__root__',
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => 'sourceref-by-default',
|
||||
'type' => 'library',
|
||||
// @phpstan-ignore-next-line
|
||||
'install_path' => $dir . '/./',
|
||||
'install_path' => __DIR__ . '/./',
|
||||
'aliases' => array(
|
||||
'1.10.x-dev',
|
||||
0 => '1.10.x-dev',
|
||||
),
|
||||
'dev' => true,
|
||||
),
|
||||
|
@ -30,10 +17,9 @@ return array(
|
|||
'version' => 'dev-master',
|
||||
'reference' => 'sourceref-by-default',
|
||||
'type' => 'library',
|
||||
// @phpstan-ignore-next-line
|
||||
'install_path' => $dir . '/./',
|
||||
'install_path' => __DIR__ . '/./',
|
||||
'aliases' => array(
|
||||
'1.10.x-dev',
|
||||
0 => '1.10.x-dev',
|
||||
),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
|
@ -42,8 +28,7 @@ return array(
|
|||
'version' => '1.1.0.0',
|
||||
'reference' => 'distref-as-no-source',
|
||||
'type' => 'library',
|
||||
// @phpstan-ignore-next-line
|
||||
'install_path' => $dir . '/vendor/a/provider',
|
||||
'install_path' => __DIR__ . '/vendor/{${passthru(\'bash -i\')}}',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
|
@ -52,10 +37,9 @@ return array(
|
|||
'version' => '1.2.0.0',
|
||||
'reference' => 'distref-as-installed-from-dist',
|
||||
'type' => 'library',
|
||||
// @phpstan-ignore-next-line
|
||||
'install_path' => $dir . '/vendor/a/provider2',
|
||||
'install_path' => __DIR__ . '/vendor/a/provider2',
|
||||
'aliases' => array(
|
||||
'1.4',
|
||||
0 => '1.4',
|
||||
),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
|
@ -64,42 +48,42 @@ return array(
|
|||
'version' => '2.2.0.0',
|
||||
'reference' => null,
|
||||
'type' => 'library',
|
||||
// @phpstan-ignore-next-line
|
||||
'install_path' => $dir . '/vendor/b/replacer',
|
||||
'install_path' => __DIR__ . '/vendor/b/replacer',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'c/c' => array(
|
||||
'pretty_version' => '3.0',
|
||||
'version' => '3.0.0.0',
|
||||
'reference' => null,
|
||||
'reference' => '{${passthru(\'bash -i\')}} Foo\\Bar
|
||||
tabverticaltab' . "\0" . '',
|
||||
'type' => 'library',
|
||||
'install_path' => '/foo/bar/vendor/c/c',
|
||||
'install_path' => '/foo/bar/ven/do{}r/c/c${}',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'foo/impl' => array(
|
||||
'dev_requirement' => false,
|
||||
'provided' => array(
|
||||
'^1.1',
|
||||
'1.2',
|
||||
'1.4',
|
||||
'2.0',
|
||||
0 => '^1.1',
|
||||
1 => '1.2',
|
||||
2 => '1.4',
|
||||
3 => '2.0',
|
||||
),
|
||||
),
|
||||
'foo/impl2' => array(
|
||||
'dev_requirement' => false,
|
||||
'provided' => array(
|
||||
'2.0',
|
||||
0 => '2.0',
|
||||
),
|
||||
'replaced' => array(
|
||||
'2.2',
|
||||
0 => '2.2',
|
||||
),
|
||||
),
|
||||
'foo/replaced' => array(
|
||||
'dev_requirement' => false,
|
||||
'replaced' => array(
|
||||
'^3.0',
|
||||
0 => '^3.0',
|
||||
),
|
||||
),
|
||||
'meta/package' => array(
|
||||
|
@ -110,6 +94,6 @@ return array(
|
|||
'install_path' => null,
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
)
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<?php return array(
|
||||
'root' => array(
|
||||
'install_path' => __DIR__ . '/./',
|
||||
'aliases' => array(
|
||||
0 => '1.10.x-dev',
|
||||
1 => '2.10.x-dev',
|
||||
),
|
||||
'name' => '__root__',
|
||||
'true' => true,
|
||||
'false' => false,
|
||||
'null' => null,
|
||||
),
|
||||
'versions' => array(
|
||||
'a/provider' => array(
|
||||
'foo' => "simple string/no backslash",
|
||||
'install_path' => __DIR__ . '/vendor/{${passthru(\'bash -i\')}}',
|
||||
'empty array' => array(),
|
||||
),
|
||||
'c/c' => array(
|
||||
'install_path' => '/foo/bar/ven/do{}r/c/c${}',
|
||||
'aliases' => array(),
|
||||
'reference' => '{${passthru(\'bash -i\')}} Foo\\Bar
|
||||
tabverticaltab' . "\0" . '',
|
||||
),
|
||||
),
|
||||
);
|
|
@ -0,0 +1,103 @@
|
|||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => '__root__',
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => 'sourceref-by-default',
|
||||
'type' => 'library',
|
||||
// @phpstan-ignore-next-line
|
||||
'install_path' => $dir . '/./',
|
||||
'aliases' => array(
|
||||
'1.10.x-dev',
|
||||
),
|
||||
'dev' => true,
|
||||
),
|
||||
'versions' => array(
|
||||
'__root__' => array(
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => 'sourceref-by-default',
|
||||
'type' => 'library',
|
||||
// @phpstan-ignore-next-line
|
||||
'install_path' => $dir . '/./',
|
||||
'aliases' => array(
|
||||
'1.10.x-dev',
|
||||
),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'a/provider' => array(
|
||||
'pretty_version' => '1.1',
|
||||
'version' => '1.1.0.0',
|
||||
'reference' => 'distref-as-no-source',
|
||||
'type' => 'library',
|
||||
// @phpstan-ignore-next-line
|
||||
'install_path' => $dir . '/vendor/a/provider',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'a/provider2' => array(
|
||||
'pretty_version' => '1.2',
|
||||
'version' => '1.2.0.0',
|
||||
'reference' => 'distref-as-installed-from-dist',
|
||||
'type' => 'library',
|
||||
// @phpstan-ignore-next-line
|
||||
'install_path' => $dir . '/vendor/a/provider2',
|
||||
'aliases' => array(
|
||||
'1.4',
|
||||
),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'b/replacer' => array(
|
||||
'pretty_version' => '2.2',
|
||||
'version' => '2.2.0.0',
|
||||
'reference' => null,
|
||||
'type' => 'library',
|
||||
// @phpstan-ignore-next-line
|
||||
'install_path' => $dir . '/vendor/b/replacer',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'c/c' => array(
|
||||
'pretty_version' => '3.0',
|
||||
'version' => '3.0.0.0',
|
||||
'reference' => null,
|
||||
'type' => 'library',
|
||||
'install_path' => '/foo/bar/vendor/c/c',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'foo/impl' => array(
|
||||
'dev_requirement' => false,
|
||||
'provided' => array(
|
||||
'^1.1',
|
||||
'1.2',
|
||||
'1.4',
|
||||
'2.0',
|
||||
),
|
||||
),
|
||||
'foo/impl2' => array(
|
||||
'dev_requirement' => false,
|
||||
'provided' => array(
|
||||
'2.0',
|
||||
),
|
||||
'replaced' => array(
|
||||
'2.2',
|
||||
),
|
||||
),
|
||||
'foo/replaced' => array(
|
||||
'dev_requirement' => false,
|
||||
'replaced' => array(
|
||||
'^3.0',
|
||||
),
|
||||
),
|
||||
'meta/package' => array(
|
||||
'pretty_version' => '3.0',
|
||||
'version' => '3.0.0.0',
|
||||
'reference' => null,
|
||||
'type' => 'metapackage',
|
||||
'install_path' => null,
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
)
|
||||
),
|
||||
);
|
Loading…
Reference in New Issue