diff --git a/internal/archive/extract.go b/internal/archive/extract.go index 60aec3cf0..ae46b30c9 100644 --- a/internal/archive/extract.go +++ b/internal/archive/extract.go @@ -85,17 +85,17 @@ func archiveFileName(archivePath string) string { // // Identification from the head of the stream wins: a tar holding one jar ends in that jar's central // directory, and reading the tar as a zip would lose the rest. The end of the stream is consulted -// only when the head identifies nothing, which is how a zip appended to a launcher script (a Spring -// Boot executable jar) is found. +// only when the head identifies nothing supported, which is how a zip appended to a launcher script (a +// Spring Boot executable jar) is found, and how a java resource adapter (a zip named .rar) is read. func identifyFormat(ctx context.Context, name string, content ReaderAtSeeker) archives.Extractor { if _, err := content.Seek(0, io.SeekStart); err != nil { return nil } format, _, err := intFile.IdentifyArchive(ctx, name, content) if err == nil { - // a bare compression format, such as a gzipped file that is not a tar, is not extractable - extractor, _ := format.(archives.Extractor) - return extractor + if extractor := supportedExtractor(format); extractor != nil { + return extractor + } } if HasZipEndOfCentralDirectory(content) { return archives.Zip{} @@ -103,6 +103,24 @@ func identifyFormat(ctx context.Context, name string, content ReaderAtSeeker) ar return nil } +// supportedExtractor admits only the zip and tar families, the tar family optionally compressed. Other +// formats mholt identifies, such as 7z and rar, pull in decoders that have not been exercised against +// untrusted input here, so they are not opened until they are deliberately supported. A bare +// compression format, such as a gzipped file that is not a tar, is not extractable either. +func supportedExtractor(format archives.Format) archives.Extractor { + switch f := format.(type) { + case archives.Zip: + return f + case archives.Tar: + return f + case archives.CompressedArchive: + if _, ok := f.Extraction.(archives.Tar); ok { + return f + } + } + return nil +} + // extract adds every entry of the archive. Reaching the disk limit stops the walk and marks the // resolver truncated rather than failing. func (r *Resolver) extract(ctx context.Context, format archives.Extractor, content ReaderAtSeeker, exclusions Exclusions) error { diff --git a/internal/archive/extract_test.go b/internal/archive/extract_test.go index d587a52a5..3b174be08 100644 --- a/internal/archive/extract_test.go +++ b/internal/archive/extract_test.go @@ -38,6 +38,11 @@ func Test_identifyFormat(t *testing.T) { {"test.zip", zipBytes(t, sampleFiles), archives.Zip{}}, {"test.tar.gz", tarGzBytes(t, sampleFiles), archives.CompressedArchive{}}, {"plain.txt", []byte("just text"), nil}, + // identified by mholt, but not opened until their decoders are deliberately supported + {"test.7z", []byte("7z\xbc\xaf\x27\x1c\x00\x04"), nil}, + {"test.rar", []byte("Rar!\x1a\x07\x01\x00"), nil}, + // a java resource adapter archive is a zip that shares rar's extension + {"adapter.rar", zipBytes(t, sampleFiles), archives.Zip{}}, // the name can be claimed by identification, so the probe alone must not admit prose {"app.jar", []byte("plain text, no central directory anywhere in it"), nil}, {"bundle", []byte("plain text, no central directory anywhere in it"), nil},