mirror of
https://github.com/anchore/grype
synced 2024-11-10 06:34:13 +00:00
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:
parent
09fdabd814
commit
5f7620fb80
2 changed files with 0 additions and 244 deletions
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue