From c7fa4bfe3571667e9731f3f504da3c6be709a24c Mon Sep 17 00:00:00 2001 From: Xun Jiang Date: Fri, 13 Mar 2026 11:24:09 +0800 Subject: [PATCH] Add more check for file extraction from tarball. Signed-off-by: Xun Jiang --- changelogs/unreleased/9661-blackpiglet | 1 + pkg/archive/extractor.go | 18 +++++++++++++++++- pkg/archive/extractor_test.go | 26 ++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/9661-blackpiglet diff --git a/changelogs/unreleased/9661-blackpiglet b/changelogs/unreleased/9661-blackpiglet new file mode 100644 index 000000000..6d15e4f2e --- /dev/null +++ b/changelogs/unreleased/9661-blackpiglet @@ -0,0 +1 @@ +Add check for file extraction from tarball. \ No newline at end of file diff --git a/pkg/archive/extractor.go b/pkg/archive/extractor.go index 87d9ab413..aae9a7a85 100644 --- a/pkg/archive/extractor.go +++ b/pkg/archive/extractor.go @@ -19,8 +19,10 @@ package archive import ( "archive/tar" "compress/gzip" + "fmt" "io" "path/filepath" + "strings" "github.com/sirupsen/logrus" @@ -66,6 +68,16 @@ func (e *Extractor) writeFile(target string, tarRdr *tar.Reader) error { return nil } +// sanitizeArchivePath sanitizes archive file path from "G305: Zip Slip vulnerability" +func sanitizeArchivePath(destDir, sourcePath string) (targetPath string, err error) { + targetPath = filepath.Join(destDir, sourcePath) + if strings.HasPrefix(targetPath, filepath.Clean(destDir)) { + return targetPath, nil + } + + return "", fmt.Errorf("invalid archive path %q: escapes target directory", sourcePath) +} + func (e *Extractor) readBackup(tarRdr *tar.Reader) (string, error) { dir, err := e.fs.TempDir("", "") if err != nil { @@ -84,7 +96,11 @@ func (e *Extractor) readBackup(tarRdr *tar.Reader) (string, error) { return "", err } - target := filepath.Join(dir, header.Name) //nolint:gosec // Internal usage. No need to check. + target, err := sanitizeArchivePath(dir, header.Name) + if err != nil { + e.log.Infof("error sanitizing archive path: %s", err.Error()) + return "", err + } switch header.Typeflag { case tar.TypeDir: diff --git a/pkg/archive/extractor_test.go b/pkg/archive/extractor_test.go index 47cea734c..a4daf02ca 100644 --- a/pkg/archive/extractor_test.go +++ b/pkg/archive/extractor_test.go @@ -18,6 +18,7 @@ package archive import ( "archive/tar" + "bytes" "compress/gzip" "io" "os" @@ -87,6 +88,31 @@ func TestUnzipAndExtractBackup(t *testing.T) { } } +func TestUnzipAndExtractBackupRejectsPathTraversal(t *testing.T) { + ext := NewExtractor(test.NewLogger(), test.NewFakeFileSystem()) + + var buf bytes.Buffer + gzw := gzip.NewWriter(&buf) + tw := tar.NewWriter(gzw) + + err := tw.WriteHeader(&tar.Header{ + Name: "../escape.txt", + Mode: 0600, + Typeflag: tar.TypeReg, + Size: int64(len("data")), + }) + require.NoError(t, err) + + _, err = tw.Write([]byte("data")) + require.NoError(t, err) + require.NoError(t, tw.Close()) + require.NoError(t, gzw.Close()) + + _, err = ext.UnzipAndExtractBackup(&buf) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid archive path") +} + func createArchive(files []string, fs filesystem.Interface) (string, error) { outName := "output.tar.gz" out, err := fs.Create(outName)