diff --git a/evetest/utils/tar.go b/evetest/utils/tar.go index ce0461b1c47..ed24e45ebe4 100644 --- a/evetest/utils/tar.go +++ b/evetest/utils/tar.go @@ -10,6 +10,7 @@ import ( "io" "os" "path" + "strings" ) // MaxDecompressedContentSize is the maximum size of a file that can be written to disk after decompression. @@ -18,9 +19,21 @@ const MaxDecompressedContentSize = 1024 * 1024 * 1024 // 1 GB // ExtractFromTar extracts files from a tar reader into the destination directory func ExtractFromTar(u io.Reader, destination string) error { - // path inside tar is relative - pathBuilder := func(oldPath string) string { - return path.Join(destination, oldPath) + cleanDestination := path.Clean(destination) + // path inside tar must remain relative and contained within destination + safePath := func(oldPath string) (string, error) { + if oldPath == "" { + return "", fmt.Errorf("empty archive path") + } + cleanOldPath := path.Clean(oldPath) + if cleanOldPath == "." || strings.HasPrefix(cleanOldPath, "..") || strings.Contains(cleanOldPath, "/..") || path.IsAbs(cleanOldPath) { + return "", fmt.Errorf("invalid archive path: %s", oldPath) + } + fullPath := path.Clean(path.Join(cleanDestination, cleanOldPath)) + if fullPath != cleanDestination && !strings.HasPrefix(fullPath, cleanDestination+"/") { + return "", fmt.Errorf("archive path escapes destination: %s", oldPath) + } + return fullPath, nil } tarReader := tar.NewReader(u) for { @@ -31,19 +44,23 @@ func ExtractFromTar(u io.Reader, destination string) error { if err != nil { return fmt.Errorf("ExtractFromTar: Next() failed: %w", err) } + entryPath, err := safePath(header.Name) + if err != nil { + return fmt.Errorf("ExtractFromTar: invalid entry name %q: %w", header.Name, err) + } switch header.Typeflag { case tar.TypeDir: - if err := os.MkdirAll(pathBuilder(header.Name), os.FileMode(header.Mode)); err != nil { + if err := os.MkdirAll(entryPath, os.FileMode(header.Mode)); err != nil { return fmt.Errorf("ExtractFromTar: Mkdir() failed: %w", err) } case tar.TypeReg: - if _, err := os.Lstat(pathBuilder(header.Name)); err == nil { - err = os.Remove(pathBuilder(header.Name)) + if _, err := os.Lstat(entryPath); err == nil { + err = os.Remove(entryPath) if err != nil { return fmt.Errorf("ExtractFromTar: cannot remove old file: %w", err) } } - outFile, err := os.OpenFile(pathBuilder(header.Name), os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) + outFile, err := os.OpenFile(entryPath, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) if err != nil { return fmt.Errorf("ExtractFromTar: OpenFile() failed: %w", err) } @@ -60,15 +77,19 @@ func ExtractFromTar(u io.Reader, destination string) error { return fmt.Errorf("ExtractFromTar: outFile.Close() failed: %w", err) } case tar.TypeLink, tar.TypeSymlink: - if _, err := os.Lstat(pathBuilder(header.Name)); err == nil { - err = os.Remove(pathBuilder(header.Name)) + if _, err := os.Lstat(entryPath); err == nil { + err = os.Remove(entryPath) if err != nil { return fmt.Errorf("ExtractFromTar: cannot remove old symlink: %w", err) } } - if err := os.Symlink(pathBuilder(header.Linkname), pathBuilder(header.Name)); err != nil { + linkPath, err := safePath(header.Linkname) + if err != nil { + return fmt.Errorf("ExtractFromTar: invalid link name %q: %w", header.Linkname, err) + } + if err := os.Symlink(linkPath, entryPath); err != nil { return fmt.Errorf("ExtractFromTar: Symlink(%s, %s) failed: %w", - pathBuilder(header.Name), pathBuilder(header.Linkname), err) + entryPath, linkPath, err) } default: return fmt.Errorf(