chore: remove unused file internal/file/tar.go and its test (#1724)

Signed-off-by: seiya <20365512+seiyab@users.noreply.github.com>
This commit is contained in:
Seiya 2024-02-22 03:07:07 +09:00 committed by GitHub
parent 09fdabd814
commit 5f7620fb80
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 0 additions and 244 deletions

View file

@ -1,106 +0,0 @@
package file
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
const (
_ = iota
KB = 1 << (10 * iota)
MB
GB
// limit the tar reader to 5GB per file to prevent decompression bomb attacks. Why 5GB? This is somewhat of an
// arbitrary threshold, however, we need to keep this at at minimum 2GB to accommodate possible grype DB sizes.
decompressionByteReadLimit = 5 * GB
)
type errZipSlipDetected struct {
Prefix string
JoinArgs []string
}
func (e *errZipSlipDetected) Error() string {
return fmt.Sprintf("paths are not allowed to resolve outside of the root prefix (%q). Destination: %q", e.Prefix, e.JoinArgs)
}
// safeJoin ensures that any destinations do not resolve to a path above the prefix path.
func safeJoin(prefix string, dest ...string) (string, error) {
joinResult := filepath.Join(append([]string{prefix}, dest...)...)
cleanJoinResult := filepath.Clean(joinResult)
if !strings.HasPrefix(cleanJoinResult, filepath.Clean(prefix)) {
return "", &errZipSlipDetected{
Prefix: prefix,
JoinArgs: dest,
}
}
// why not return the clean path? the called may not be expected it from what should only be a join operation.
return joinResult, nil
}
func UnTarGz(dst string, r io.Reader) error {
gzr, err := gzip.NewReader(r)
if err != nil {
return err
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
header, err := tr.Next()
switch {
case err == io.EOF:
return nil
case err != nil:
return err
case header == nil:
continue
}
target, err := safeJoin(dst, header.Name)
if err != nil {
return err
}
switch header.Typeflag {
case tar.TypeDir:
if _, err := os.Stat(target); err != nil {
if err := os.MkdirAll(target, 0755); err != nil {
return fmt.Errorf("failed to mkdir (%s): %w", target, err)
}
}
case tar.TypeReg:
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
return fmt.Errorf("failed to open file (%s): %w", target, err)
}
if err := copyWithLimits(f, tr, decompressionByteReadLimit, target); err != nil {
return err
}
if err = f.Close(); err != nil {
return fmt.Errorf("failed to close file (%s): %w", target, err)
}
}
}
}
func copyWithLimits(writer io.Writer, reader io.Reader, byteReadLimit int64, pathInArchive string) error {
if numBytes, err := io.Copy(writer, io.LimitReader(reader, byteReadLimit)); err != nil {
return fmt.Errorf("failed to copy file (%s): %w", pathInArchive, err)
} else if numBytes >= byteReadLimit {
return fmt.Errorf("failed to copy file (%s): read limit (%d bytes) reached ", pathInArchive, byteReadLimit)
}
return nil
}

View file

@ -1,138 +0,0 @@
package file
import (
"bytes"
"errors"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func assertErrorAs(expectedErr interface{}) assert.ErrorAssertionFunc {
return func(t assert.TestingT, actualErr error, i ...interface{}) bool {
return errors.As(actualErr, &expectedErr)
}
}
func TestSafeJoin(t *testing.T) {
tests := []struct {
prefix string
args []string
expected string
errAssertion assert.ErrorAssertionFunc
}{
// go cases...
{
prefix: "/a/place",
args: []string{
"somewhere/else",
},
expected: "/a/place/somewhere/else",
errAssertion: assert.NoError,
},
{
prefix: "/a/place",
args: []string{
"somewhere/../else",
},
expected: "/a/place/else",
errAssertion: assert.NoError,
},
{
prefix: "/a/../place",
args: []string{
"somewhere/else",
},
expected: "/place/somewhere/else",
errAssertion: assert.NoError,
},
// zip slip examples....
{
prefix: "/a/place",
args: []string{
"../../../etc/passwd",
},
expected: "",
errAssertion: assertErrorAs(&errZipSlipDetected{}),
},
{
prefix: "/a/place",
args: []string{
"../",
"../",
},
expected: "",
errAssertion: assertErrorAs(&errZipSlipDetected{}),
},
{
prefix: "/a/place",
args: []string{
"../",
},
expected: "",
errAssertion: assertErrorAs(&errZipSlipDetected{}),
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%+v:%+v", test.prefix, test.args), func(t *testing.T) {
actual, err := safeJoin(test.prefix, test.args...)
test.errAssertion(t, err)
assert.Equal(t, test.expected, actual)
})
}
}
func Test_copyWithLimits(t *testing.T) {
tests := []struct {
name string
input string
byteReadLimit int64
pathInArchive string
expectWritten string
expectErr bool
}{
{
name: "write bytes",
input: "something here",
byteReadLimit: 1000,
pathInArchive: "dont care",
expectWritten: "something here",
expectErr: false,
},
{
name: "surpass upper limit",
input: "something here",
byteReadLimit: 11,
pathInArchive: "dont care",
expectWritten: "something h",
expectErr: true,
},
// since we want the threshold being reached to be easily detectable, simply reaching the threshold is
// enough to cause an error. Otherwise surpassing the threshold would be undetectable.
{
name: "reach limit exactly",
input: "something here",
byteReadLimit: 14,
pathInArchive: "dont care",
expectWritten: "something here",
expectErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
writer := &bytes.Buffer{}
err := copyWithLimits(writer, strings.NewReader(test.input), test.byteReadLimit, test.pathInArchive)
if (err != nil) != test.expectErr {
t.Errorf("copyWithLimits() error = %v, want %v", err, test.expectErr)
return
} else if err != nil {
assert.Contains(t, err.Error(), test.pathInArchive)
}
assert.Equal(t, test.expectWritten, writer.String())
})
}
}