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

fix: Prevent duplicate folders from being created. #411

Merged
merged 2 commits into from
Dec 1, 2021
Merged
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
24 changes: 20 additions & 4 deletions apk/apk.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,8 +414,14 @@ func createFilesInsideTarGz(info *nfpm.Info, tw *tar.Writer, created map[string]
continue
}

normalizedName := normalizePath(strings.Trim(file.Destination, "/")) + "/"

if created[normalizedName] {
return fmt.Errorf("duplicate directory: %q", normalizedName)
}

err = tw.WriteHeader(&tar.Header{
Name: files.ToNixPath(strings.Trim(file.Destination, "/") + "/"),
Name: normalizedName,
Mode: int64(file.FileInfo.Mode),
Typeflag: tar.TypeDir,
Format: tar.FormatGNU,
Expand All @@ -427,7 +433,7 @@ func createFilesInsideTarGz(info *nfpm.Info, tw *tar.Writer, created map[string]
return err
}

created[strings.TrimPrefix(file.Destination, "/")] = true
created[normalizedName] = true
}

for _, file := range info.Contents {
Expand Down Expand Up @@ -483,7 +489,7 @@ func copyToTarAndDigest(file *files.Content, tw *tar.Writer, sizep *int64) error
// tar.FileInfoHeader only uses file.Mode().Perm() which masks the mode with
// 0o777 which we don't want because we want to be able to set the suid bit.
header.Mode = int64(file.Mode())
header.Name = files.ToNixPath(file.Destination[1:])
header.Name = normalizePath(file.Destination)
header.Uname = file.FileInfo.Owner
header.Gname = file.FileInfo.Group
if err = newItemInsideTarGz(tw, contents, header); err != nil {
Expand All @@ -494,20 +500,30 @@ func copyToTarAndDigest(file *files.Content, tw *tar.Writer, sizep *int64) error
return nil
}

// normalizePath returns a path separated by slashes without a leading slash.
func normalizePath(src string) string {
return files.ToNixPath(strings.TrimLeft(src, "/"))
}

// this is needed because the data.tar.gz file should have the empty folders
// as well, so we walk through the dst and create all subfolders.
func createTree(tarw *tar.Writer, dst string, created map[string]bool) error {
for _, path := range pathsToCreate(dst) {
path = normalizePath(path) + "/"

if created[path] {
// skipping dir that was previously created inside the archive
// (eg: usr/)
continue
}

if err := tarw.WriteHeader(&tar.Header{
Name: files.ToNixPath(path + "/"),
Name: path,
Mode: 0o755,
Typeflag: tar.TypeDir,
Format: tar.FormatGNU,
Uname: "root",
Gname: "root",
}); err != nil {
return fmt.Errorf("failed to create folder %s: %w", path, err)
}
Expand Down
79 changes: 79 additions & 0 deletions apk/apk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,25 @@ func extractFromTar(t *testing.T, tarFile []byte, fileName string) []byte {
return nil
}

func tarContents(tb testing.TB, tarFile []byte) []string {
tb.Helper()

contents := []string{}

tr := tar.NewReader(bytes.NewReader(tarFile))
for {
hdr, err := tr.Next()
if errors.Is(err, io.EOF) {
break // End of archive
}
require.NoError(tb, err)

contents = append(contents, hdr.Name)
}

return contents
}

func TestAPKConventionalFileName(t *testing.T) {
apkName := "default"
testCases := []struct {
Expand Down Expand Up @@ -516,6 +535,66 @@ func TestDirectories(t *testing.T) {
require.Equal(t, h.Typeflag, byte(tar.TypeDir))
}

func TestNoDuplicateAutocreatedDirectories(t *testing.T) {
info := exampleInfo()
info.DisableGlobbing = true
info.Contents = []*files.Content{
{
Source: "../testdata/fake",
Destination: "/etc/foo/bar",
},
{
Type: "dir",
Destination: "/etc/foo",
},
}
require.NoError(t, info.Validate())

expected := map[string]bool{
"etc/": true,
"etc/foo/": true,
"etc/foo/bar": true,
}

var buf bytes.Buffer
size := int64(0)
err := createFilesInsideTarGz(info, tar.NewWriter(&buf), make(map[string]bool), &size)
require.NoError(t, err)

contents := tarContents(t, buf.Bytes())

if len(expected) != len(contents) {
t.Fatalf("contents has %d entries instead of %d: %#v", len(contents), len(expected), contents)
}

for _, entry := range contents {
if !expected[entry] {
t.Fatalf("unexpected content: %q", entry)
}
}
}

func TestNoDuplicateDirectories(t *testing.T) {
info := exampleInfo()
info.DisableGlobbing = true
info.Contents = []*files.Content{
{
Type: "dir",
Destination: "/etc/foo",
},
{
Type: "dir",
Destination: "/etc/foo/",
},
}
require.NoError(t, info.Validate())

var buf bytes.Buffer
size := int64(0)
err := createFilesInsideTarGz(info, tar.NewWriter(&buf), make(map[string]bool), &size)
require.Error(t, err)
}

func TestNoDuplicateContents(t *testing.T) {
info := exampleInfo()
info.Contents = []*files.Content{
Expand Down
13 changes: 11 additions & 2 deletions deb/deb.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,14 @@ func createFilesInsideDataTar(info *nfpm.Info, tw *tar.Writer,
continue
}

normalizedName := normalizePath(strings.Trim(file.Destination, "/")) + "/"

if created[normalizedName] {
return md5buf, 0, fmt.Errorf("duplicate directory: %q", normalizedName)
}

err = tw.WriteHeader(&tar.Header{
Name: normalizePath(strings.Trim(file.Destination, "/") + "/"),
Name: normalizedName,
Mode: int64(file.FileInfo.Mode),
Typeflag: tar.TypeDir,
Format: tar.FormatGNU,
Expand All @@ -293,7 +299,7 @@ func createFilesInsideDataTar(info *nfpm.Info, tw *tar.Writer,
return md5buf, 0, err
}

created[strings.TrimPrefix(file.Destination, "/")] = true
created[normalizedName] = true
}

// create files and implicit directories
Expand Down Expand Up @@ -588,12 +594,15 @@ func createTree(tarw *tar.Writer, dst string, created map[string]bool) error {
// (eg: usr/)
continue
}

if err := tarw.WriteHeader(&tar.Header{
Name: path,
Mode: 0o755,
Typeflag: tar.TypeDir,
Format: tar.FormatGNU,
ModTime: time.Now(),
Uname: "root",
Gname: "root",
}); err != nil {
return fmt.Errorf("failed to create folder: %w", err)
}
Expand Down
75 changes: 75 additions & 0 deletions deb/deb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,62 @@ func TestDisableGlobbing(t *testing.T) {
require.Equal(t, expectedContent, actualContent)
}

func TestNoDuplicateAutocreatedDirectories(t *testing.T) {
info := exampleInfo()
info.DisableGlobbing = true
info.Contents = []*files.Content{
{
Source: "../testdata/fake",
Destination: "/etc/foo/bar",
},
{
Type: "dir",
Destination: "/etc/foo",
},
}
require.NoError(t, info.Validate())

expected := map[string]bool{
"./etc/": true,
"./etc/foo/": true,
"./etc/foo/bar": true,
}

dataTarball, _, _, tarballName, err := createDataTarball(info)
require.NoError(t, err)

contents := tarContents(t, inflate(t, tarballName, dataTarball))

if len(expected) != len(contents) {
t.Fatalf("contents has %d entries instead of %d: %#v", len(contents), len(expected), contents)
}

for _, entry := range contents {
if !expected[entry] {
t.Fatalf("unexpected content: %q", entry)
}
}
}

func TestNoDuplicateDirectories(t *testing.T) {
info := exampleInfo()
info.DisableGlobbing = true
info.Contents = []*files.Content{
{
Type: "dir",
Destination: "/etc/foo",
},
{
Type: "dir",
Destination: "/etc/foo/",
},
}
require.NoError(t, info.Validate())

_, _, _, _, err := createDataTarball(info)
require.Error(t, err)
}

func TestCompressionAlgorithms(t *testing.T) {
testCases := []struct {
algorithm string
Expand Down Expand Up @@ -1028,6 +1084,25 @@ func tarContains(tb testing.TB, tarFile []byte, filename string) bool {
return false
}

func tarContents(tb testing.TB, tarFile []byte) []string {
tb.Helper()

contents := []string{}

tr := tar.NewReader(bytes.NewReader(tarFile))
for {
hdr, err := tr.Next()
if errors.Is(err, io.EOF) {
break // End of archive
}
require.NoError(tb, err)

contents = append(contents, hdr.Name)
}

return contents
}

func extractFileHeaderFromTar(tb testing.TB, tarFile []byte, filename string) *tar.Header {
tb.Helper()

Expand Down
7 changes: 6 additions & 1 deletion files/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,13 @@ func checkNoCollisions(contents Contents) error {
for _, elem := range contents {
present, ok := alreadyPresent[elem.Destination]
if ok && (present.Packager == "" || elem.Packager == "" || present.Packager == elem.Packager) {
if elem.Type == "dir" {
return fmt.Errorf("cannot add directory %q because it is already present: %w",
elem.Destination, ErrContentCollision)
}

return fmt.Errorf(
"cannot add %s because %s is already present at the same destination (%s): %w",
"cannot add %q because %q is already present at the same destination (%s): %w",
elem.Source, present.Source, present.Destination, ErrContentCollision)
}

Expand Down