Signed-off-by: Tibor Vass <teabee89@gmail.com>
Tibor Vass authored on 2014/10/21 04:36:28... | ... |
@@ -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 := "/" |