Unverified Commit b95fca09 authored by Kubernetes Prow Robot's avatar Kubernetes Prow Robot Committed by GitHub

Merge pull request #76788 from soltysh/tar-fixes

Clean links handling in cp's tar code
parents a69702e8 7962231a
...@@ -32,6 +32,7 @@ go_test( ...@@ -32,6 +32,7 @@ go_test(
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/client-go/rest/fake:go_default_library", "//staging/src/k8s.io/client-go/rest/fake:go_default_library",
"//vendor/github.com/stretchr/testify/require:go_default_library",
], ],
) )
......
...@@ -419,9 +419,7 @@ func clean(fileName string) string { ...@@ -419,9 +419,7 @@ func clean(fileName string) string {
return path.Clean(string(os.PathSeparator) + fileName) return path.Clean(string(os.PathSeparator) + fileName)
} }
func (o *CopyOptions) untarAll(reader io.Reader, destFile, prefix string) error { func (o *CopyOptions) untarAll(reader io.Reader, destDir, prefix string) error {
entrySeq := -1
// TODO: use compression here? // TODO: use compression here?
tarReader := tar.NewReader(reader) tarReader := tar.NewReader(reader)
for { for {
...@@ -432,52 +430,60 @@ func (o *CopyOptions) untarAll(reader io.Reader, destFile, prefix string) error ...@@ -432,52 +430,60 @@ func (o *CopyOptions) untarAll(reader io.Reader, destFile, prefix string) error
} }
break break
} }
entrySeq++
mode := header.FileInfo().Mode() // All the files will start with the prefix, which is the directory where
// all the files will start with the prefix, which is the directory where
// they were located on the pod, we need to strip down that prefix, but // they were located on the pod, we need to strip down that prefix, but
// if the prefix is missing it means the tar was tempered with // if the prefix is missing it means the tar was tempered with.
// For the case where prefix is empty we need to ensure that the path
// is not absolute, which also indicates the tar file was tempered with.
if !strings.HasPrefix(header.Name, prefix) { if !strings.HasPrefix(header.Name, prefix) {
return fmt.Errorf("tar contents corrupted") return fmt.Errorf("tar contents corrupted")
} }
outFileName := path.Join(destFile, clean(header.Name[len(prefix):]))
baseName := path.Dir(outFileName) // basic file information
mode := header.FileInfo().Mode()
destFileName := path.Join(destDir, header.Name[len(prefix):])
baseName := path.Dir(destFileName)
if err := os.MkdirAll(baseName, 0755); err != nil { if err := os.MkdirAll(baseName, 0755); err != nil {
return err return err
} }
if header.FileInfo().IsDir() { if header.FileInfo().IsDir() {
if err := os.MkdirAll(outFileName, 0755); err != nil { if err := os.MkdirAll(destFileName, 0755); err != nil {
return err return err
} }
continue continue
} }
// handle coping remote file into local directory // We need to ensure that the destination file is always within boundries
if entrySeq == 0 && !header.FileInfo().IsDir() { // of the destination directory. This prevents any kind of path traversal
exists, err := dirExists(outFileName) // from within tar archive.
if err != nil { dir, file := filepath.Split(destFileName)
return err evaledPath, err := filepath.EvalSymlinks(dir)
} if err != nil {
if exists { return err
outFileName = filepath.Join(outFileName, path.Base(clean(header.Name))) }
} // For scrutiny we verify both the actual destination as well as we follow
// all the links that might lead outside of the destination directory.
if !isDestRelative(destDir, destFileName) || !isDestRelative(destDir, filepath.Join(evaledPath, file)) {
fmt.Fprintf(o.IOStreams.ErrOut, "warning: link %q is pointing to %q which is outside target destination, skipping\n", destFileName, header.Linkname)
continue
} }
if mode&os.ModeSymlink != 0 { if mode&os.ModeSymlink != 0 {
linkname := header.Linkname linkname := header.Linkname
// error is returned if linkname can't be made relative to destFile, // We need to ensure that the link destination is always within boundries
// but relative can end up being ../dir that's why we also need to // of the destination directory. This prevents any kind of path traversal
// verify if relative path is the same after Clean-ing // from within tar archive.
relative, err := filepath.Rel(destFile, linkname) if !isDestRelative(destDir, linkJoin(destFileName, linkname)) {
if path.IsAbs(linkname) && (err != nil || relative != stripPathShortcuts(relative)) { fmt.Fprintf(o.IOStreams.ErrOut, "warning: link %q is pointing to %q which is outside target destination, skipping\n", destFileName, header.Linkname)
fmt.Fprintf(o.IOStreams.ErrOut, "warning: link %q is pointing to %q which is outside target destination, skipping\n", outFileName, header.Linkname)
continue continue
} }
if err := os.Symlink(linkname, outFileName); err != nil { if err := os.Symlink(linkname, destFileName); err != nil {
return err return err
} }
} else { } else {
outFile, err := os.Create(outFileName) outFile, err := os.Create(destFileName)
if err != nil { if err != nil {
return err return err
} }
...@@ -491,14 +497,32 @@ func (o *CopyOptions) untarAll(reader io.Reader, destFile, prefix string) error ...@@ -491,14 +497,32 @@ func (o *CopyOptions) untarAll(reader io.Reader, destFile, prefix string) error
} }
} }
if entrySeq == -1 {
//if no file was copied
errInfo := fmt.Sprintf("error: %s no such file or directory", prefix)
return errors.New(errInfo)
}
return nil return nil
} }
// linkJoin joins base and link to get the final path to be created.
// It will consider whether link is an absolute path or not when returning result.
func linkJoin(base, link string) string {
if filepath.IsAbs(link) {
return link
}
return filepath.Join(base, link)
}
// isDestRelative returns true if dest is pointing outside the base directory,
// false otherwise.
func isDestRelative(base, dest string) bool {
fullPath := dest
if !filepath.IsAbs(dest) {
fullPath = filepath.Join(base, dest)
}
relative, err := filepath.Rel(base, fullPath)
if err != nil {
return false
}
return relative == "." || relative == stripPathShortcuts(relative)
}
func getPrefix(file string) string { func getPrefix(file string) string {
// tar strips the leading '/' if it's there, so we will too // tar strips the leading '/' if it's there, so we will too
return strings.TrimLeft(file, "/") return strings.TrimLeft(file, "/")
...@@ -525,15 +549,3 @@ func (o *CopyOptions) execute(options *exec.ExecOptions) error { ...@@ -525,15 +549,3 @@ func (o *CopyOptions) execute(options *exec.ExecOptions) error {
} }
return nil return nil
} }
// dirExists checks if a path exists and is a directory.
func dirExists(path string) (bool, error) {
fi, err := os.Stat(path)
if err == nil && fi.IsDir() {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment