Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for Symlinks in all Tar decompressors #192

Closed
wants to merge 1 commit into from
Closed
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
2 changes: 2 additions & 0 deletions decompress_bzip2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func TestBzip2Decompressor(t *testing.T) {
false,
false,
nil,
nil,
"d3b07384d113edec49eaa6238ad5ff00",
nil,
},
Expand All @@ -21,6 +22,7 @@ func TestBzip2Decompressor(t *testing.T) {
true,
true,
nil,
nil,
"",
nil,
},
Expand Down
2 changes: 2 additions & 0 deletions decompress_gzip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func TestGzipDecompressor(t *testing.T) {
false,
false,
nil,
nil,
"d3b07384d113edec49eaa6238ad5ff00",
nil,
},
Expand All @@ -21,6 +22,7 @@ func TestGzipDecompressor(t *testing.T) {
true,
true,
nil,
nil,
"",
nil,
},
Expand Down
25 changes: 25 additions & 0 deletions decompress_tar.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"os"
"path/filepath"
"time"

securejoin "github.com/cyphar/filepath-securejoin"
)

// untar is a shared helper for untarring an archive. The reader should provide
Expand Down Expand Up @@ -45,6 +47,29 @@ func untar(input io.Reader, dst, src string, dir bool) error {
path = filepath.Join(path, hdr.Name)
}

if hdr.Typeflag == tar.TypeSymlink {
// If the type is a symlink we re-write it and
// continue instead of attempting to copy the contents

// Prevent escaping the dst path
link, err := securejoin.SecureJoin(dst, hdr.Linkname)
if err != nil {
return err
}

// Convert the link destination back into a relative path
// relative compared to the destination root
rel, err := filepath.Rel(dst, link)
if err != nil {
return err
}

if err := os.Symlink(rel, path); err != nil {
return fmt.Errorf("failed writing symbolic link: %s", err)
}
continue
}

