Browse code

archive: prevent breakout in Untar

Signed-off-by: Tibor Vass <teabee89@gmail.com>

Tibor Vass authored on 2014/10/21 04:36:28
Showing 2 changed files
... ...
@@ -22,6 +22,7 @@ import (
22 22
 	"github.com/docker/docker/pkg/fileutils"
23 23
 	"github.com/docker/docker/pkg/pools"
24 24
 	"github.com/docker/docker/pkg/promise"
25
+	"github.com/docker/docker/pkg/symlink"
25 26
 	"github.com/docker/docker/pkg/system"
26 27
 )
27 28
 
... ...
@@ -292,11 +293,23 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
292 292
 		}
293 293
 
294 294
 	case tar.TypeLink:
295
-		if err := os.Link(filepath.Join(extractDir, hdr.Linkname), path); err != nil {
295
+		targetPath := filepath.Join(extractDir, hdr.Linkname)
296
+		// check for hardlink breakout
297
+		if !strings.HasPrefix(targetPath, extractDir) {
298
+			return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", targetPath, hdr.Linkname))
299
+		}
300
+		if err := os.Link(targetPath, path); err != nil {
296 301
 			return err
297 302
 		}
298 303
 
299 304
 	case tar.TypeSymlink:
305
+		// check for symlink breakout
306
+		if _, err := symlink.FollowSymlinkInScope(filepath.Join(filepath.Dir(path), hdr.Linkname), extractDir); err != nil {
307
+			if _, ok := err.(symlink.ErrBreakout); ok {
308
+				return breakoutError(fmt.Errorf("invalid symlink %q -> %q", path, hdr.Linkname))
309
+			}
310
+			return err
311
+		}
300 312
 		if err := os.Symlink(hdr.Linkname, path); err != nil {
301 313
 			return err
302 314
 		}
... ...
@@ -456,6 +469,8 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
456 456
 //  identity (uncompressed), gzip, bzip2, xz.
457 457
 // FIXME: specify behavior when target path exists vs. doesn't exist.
458 458
 func Untar(archive io.Reader, dest string, options *TarOptions) error {
459
+	dest = filepath.Clean(dest)
460
+
459 461
 	if options == nil {
460 462
 		options = &TarOptions{}
461 463
 	}
... ...
@@ -493,6 +508,7 @@ loop:
493 493
 		}
494 494
 
495 495
 		// Normalize name, for safety and for a simple is-root check
496
+		// This keeps "../" as-is, but normalizes "/../" to "/"
496 497
 		hdr.Name = filepath.Clean(hdr.Name)
497 498
 
498 499
 		for _, exclude := range options.Excludes {
... ...
@@ -513,7 +529,11 @@ loop:
513 513
 			}
514 514
 		}
515 515
 
516
+		// Prevent symlink breakout
516 517
 		path := filepath.Join(dest, hdr.Name)
518
+		if !strings.HasPrefix(path, dest) {
519
+			return breakoutError(fmt.Errorf("%q is outside of %q", path, dest))
520
+		}
517 521
 
518 522
 		// If path exits we almost always just want to remove and replace it
519 523
 		// The only exception is when it is a directory *and* the file from
... ...
@@ -10,6 +10,8 @@ import (
10 10
 
11 11
 const maxLoopCounter = 100
12 12
 
13
+type ErrBreakout error
14
+
13 15
 // FollowSymlink will follow an existing link and scope it to the root
14 16
 // path provided.
15 17
 // 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) {
34 34
 	}
35 35
 
36 36
 	if !strings.HasPrefix(filepath.Dir(link), root) {
37
-		return "", fmt.Errorf("%s is not within %s", link, root)
37
+		return "", ErrBreakout(fmt.Errorf("%s is not within %s", link, root))
38 38
 	}
39 39
 
40 40
 	prev := "/"