Skip to content

Commit 519326b

Browse files
committed
Avoid path traversal with hard-link in tar files
1 parent 685dc25 commit 519326b

4 files changed

Lines changed: 48 additions & 7 deletions

File tree

evil_generator/main.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ func main() {
2222
log.Fatalf("Output path %s is not a directory", outputDir)
2323
}
2424

25+
generateEvilZipSlip(outputDir)
26+
generateEvilSymLinkPathTraversalTar(outputDir)
27+
}
28+
29+
func generateEvilZipSlip(outputDir *paths.Path) {
2530
evilPathTraversalFiles := []string{
2631
"..",
2732
"../../../../../../../../../../../../../../../../../../../../tmp/evil.txt",
@@ -104,3 +109,21 @@ func main() {
104109
}
105110
}
106111
}
112+
113+
func generateEvilSymLinkPathTraversalTar(outputDir *paths.Path) {
114+
outputTarFile, err := outputDir.Join("evil-link-traversal.tar").Create()
115+
if err != nil {
116+
log.Fatal(err)
117+
}
118+
defer outputTarFile.Close()
119+
120+
tw := tar.NewWriter(outputTarFile)
121+
defer tw.Close()
122+
123+
if err := tw.WriteHeader(&tar.Header{
124+
Name: "leak", Linkname: "../../../../../../../../../../../../../../../tmp/something-important",
125+
Mode: 0o0777, Size: 0, Typeflag: tar.TypeLink,
126+
}); err != nil {
127+
log.Fatal(err)
128+
}
129+
}

extractor.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,10 @@ func (e *Extractor) Tar(ctx context.Context, body io.Reader, location string, re
217217
name = rename(name)
218218
}
219219

220-
name = filepath.Join(location, name)
220+
name, err = safeJoin(location, name)
221+
if err != nil {
222+
continue
223+
}
221224
links = append(links, &link{Path: path, Name: name})
222225
case tar.TypeSymlink:
223226
symlinks = append(symlinks, &link{Path: path, Name: header.Linkname})

extractor_test.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func testArchive(t *testing.T, archivePath *paths.Path) {
6565
}
6666

6767
func TestZipSlipHardening(t *testing.T) {
68-
{
68+
t.Run("ZipTraversal", func(t *testing.T) {
6969
logger := &LoggingFS{}
7070
extractor := extract.Extractor{FS: logger}
7171
data, err := os.Open("testdata/zipslip/evil.zip")
@@ -74,8 +74,9 @@ func TestZipSlipHardening(t *testing.T) {
7474
require.NoError(t, data.Close())
7575
fmt.Print(logger)
7676
require.Empty(t, logger.Journal)
77-
}
78-
{
77+
})
78+
79+
t.Run("TarTraversal", func(t *testing.T) {
7980
logger := &LoggingFS{}
8081
extractor := extract.Extractor{FS: logger}
8182
data, err := os.Open("testdata/zipslip/evil.tar")
@@ -84,9 +85,23 @@ func TestZipSlipHardening(t *testing.T) {
8485
require.NoError(t, data.Close())
8586
fmt.Print(logger)
8687
require.Empty(t, logger.Journal)
87-
}
88+
})
89+
90+
t.Run("TarLinkTraversal", func(t *testing.T) {
91+
logger := &LoggingFS{}
92+
extractor := extract.Extractor{FS: logger}
93+
data, err := os.Open("testdata/zipslip/evil-link-traversal.tar")
94+
require.NoError(t, err)
95+
require.NoError(t, extractor.Tar(context.Background(), data, "/tmp/test", nil))
96+
require.NoError(t, data.Close())
97+
fmt.Print(logger)
98+
require.Empty(t, logger.Journal)
99+
})
88100

89-
if runtime.GOOS == "windows" {
101+
t.Run("WindowsTarTraversal", func(t *testing.T) {
102+
if runtime.GOOS != "windows" {
103+
t.Skip("Skipped on non-Windows host")
104+
}
90105
logger := &LoggingFS{}
91106
extractor := extract.Extractor{FS: logger}
92107
data, err := os.Open("testdata/zipslip/evil-win.tar")
@@ -95,7 +110,7 @@ func TestZipSlipHardening(t *testing.T) {
95110
require.NoError(t, data.Close())
96111
fmt.Print(logger)
97112
require.Empty(t, logger.Journal)
98-
}
113+
})
99114
}
100115

101116
// MockDisk is a disk that chroots to a directory
1.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)