From 0acf3ec695c0716d6300c93a2db20beb9b507a00 Mon Sep 17 00:00:00 2001 From: egibs <20933572+egibs@users.noreply.github.com> Date: Thu, 19 Dec 2024 20:46:52 -0600 Subject: [PATCH] Address more gzip, tar, and tar.gz edge cases Signed-off-by: egibs <20933572+egibs@users.noreply.github.com> --- pkg/archive/archive.go | 4 ++-- pkg/archive/deb.go | 14 +++++++------- pkg/archive/gzip.go | 13 +++++++++++++ pkg/archive/tar.go | 27 +++++++++++++++++++-------- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index c4bd936b..9894600f 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -181,10 +181,10 @@ func ExtractionMethod(ext string) func(context.Context, string, string) error { switch ext { case ".jar", ".zip", ".whl": return ExtractZip - case ".gz": - return ExtractGzip case ".apk", ".gem", ".tar", ".tar.bz2", ".tar.gz", ".tgz", ".tar.xz", ".tbz", ".xz": return ExtractTar + case ".gz": + return ExtractGzip case ".bz2", ".bzip2": return ExtractBz2 case ".rpm": diff --git a/pkg/archive/deb.go b/pkg/archive/deb.go index cf94e963..ae676b53 100644 --- a/pkg/archive/deb.go +++ b/pkg/archive/deb.go @@ -74,21 +74,21 @@ func ExtractDeb(ctx context.Context, d, f string) error { return fmt.Errorf("failed to close file: %w", err) } case tar.TypeSymlink: - // Skip symlinks for targets that do not exist - _, err = os.Readlink(target) + // Ensure that symlinks are not relative path traversals + // #nosec G305 // L88 handles the check + fullLink := filepath.Join(d, header.Linkname) + _, err = os.Lstat(fullLink) if os.IsNotExist(err) { continue } - // Ensure that symlinks are not relative path traversals - // #nosec G305 // L208 handles the check - linkReal, err := filepath.EvalSymlinks(filepath.Join(d, header.Linkname)) + linkReal, err := filepath.EvalSymlinks(fullLink) if err != nil { return fmt.Errorf("failed to evaluate symlink: %w", err) } - if !IsValidPath(linkReal, d) { + if !IsValidPath(target, d) { return fmt.Errorf("symlink points outside temporary directory: %s", linkReal) } - if err := os.Symlink(linkReal, target); err != nil { + if err := os.Symlink(header.Linkname, target); err != nil { return fmt.Errorf("failed to create symlink: %w", err) } } diff --git a/pkg/archive/gzip.go b/pkg/archive/gzip.go index 9260253c..60141c14 100644 --- a/pkg/archive/gzip.go +++ b/pkg/archive/gzip.go @@ -9,10 +9,23 @@ import ( "path/filepath" "github.com/chainguard-dev/clog" + "github.com/chainguard-dev/malcontent/pkg/programkind" ) // extractGzip extracts .gz archives. func ExtractGzip(ctx context.Context, d string, f string) error { + // Check whether the provided file is a valid gzip archive + var isGzip bool + if ft, err := programkind.File(f); err == nil && ft != nil { + if ft.MIME == "application/gzip" { + isGzip = true + } + } + + if !isGzip { + return fmt.Errorf("not a valid gzip archive") + } + logger := clog.FromContext(ctx).With("dir", d, "file", f) logger.Debug("extracting gzip") diff --git a/pkg/archive/tar.go b/pkg/archive/tar.go index aa7fade7..aa022d0a 100644 --- a/pkg/archive/tar.go +++ b/pkg/archive/tar.go @@ -13,10 +13,13 @@ import ( "strings" "github.com/chainguard-dev/clog" + "github.com/chainguard-dev/malcontent/pkg/programkind" "github.com/ulikunitz/xz" ) // extractTar extracts .apk and .tar* archives. +// +//nolint:cyclop // ignore complexity of 39 func ExtractTar(ctx context.Context, d string, f string) error { logger := clog.FromContext(ctx).With("dir", d, "file", f) logger.Debug("extracting tar") @@ -33,6 +36,15 @@ func ExtractTar(ctx context.Context, d string, f string) error { return fmt.Errorf("failed to open file: %w", err) } defer tf.Close() + + isTGZ := strings.Contains(f, ".tar.gz") || strings.Contains(f, ".tgz") + var isGzip bool + if ft, err := programkind.File(f); err == nil && ft != nil { + if ft.MIME == "application/gzip" { + isGzip = true + } + } + // Set offset to the file origin regardless of type _, err = tf.Seek(0, io.SeekStart) if err != nil { @@ -40,9 +52,8 @@ func ExtractTar(ctx context.Context, d string, f string) error { } var tr *tar.Reader - switch { - case strings.Contains(f, ".apk") || strings.Contains(f, ".tar.gz") || strings.Contains(f, ".tgz"): + case strings.Contains(f, ".apk") || (isTGZ && isGzip): gzStream, err := gzip.NewReader(tf) if err != nil { return fmt.Errorf("failed to create gzip reader: %w", err) @@ -133,21 +144,21 @@ func ExtractTar(ctx context.Context, d string, f string) error { return fmt.Errorf("failed to close file: %w", err) } case tar.TypeSymlink: - // Skip symlinks for targets that do not exist - _, err = os.Readlink(target) + // Ensure that symlinks are not relative path traversals + // #nosec G305 // L158 handles the check + fullLink := filepath.Join(d, header.Linkname) + _, err = os.Lstat(fullLink) if os.IsNotExist(err) { continue } - // Ensure that symlinks are not relative path traversals - // #nosec G305 // L208 handles the check - linkReal, err := filepath.EvalSymlinks(filepath.Join(d, header.Linkname)) + linkReal, err := filepath.EvalSymlinks(fullLink) if err != nil { return fmt.Errorf("failed to evaluate symlink: %w", err) } if !IsValidPath(target, d) { return fmt.Errorf("symlink points outside temporary directory: %s", linkReal) } - if err := os.Symlink(linkReal, target); err != nil { + if err := os.Symlink(header.Linkname, target); err != nil { return fmt.Errorf("failed to create symlink: %w", err) } }