mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2026-07-16 17:26:58 -04:00
Cap conan package extraction size; require http(s) package URLs (#8820)
Fixes #8817. Kept deliberately simple per discussion — the conan server is CE's own infrastructure, so these are hygiene bounds, not attack mitigations: - A generous fixed 2GiB cap on the total declared size of extracted files (tar-stream enforces entry bodies match their headers, so summing `header.size` bounds bytes written). Real packages are tens to a few hundred MiB; hitting this means a packaging error or a corrupt/bombed archive, and the extraction rejects cleanly through the existing error path. - The package URL conan returns must be http(s). No redirect restrictions (conan may legitimately hand out redirecting/presigned URLs), no config plumbing. Both paths tested (cap exercised by lowering the limit on the instance under test; scheme via a `file://` URL). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
8e6033825a
commit
51317dc13f
@@ -65,6 +65,9 @@ export class BuildEnvSetupCeConanDirect extends BuildEnvSetupBase {
|
||||
protected onlyonstaticliblink: any;
|
||||
protected extractAllToRoot: boolean;
|
||||
protected conan_os: string;
|
||||
// Generous cap on the total declared size of extracted files; real library packages are
|
||||
// tens to a few hundred MiB, so anything near this is a packaging error or a gzip bomb.
|
||||
protected maxExtractedBytes = 2 * 1024 * 1024 * 1024;
|
||||
|
||||
static get key() {
|
||||
return 'ceconan';
|
||||
@@ -143,9 +146,15 @@ export class BuildEnvSetupCeConanDirect extends BuildEnvSetupBase {
|
||||
downloadPath: string,
|
||||
packageUrl: string,
|
||||
): Promise<BuildEnvDownloadInfo> {
|
||||
const protocol = new URL(packageUrl).protocol;
|
||||
if (protocol !== 'http:' && protocol !== 'https:') {
|
||||
throw new Error(`Unexpected protocol '${protocol}' for conan package URL`);
|
||||
}
|
||||
|
||||
const startTime = process.hrtime.bigint();
|
||||
const extract = tar.extract();
|
||||
const gunzip = zlib.createGunzip();
|
||||
let extractedBytes = 0;
|
||||
|
||||
extract.on('entry', async (header, stream, next) => {
|
||||
// Drained streams (skipped entries) have no other error listener; without this an
|
||||
@@ -167,6 +176,11 @@ export class BuildEnvSetupCeConanDirect extends BuildEnvSetupBase {
|
||||
return;
|
||||
}
|
||||
|
||||
extractedBytes += header.size ?? 0;
|
||||
if (extractedBytes > this.maxExtractedBytes) {
|
||||
throw new Error(`Package ${libId}/${version} exceeds ${this.maxExtractedBytes} bytes extracted`);
|
||||
}
|
||||
|
||||
if (header.type !== 'file') {
|
||||
// Only regular files are ever written; symlinks, hardlinks, directories etc
|
||||
// are drained and skipped. Draining also avoids a hang on malformed entries
|
||||
|
||||
@@ -192,6 +192,25 @@ describe('BuildEnvSetupCeConanDirect.downloadAndExtractPackage', () => {
|
||||
await expect(fs.promises.access(path.join(downloadPath, 'somelib', 'evildir'))).rejects.toThrow();
|
||||
}, 10_000);
|
||||
|
||||
it('rejects a package whose extracted size exceeds the cap', async () => {
|
||||
const downloadPath = await temp.mkdir('ce-conan-test');
|
||||
(setup as any).maxExtractedBytes = 1024;
|
||||
try {
|
||||
await expect(
|
||||
setup.downloadAndExtractPackage('somelib', '1.0', downloadPath, `${baseUrl}/package.tgz`),
|
||||
).rejects.toThrow(/exceeds 1024 bytes/);
|
||||
} finally {
|
||||
(setup as any).maxExtractedBytes = 2 * 1024 * 1024 * 1024;
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects a non-http(s) package URL', async () => {
|
||||
const downloadPath = await temp.mkdir('ce-conan-test');
|
||||
await expect(
|
||||
setup.downloadAndExtractPackage('somelib', '1.0', downloadPath, 'file:///etc/passwd'),
|
||||
).rejects.toThrow(/Unexpected protocol 'file:'/);
|
||||
});
|
||||
|
||||
it('rejects when the download is severed mid-stream', async () => {
|
||||
const downloadPath = await temp.mkdir('ce-conan-test');
|
||||
await expect(
|
||||
|
||||
Reference in New Issue
Block a user