if hdr.FileInfo().IsDir() {
if !dir {
return fmt.Errorf("expected a single file: %s", src)
Expand Down
35 changes: 35 additions & 0 deletions decompress_tar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func TestTar(t *testing.T) {
true,
false,
[]string{"directory/", "directory/a", "directory/b"},
nil,
"",
nil,
},
Expand All @@ -22,6 +23,7 @@ func TestTar(t *testing.T) {
true,
false,
[]string{"directory/", "directory/sub/", "directory/sub/a", "directory/sub/b"},
nil,
"",
nil,
},
Expand All @@ -30,6 +32,39 @@ func TestTar(t *testing.T) {
true,
false,
[]string{"directory/", "directory/sub/", "directory/sub/a", "directory/sub/b"},
nil,
"",
&mtime,
},
{
"with-symlinks.tar",
true,
false,
[]string{"baz", "foo"},
map[string]string{"bar": "baz"},
"",
&mtime,
},

// These two test cases ensure that symlinks that try to escape the dst
// path being extracted to are disallowed. The secure.join.SecureJoin()
// library function is used here which doesn't return an error but
// guarentees that the resulting path is within the root path (`dst`)
{
"with-unsafe-symlinks-1.tar",
true,
false,
[]string{"baz", "foo"},
map[string]string{"bar": "baz"},
"",
&mtime,
},
{
"with-unsafe-symlinks-2.tar",
true,
false,
[]string{"baz", "foo"},
map[string]string{"bar": "baz"},
"",
&mtime,
},
Expand Down
6 changes: 6 additions & 0 deletions decompress_tbz2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func TestTarBzip2Decompressor(t *testing.T) {
false,
true,
nil,
nil,
"",
nil,
},
Expand All @@ -23,6 +24,7 @@ func TestTarBzip2Decompressor(t *testing.T) {
false,
false,
nil,
nil,
"d3b07384d113edec49eaa6238ad5ff00",
nil,
},
Expand All @@ -32,6 +34,7 @@ func TestTarBzip2Decompressor(t *testing.T) {
true,
false,
[]string{"file"},
nil,
"",
nil,
},
Expand All @@ -41,6 +44,7 @@ func TestTarBzip2Decompressor(t *testing.T) {
true,
false,
[]string{"file1", "file2"},
nil,
"",
nil,
},
Expand All @@ -50,6 +54,7 @@ func TestTarBzip2Decompressor(t *testing.T) {
false,
true,
nil,
nil,
"",
nil,
},
Expand All @@ -60,6 +65,7 @@ func TestTarBzip2Decompressor(t *testing.T) {
true,
false,
orderingPaths,
nil,
"",
nil,
},
Expand Down
65 changes: 52 additions & 13 deletions decompress_testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import (

// TestDecompressCase is a single test case for testing decompressors
type TestDecompressCase struct {
Input string // Input is the complete path to the input file
Dir bool // Dir is whether or not we're testing directory mode
Err bool // Err is whether we expect an error or not
DirList []string // DirList is the list of files for Dir mode
FileMD5 string // FileMD5 is the expected MD5 for a single file
Mtime *time.Time // Mtime is the optionally expected mtime for a single file (or all files if in Dir mode)
Input string // Input is the complete path to the input file
Dir bool // Dir is whether or not we're testing directory mode
Err bool // Err is whether we expect an error or not
DirList []string // DirList is the list of files for Dir mode
Symlinks map[string]string // Optional map of symlinks to test
FileMD5 string // FileMD5 is the expected MD5 for a single file
Mtime *time.Time // Mtime is the optionally expected mtime for a single file (or all files if in Dir mode)
}

// TestDecompressor is a helper function for testing generic decompressors.
Expand Down Expand Up @@ -97,11 +98,17 @@ func TestDecompressor(t testing.T, d Decompressor, cases []TestDecompressCase) {

// Directory, check for the correct contents
actual := testListDir(t, dst)
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad %s\n\n%#v\n\n%#v", tc.Input, actual, expected)
if !reflect.DeepEqual(actual.files, expected) {
t.Fatalf("bad %s\n\n%#v\n\n%#v", tc.Input, actual.files, expected)
}

// Symlinks, check that symlinks match
if tc.Symlinks != nil && !reflect.DeepEqual(actual.symlinks, tc.Symlinks) {
t.Fatalf("bad %s\n\n%#v\n\n%#v", tc.Input, actual.symlinks, tc.Symlinks)
}

// Check for correct atime/mtime
for _, dir := range actual {
for _, dir := range actual.files {
path := filepath.Join(dst, dir)
if tc.Mtime != nil {
fi, err := os.Stat(path)
Expand All @@ -124,8 +131,32 @@ func TestDecompressor(t testing.T, d Decompressor, cases []TestDecompressCase) {
}
}

func testListDir(t testing.T, path string) []string {
var result []string
type testResult struct {
files []string
symlinks map[string]string
}

func (tr *testResult) AddFile(name string) {
tr.files = append(tr.files, name)
}

func (tr *testResult) AddSymlink(name, link string) {
tr.symlinks[name] = link
}

func (tr *testResult) SortFiles() {
sort.Strings(tr.files)
}

func newTestResult() *testResult {
return &testResult{
files: make([]string, 0),
symlinks: make(map[string]string),
}
}

func testListDir(t testing.T, path string) *testResult {
result := newTestResult()
err := filepath.Walk(path, func(sub string, info os.FileInfo, err error) error {
if err != nil {
return err
Expand All @@ -140,16 +171,24 @@ func testListDir(t testing.T, path string) []string {
// If it is a dir, add trailing sep
if info.IsDir() {
sub += string(os.PathSeparator)
result.AddFile(sub)
} else if info.Mode()&os.ModeSymlink != 0 {
link, err := os.Readlink(filepath.Join(path, info.Name()))
if err != nil {
return err
}
result.AddSymlink(sub, link)
} else {
result.AddFile(sub)
}

result = append(result, sub)
return nil
})
if err != nil {
t.Fatalf("err: %s", err)
}

sort.Strings(result)
result.SortFiles()
return result
}

Expand Down
8 changes: 8 additions & 0 deletions decompress_tgz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func TestTarGzipDecompressor(t *testing.T) {
false,
true,
nil,
nil,
"",
nil,
},
Expand All @@ -25,6 +26,7 @@ func TestTarGzipDecompressor(t *testing.T) {
false,
false,
nil,
nil,
"d3b07384d113edec49eaa6238ad5ff00",
nil,
},
Expand All @@ -34,6 +36,7 @@ func TestTarGzipDecompressor(t *testing.T) {
true,
false,
[]string{"file"},
nil,
"",
nil,
},
Expand All @@ -43,6 +46,7 @@ func TestTarGzipDecompressor(t *testing.T) {
true,
false,
[]string{"file1", "file2"},
nil,
"",
nil,
},
Expand All @@ -52,6 +56,7 @@ func TestTarGzipDecompressor(t *testing.T) {
false,
true,
nil,
nil,
"",
nil,
},
Expand All @@ -61,6 +66,7 @@ func TestTarGzipDecompressor(t *testing.T) {
true,
false,
multiplePaths,
nil,
"",
nil,
},
Expand All @@ -71,6 +77,7 @@ func TestTarGzipDecompressor(t *testing.T) {
true,
false,
orderingPaths,
nil,
"",
nil,
},
Expand All @@ -82,6 +89,7 @@ func TestTarGzipDecompressor(t *testing.T) {
true,
true,
nil,
nil,
"",
nil,
},
Expand Down
7 changes: 7 additions & 0 deletions decompress_txz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func TestTarXzDecompressor(t *testing.T) {
false,
true,
nil,
nil,
"",
nil,
},
Expand All @@ -25,6 +26,7 @@ func TestTarXzDecompressor(t *testing.T) {
false,
false,
nil,
nil,
"d3b07384d113edec49eaa6238ad5ff00",
nil,
},
Expand All @@ -34,6 +36,7 @@ func TestTarXzDecompressor(t *testing.T) {
true,
false,
[]string{"file"},
nil,
"",
nil,
},
Expand All @@ -43,6 +46,7 @@ func TestTarXzDecompressor(t *testing.T) {
true,
false,
[]string{"file1", "file2"},
nil,
"",
nil,
},
Expand All @@ -52,6 +56,7 @@ func TestTarXzDecompressor(t *testing.T) {
false,
true,
nil,
nil,
"",
nil,
},
Expand All @@ -61,6 +66,7 @@ func TestTarXzDecompressor(t *testing.T) {
true,
false,
multiplePaths,
nil,
"",
nil,
},
Expand All @@ -71,6 +77,7 @@ func TestTarXzDecompressor(t *testing.T) {
true,
false,
orderingPaths,
nil,
"",
nil,
},
Expand Down
Loading