Skip to content

Commit 32b13f5

Browse files
authored
Merge pull request #68 from thaJeztah/add_regression_tests
archive: add regression tests for tar path-traversal containment
2 parents 5bfe8d9 + 216738e commit 32b13f5

1 file changed

Lines changed: 81 additions & 0 deletions

File tree

archive_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,87 @@ func TestUntarInvalidFilenames(t *testing.T) {
766766
}
767767
}
768768

769+
// TestUntarParentTraversalContained verifies that entries whose names traverse
770+
// above the destination (including a bare "..") are rejected and never write
771+
// into the destination's parent. Regression test for the "write to the parent
772+
// of the extraction root" breakout.
773+
func TestUntarParentTraversalContained(t *testing.T) {
774+
t.Skip("FIXME: currently failing: enable once https://github.com/moby/go-archive/pull/45 is merged")
775+
for _, tc := range []struct {
776+
name string
777+
entry string
778+
}{
779+
{name: "bare parent", entry: ".."},
780+
{name: "parent child", entry: "../pwned"},
781+
{name: "nested parent", entry: "../../pwned"},
782+
} {
783+
t.Run(tc.name, func(t *testing.T) {
784+
base := t.TempDir()
785+
dest := filepath.Join(base, "dest")
786+
assert.NilError(t, os.Mkdir(dest, 0o755))
787+
788+
var buf bytes.Buffer
789+
tw := tar.NewWriter(&buf)
790+
assert.NilError(t, tw.WriteHeader(&tar.Header{
791+
Name: tc.entry,
792+
Typeflag: tar.TypeReg,
793+
Mode: 0o644,
794+
Size: int64(len("bad")),
795+
}))
796+
_, err := tw.Write([]byte("bad"))
797+
assert.NilError(t, err)
798+
assert.NilError(t, tw.Close())
799+
800+
// Untar must reject parent-traversal entries; regardless,
801+
// nothing may be written above dest.
802+
err = Untar(&buf, dest, &TarOptions{NoLchown: true})
803+
assert.ErrorType(t, err, &breakoutErr{})
804+
805+
// dest's parent must still contain only dest.
806+
entries, err := os.ReadDir(base)
807+
assert.NilError(t, err)
808+
assert.Equal(t, len(entries), 1, "unexpected escape into parent: %v", entries)
809+
assert.Equal(t, entries[0].Name(), "dest")
810+
})
811+
}
812+
}
813+
814+
// TestUntarSiblingPrefixContained verifies that a symlink whose target is a
815+
// sibling directory sharing the destination's path prefix (dest "base/dest",
816+
// sibling "base/dest-evil") cannot be written through. Regression test for the
817+
// old string-prefix (HasPrefix) containment check, which treated such a sibling
818+
// as inside the destination.
819+
func TestUntarSiblingPrefixContained(t *testing.T) {
820+
t.Skip("FIXME: currently failing: enable once https://github.com/moby/go-archive/pull/45 is merged")
821+
base := t.TempDir()
822+
dest := filepath.Join(base, "dest")
823+
assert.NilError(t, os.Mkdir(dest, 0o755))
824+
// Prefix-sharing sibling with a sentinel file.
825+
evil := filepath.Join(base, "dest-evil")
826+
assert.NilError(t, os.Mkdir(evil, 0o755))
827+
828+
assert.NilError(t, os.WriteFile(filepath.Join(evil, "secret"), []byte("secret"), 0o600))
829+
830+
var buf bytes.Buffer
831+
tw := tar.NewWriter(&buf)
832+
// A hardlink whose target resolves to the prefix-sharing sibling. The old
833+
// strings.HasPrefix check accepted this because "base/dest-evil/secret"
834+
// starts with "base/dest".
835+
assert.NilError(t, tw.WriteHeader(&tar.Header{
836+
Name: "grab",
837+
Typeflag: tar.TypeLink,
838+
Linkname: "../dest-evil/secret",
839+
Mode: 0o644,
840+
}))
841+
assert.NilError(t, tw.Close())
842+
843+
_ = Untar(&buf, dest, &TarOptions{NoLchown: true}) // may error; we only require containment
844+
845+
// No hardlink to the sibling's secret may be created inside dest.
846+
_, statErr := os.Stat(filepath.Join(dest, "grab"))
847+
assert.ErrorIs(t, statErr, os.ErrNotExist, "hardlink to prefix-sibling created")
848+
}
849+
769850
func TestUntarHardlinkToSymlink(t *testing.T) {
770851
skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
771852
for i, headers := range [][]*tar.Header{

0 commit comments

Comments
 (0)