Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pkg/bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,10 +270,10 @@ func Unpack(in io.Reader, targetPath string) (*UnpackDetails, error) {
continue
}

// #nosec G305 path traversal is checked by validating that the resulting path does not contain unexpected special elements
// #nosec G305 path traversal is checked by validating that the resulting path does not escape targetPath
var target = filepath.Join(targetPath, header.Name)
if strings.Contains(target, "/../") {
return nil, fmt.Errorf("targetPath validation failed, path contains unexpected special elements")
if rel, err := filepath.Rel(targetPath, target); err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return nil, fmt.Errorf("targetPath validation failed, %q escapes the target directory", header.Name)
}

switch header.Typeflag {
Expand Down
37 changes: 37 additions & 0 deletions pkg/bundle/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
package bundle_test

import (
"archive/tar"
"bytes"
"fmt"
"log"
"net/http/httptest"
Expand Down Expand Up @@ -85,6 +87,41 @@ var _ = Describe("Bundle", func() {
})
})
})

DescribeTable("should reject a tar entry that attempts to escape the target directory",
func(entryName string) {
withTempDir(func(outerDir string) {
// nested four levels deep so that the "../../../../etc/canary"
// payload still escapes targetDir while resolving to a path
// inside outerDir, not the host's real /etc
targetDir := filepath.Join(outerDir, "a", "b", "c", "target")
Expect(os.MkdirAll(targetDir, os.FileMode(0755))).To(Succeed())

// where the malicious entry would land if it were not rejected
escapedPath := filepath.Join(targetDir, entryName)

var buf bytes.Buffer
tw := tar.NewWriter(&buf)
Expect(tw.WriteHeader(&tar.Header{
Name: entryName,
Typeflag: tar.TypeReg,
Mode: 0644,
Size: int64(len("owned")),
})).To(Succeed())
_, err := tw.Write([]byte("owned"))
Expect(err).ToNot(HaveOccurred())
Expect(tw.Close()).To(Succeed())

_, err = Unpack(&buf, targetDir)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("escapes the target directory"))

Expect(escapedPath).ToNot(BeAnExistingFile())
})
},
Entry("single level traversal", "../canary"),
Entry("deep traversal matching the reported issue", "../../../../etc/canary"),
)
})

Context("packing/pushing and pulling/unpacking", func() {
Expand Down