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
13 changes: 12 additions & 1 deletion pkg/archive/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package archive // import "github.com/buildpacks/pack/pkg/archive"
import (
"archive/tar"
"archive/zip"
"fmt"
"io"
"io/fs"
"os"
Expand All @@ -16,6 +17,10 @@ import (
"github.com/buildpacks/pack/internal/paths"
)

// maxTarEntrySize is the maximum number of bytes that ReadTarEntry will read
// from a single tar entry, guarding against decompression bombs.
const maxTarEntrySize = 4 * 1024 * 1024 // 4 MB

var NormalizedDateTime time.Time
var Umask fs.FileMode

Expand Down Expand Up @@ -147,10 +152,16 @@ func ReadTarEntry(rc io.Reader, entryPath string) (*tar.Header, []byte, error) {
}

if paths.CanonicalTarPath(header.Name) == canonicalEntryPath {
buf, err := io.ReadAll(tr)
if header.Size > maxTarEntrySize {
return nil, nil, fmt.Errorf("tar entry %q is too large: %d bytes (max %d)", entryPath, header.Size, maxTarEntrySize)
}
buf, err := io.ReadAll(io.LimitReader(tr, maxTarEntrySize+1))
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to read contents of '%s'", entryPath)
}
if int64(len(buf)) > maxTarEntrySize {
return nil, nil, fmt.Errorf("tar entry %q exceeds maximum allowed size of %d bytes", entryPath, maxTarEntrySize)
}

return header, buf, nil
}
Expand Down
37 changes: 37 additions & 0 deletions pkg/archive/archive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package archive_test

import (
"archive/tar"
"bytes"
"net"
"os"
"path/filepath"
Expand Down Expand Up @@ -156,6 +157,42 @@ func testArchive(t *testing.T, when spec.G, it spec.S) {
h.AssertError(t, err, "get next tar entry")
})
})

when("tar entry exceeds max size via header", func() {
it("returns an error", func() {
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
h.AssertNil(t, tw.WriteHeader(&tar.Header{
Name: "bigfile",
Size: 5 * 1024 * 1024, // 5 MB — over the 4 MB limit
Mode: 0644,
}))
// Don't write data; ReadTarEntry rejects on header size before reading.
_ = tw.Close()

_, _, err := archive.ReadTarEntry(&buf, "bigfile")
h.AssertError(t, err, "too large")
})
})

when("tar entry exceeds max size via actual content", func() {
it("returns an error", func() {
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
content := make([]byte, 5*1024*1024) // 5 MB
h.AssertNil(t, tw.WriteHeader(&tar.Header{
Name: "bigfile",
Size: int64(len(content)),
Mode: 0644,
}))
_, writeErr := tw.Write(content)
h.AssertNil(t, writeErr)
h.AssertNil(t, tw.Close())

_, _, err := archive.ReadTarEntry(&buf, "bigfile")
h.AssertError(t, err, "too large")
})
})
})

when("#CreateSingleFileTarReader", func() {
Expand Down
Loading