archive: prevent breakout in Untar

Signed-off-by: Tibor Vass <teabee89@gmail.com>
This commit is contained in:
Tibor Vass 2014-10-20 15:36:28 -04:00 committed by unclejack
parent 4ef1859f8b
commit b1096c99d2
2 changed files with 24 additions and 2 deletions

View file

@ -22,6 +22,7 @@ import (
"github.com/docker/docker/pkg/log" "github.com/docker/docker/pkg/log"
"github.com/docker/docker/pkg/pools" "github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/system"
) )
@ -275,11 +276,23 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
} }
case tar.TypeLink: case tar.TypeLink:
if err := os.Link(filepath.Join(extractDir, hdr.Linkname), path); err != nil { targetPath := filepath.Join(extractDir, hdr.Linkname)
// check for hardlink breakout
if !strings.HasPrefix(targetPath, extractDir) {
return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", targetPath, hdr.Linkname))
}
if err := os.Link(targetPath, path); err != nil {
return err return err
} }
case tar.TypeSymlink: case tar.TypeSymlink:
// check for symlink breakout
if _, err := symlink.FollowSymlinkInScope(filepath.Join(filepath.Dir(path), hdr.Linkname), extractDir); err != nil {
if _, ok := err.(symlink.ErrBreakout); ok {
return breakoutError(fmt.Errorf("invalid symlink %q -> %q", path, hdr.Linkname))
}
return err
}
if err := os.Symlink(hdr.Linkname, path); err != nil { if err := os.Symlink(hdr.Linkname, path); err != nil {
return err return err
} }
@ -424,6 +437,8 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
// identity (uncompressed), gzip, bzip2, xz. // identity (uncompressed), gzip, bzip2, xz.
// FIXME: specify behavior when target path exists vs. doesn't exist. // FIXME: specify behavior when target path exists vs. doesn't exist.
func Untar(archive io.Reader, dest string, options *TarOptions) error { func Untar(archive io.Reader, dest string, options *TarOptions) error {
dest = filepath.Clean(dest)
if options == nil { if options == nil {
options = &TarOptions{} options = &TarOptions{}
} }
@ -461,6 +476,7 @@ loop:
} }
// Normalize name, for safety and for a simple is-root check // Normalize name, for safety and for a simple is-root check
// This keeps "../" as-is, but normalizes "/../" to "/"
hdr.Name = filepath.Clean(hdr.Name) hdr.Name = filepath.Clean(hdr.Name)
for _, exclude := range options.Excludes { for _, exclude := range options.Excludes {
@ -481,7 +497,11 @@ loop:
} }
} }
// Prevent symlink breakout
path := filepath.Join(dest, hdr.Name) path := filepath.Join(dest, hdr.Name)
if !strings.HasPrefix(path, dest) {
return breakoutError(fmt.Errorf("%q is outside of %q", path, dest))
}
// If path exits we almost always just want to remove and replace it // If path exits we almost always just want to remove and replace it
// The only exception is when it is a directory *and* the file from // The only exception is when it is a directory *and* the file from

View file

@ -10,6 +10,8 @@ import (
const maxLoopCounter = 100 const maxLoopCounter = 100
type ErrBreakout error
// FollowSymlink will follow an existing link and scope it to the root // FollowSymlink will follow an existing link and scope it to the root
// path provided. // path provided.
// The role of this function is to return an absolute path in the root // The role of this function is to return an absolute path in the root
@ -34,7 +36,7 @@ func FollowSymlinkInScope(link, root string) (string, error) {
} }
if !strings.HasPrefix(filepath.Dir(link), root) { if !strings.HasPrefix(filepath.Dir(link), root) {
return "", fmt.Errorf("%s is not within %s", link, root) return "", ErrBreakout(fmt.Errorf("%s is not within %s", link, root))
} }
prev := "/" prev := "/"