diff --git a/pkg/build/nodeimage/internal/kube/tar.go b/pkg/build/nodeimage/internal/kube/tar.go index edb1b1a06b..f4201b2fff 100644 --- a/pkg/build/nodeimage/internal/kube/tar.go +++ b/pkg/build/nodeimage/internal/kube/tar.go @@ -7,7 +7,7 @@ import ( "io" "os" "path/filepath" - + "strings" "sigs.k8s.io/kind/pkg/log" ) @@ -40,13 +40,22 @@ func extractTarball(tarPath, destDirectory string, logger log.Logger) (err error continue } + // Sanitize and validate archive entry path to prevent Zip Slip (directory traversal) + cleanDestDirectory := filepath.Clean(destDirectory) + targetPath := filepath.Join(cleanDestDirectory, hdr.Name) + cleanTargetPath := filepath.Clean(targetPath) + // Ensure that the resulting path is within destDirectory + if !strings.HasPrefix(cleanTargetPath, cleanDestDirectory+string(os.PathSeparator)) && cleanTargetPath != cleanDestDirectory { + return fmt.Errorf("illegal file path in archive: %s", hdr.Name) + } + if err := os.MkdirAll( - filepath.Join(destDirectory, filepath.Dir(hdr.Name)), os.FileMode(0o755), + filepath.Dir(cleanTargetPath), os.FileMode(0o755), ); err != nil { return fmt.Errorf("creating image directory structure: %w", err) } - f, err := os.Create(filepath.Join(destDirectory, hdr.Name)) + f, err := os.Create(cleanTargetPath) if err != nil { return fmt.Errorf("creating image layer file: %w", err) } diff --git a/pkg/cluster/internal/logs/logs.go b/pkg/cluster/internal/logs/logs.go index 28f4c28b93..0701fd285c 100644 --- a/pkg/cluster/internal/logs/logs.go +++ b/pkg/cluster/internal/logs/logs.go @@ -58,6 +58,11 @@ func DumpDir(logger log.Logger, node nodes.Node, nodeDir, hostDir string) (err e // untar reads the tar file from r and writes it into dir. func untar(logger log.Logger, r io.Reader, dir string) (err error) { tr := tar.NewReader(r) + // Ensure target dir is absolute and cleaned + dirAbs, err := filepath.Abs(dir) + if err != nil { + return errors.Wrapf(err, "could not get absolute path for extraction dir: %v", err) + } for { f, err := tr.Next() @@ -74,11 +79,26 @@ func untar(logger log.Logger, r io.Reader, dir string) (err error) { } rel := filepath.FromSlash(f.Name) - abs := filepath.Join(dir, rel) + // Compute absolute extraction path + abs := filepath.Join(dirAbs, rel) + absClean, err := filepath.Abs(abs) + if err != nil { + return errors.Wrapf(err, "could not get absolute path for extraction file: %v", err) + } + // Path traversal check: absClean must be within dirAbs + // Ensure dirAbs ends with a separator to prevent prefix matching issues + dirAbsWithSep := dirAbs + if !filepath.HasSuffix(dirAbs, string(os.PathSeparator)) { + dirAbsWithSep = dirAbs + string(os.PathSeparator) + } + if absClean != dirAbs && !strings.HasPrefix(absClean, dirAbsWithSep) { + logger.Warnf("tar entry %q contains path traversal, skipping", f.Name) + continue + } switch f.Typeflag { case tar.TypeReg: - wf, err := os.OpenFile(abs, os.O_CREATE|os.O_RDWR, os.FileMode(f.Mode)) + wf, err := os.OpenFile(absClean, os.O_CREATE|os.O_RDWR, os.FileMode(f.Mode)) if err != nil { return err } @@ -87,14 +107,14 @@ func untar(logger log.Logger, r io.Reader, dir string) (err error) { err = closeErr } if err != nil { - return errors.Errorf("error writing to %s: %v", abs, err) + return errors.Errorf("error writing to %s: %v", absClean, err) } if n != f.Size { - return errors.Errorf("only wrote %d bytes to %s; expected %d", n, abs, f.Size) + return errors.Errorf("only wrote %d bytes to %s; expected %d", n, absClean, f.Size) } case tar.TypeDir: - if _, err := os.Stat(abs); err != nil { - if err := os.MkdirAll(abs, 0755); err != nil { + if _, err := os.Stat(absClean); err != nil { + if err := os.MkdirAll(absClean, 0755); err != nil { return err } }