Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Building/PlaceholderReplacer.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ public function replacePlaceholdersWithPlaceholderReplacements(IOInterface $io,
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($downloadedPackage->extractedSourcePath)) as $file) {
assert($file instanceof SplFileInfo);

// Refuse to follow symlinks into files the package author did not
// legitimately own at build time (e.g., an archive entry that points
// at the invoking user's $HOME).
if ($file->isLink()) {
continue;
}

if (! $file->isFile() || ! in_array($file->getExtension(), self::FILE_EXTENSIONS)) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ function (OperationInterface $operation): void {
$this->composerRequest->pieOutput->write('Found prebuilt archive: ' . $url);
$composerPackage->setDistUrl($url);

// Composer's dist-sha was computed against the original
// Packagist URL; once we swap to a release-asset URL the
// FileDownloader has nothing to validate the new bytes
// against. Surface that so the caller knows HTTPS-to-origin
// is the only integrity guarantee left.
$this->composerRequest->pieOutput->write(
'<warning>Note: dist-sha integrity check is not available for prebuilt-binary URLs; HTTPS to the release-asset origin is the only integrity guarantee.</warning>',
);

if (pathinfo($url, PATHINFO_EXTENSION) === 'tgz') {
$composerPackage->setDistType('tar');
}
Expand Down
8 changes: 8 additions & 0 deletions src/ConfigureOption.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ public static function fromComposerJsonDefinition(array $configureOptionDefiniti
{
Assert::keyExists($configureOptionDefinition, 'name');
Assert::stringNotEmpty($configureOptionDefinition['name']);
// Restrict to identifier characters that match real ./configure flag
// conventions. Whitespace and shell metacharacters in this field flow
// verbatim into argv, log output, and installed.json metadata.
Assert::regex(
$configureOptionDefinition['name'],
'/^[a-zA-Z][a-zA-Z0-9_-]*$/',
'php-ext.configure-options[].name must be a configure-flag identifier (got %s)',
);

$needsValue = false;
if (array_key_exists('needs-value', $configureOptionDefinition)) {
Expand Down
9 changes: 7 additions & 2 deletions src/Installing/Ini/RemoveIniEntryWithFileGetContents.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use function file_exists;
use function in_array;
use function is_dir;
use function preg_quote;
use function Safe\file_get_contents;
use function Safe\preg_replace;
use function Safe\scandir;
Expand Down Expand Up @@ -67,10 +68,14 @@ static function (string $path) use ($additionalIniDirectory): bool {
// Make sure all symlinks are resolved
$allIniFiles = array_filter(array_map('realpath', $allIniFiles));

// Anchor on the right with \b so uninstalling `foo` doesn't also
// rewrite the prefix of `extension=foo_other`. preg_quote on the
// extension name is defence-in-depth in case future ExtensionName
// validation ever loosens past `^[A-Za-z][a-zA-Z0-9_]+$`.
$regex = sprintf(
'/^(%s\s*=\s*%s)/m',
'/^(%s\s*=\s*%s)\b/m',
$package->extensionType() === ExtensionType::PhpModule ? 'extension' : 'zend_extension',
$package->extensionName()->name(),
preg_quote($package->extensionName()->name(), '/'),
);

$updatedIniFiles = [];
Expand Down
5 changes: 4 additions & 1 deletion src/Installing/WindowsInstall.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,13 @@ public function __invoke(
assert($file instanceof SplFileInfo);

/**
* Skip directories, the main DLL, PDB
* Skip directories, the main DLL, PDB, and any symlinks the archive
* may have shipped (symlink-followed targets fall outside the source
* dir's containment guarantees).
*/
if (
$file->isDir()
|| $file->isLink()
|| $this->normalisedPathsMatch($file->getPathname(), $sourceDllName)
|| $this->normalisedPathsMatch($file->getPathname(), $sourcePdbName)
) {
Expand Down
9 changes: 6 additions & 3 deletions src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ public function __construct(

public function verify(ReleaseMetadata $releaseMetadata, BinaryFile $pharFilename, IOInterface $io): void
{
$io->write(
'Falling back to basic verification. To use full verification, install the `gh` CLI tool.',
verbosity: IOInterface::VERBOSE,
// The fallback verifier checks cert chain, cert extension claims, DSSE
// subject digest, and DSSE signature, but does NOT validate Rekor
// transparency-log inclusion. `gh attestation verify` does. Surface the
// reduced guarantees so users on shared / air-gapped hosts know.
$io->writeError(
'<warning>Falling back to OpenSSL verification (no Rekor inclusion check). Install `gh` for full attestation verification.</warning>',
);

try {
Expand Down
Loading