mirror of
https://github.com/anchore/syft.git
synced 2026-08-20 17:18:30 +02:00
fix: panic when scanning squashfs symlinks (#5119)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
This commit is contained in:
parent
ab508169e6
commit
ed76e96749
@ -314,19 +314,7 @@ func squashfsVisitor(ft filetree.Writer, fileCatalog *image.FileCatalog, size *i
|
||||
|
||||
prog.AtomicStage.Set(path)
|
||||
|
||||
var f filesystem.File
|
||||
var mimeType string
|
||||
var err error
|
||||
|
||||
if !d.IsDir() {
|
||||
f, err = fsys.OpenFile(intFile.ToFSPath(path), os.O_RDONLY)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "path", path).Trace("unable to open squash file path")
|
||||
} else {
|
||||
defer f.Close()
|
||||
mimeType = stereoFile.MIMEType(f)
|
||||
}
|
||||
}
|
||||
|
||||
var ty stereoFile.Type
|
||||
var linkPath string
|
||||
@ -335,11 +323,21 @@ func squashfsVisitor(ft filetree.Writer, fileCatalog *image.FileCatalog, size *i
|
||||
// in some implementations, the mode does not indicate a directory, so we check the FileInfo type explicitly
|
||||
ty = stereoFile.TypeDirectory
|
||||
default:
|
||||
f, err := fsys.OpenFile(intFile.ToFSPath(path), os.O_RDONLY)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "path", path).Trace("unable to open squash file path")
|
||||
}
|
||||
if f != nil {
|
||||
defer f.Close()
|
||||
}
|
||||
|
||||
ty = stereoFile.TypeFromMode(d.Mode())
|
||||
if ty == stereoFile.TypeSymLink && f != nil {
|
||||
if l, ok := f.(linker); ok {
|
||||
linkPath, _ = l.Readlink()
|
||||
}
|
||||
} else {
|
||||
mimeType = stereoFile.MIMEType(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,23 +1,31 @@
|
||||
package snapsource
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
diskFile "github.com/diskfs/go-diskfs/backend/file"
|
||||
"github.com/diskfs/go-diskfs/filesystem"
|
||||
"github.com/diskfs/go-diskfs/filesystem/squashfs"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/go-homedir"
|
||||
stereoFile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/stereoscope/pkg/filetree"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
@ -294,3 +302,102 @@ func writeSquashfs(t *testing.T, manifest string) string {
|
||||
require.NoError(t, fs.Finalize(squashfs.FinalizeOptions{}))
|
||||
return snapPath
|
||||
}
|
||||
|
||||
func Test_SquashfsSymlinkCrash(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
info fakeFileInfo
|
||||
file *fakeSquashFile
|
||||
wantType stereoFile.Type
|
||||
wantLinkDest string
|
||||
wantMIME assert.ValueAssertionFunc
|
||||
}{
|
||||
{
|
||||
// regression: previously the visitor opened every non-dir file and computed its MIME type
|
||||
// before inspecting the type, which consumed the symlink's reader so Readlink returned
|
||||
// nothing and the symlink was given a bogus MIME type. The fix only reads the link for
|
||||
// symlinks and only computes a MIME type for everything else.
|
||||
name: "symlink resolves link destination and is not given a mime type",
|
||||
path: "/link",
|
||||
info: fakeFileInfo{name: "link", mode: os.ModeSymlink | 0o777},
|
||||
// the reader returns content so that the (buggy) behavior of computing a MIME type would
|
||||
// have produced a non-empty MIME type, making this test fail against the old code.
|
||||
file: &fakeSquashFile{Reader: bytes.NewReader([]byte("/usr/bin/target")), linkTarget: "/usr/bin/target"},
|
||||
wantType: stereoFile.TypeSymLink,
|
||||
wantLinkDest: "/usr/bin/target",
|
||||
wantMIME: assert.Empty,
|
||||
},
|
||||
{
|
||||
name: "regular file is given a mime type",
|
||||
path: "/file.txt",
|
||||
info: fakeFileInfo{name: "file.txt", mode: 0o644, size: 11},
|
||||
file: &fakeSquashFile{Reader: bytes.NewReader([]byte("hello world"))},
|
||||
wantType: stereoFile.TypeRegular,
|
||||
wantMIME: assert.NotEmpty,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tree := filetree.New()
|
||||
catalog := image.NewFileCatalog()
|
||||
prog := &monitor.TaskProgress{
|
||||
AtomicStage: progress.NewAtomicStage(""),
|
||||
Manual: progress.NewManual(-1),
|
||||
}
|
||||
|
||||
fsys := fakeSquashFS{openFile: func(string, int) (filesystem.File, error) {
|
||||
return tt.file, nil
|
||||
}}
|
||||
|
||||
visit := squashfsVisitor(tree, catalog, nil, prog)
|
||||
require.NoError(t, visit(fsys, tt.path, tt.info, nil))
|
||||
|
||||
entries, err := catalog.GetByBasename(tt.info.name)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, entries, 1)
|
||||
|
||||
entry := entries[0]
|
||||
assert.Equal(t, tt.wantType, entry.Metadata.Type)
|
||||
assert.Equal(t, tt.wantLinkDest, entry.Metadata.LinkDestination)
|
||||
tt.wantMIME(t, entry.Metadata.MIMEType)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// fakeFileInfo is a minimal os.FileInfo for driving squashfsVisitor in tests.
|
||||
type fakeFileInfo struct {
|
||||
name string
|
||||
mode os.FileMode
|
||||
size int64
|
||||
}
|
||||
|
||||
func (f fakeFileInfo) Name() string { return f.name }
|
||||
func (f fakeFileInfo) Size() int64 { return f.size }
|
||||
func (f fakeFileInfo) Mode() os.FileMode { return f.mode }
|
||||
func (f fakeFileInfo) ModTime() time.Time { return time.Time{} }
|
||||
func (f fakeFileInfo) IsDir() bool { return f.mode.IsDir() }
|
||||
func (f fakeFileInfo) Sys() any { return nil }
|
||||
|
||||
// fakeSquashFile implements filesystem.File and the linker interface used by squashfsVisitor.
|
||||
type fakeSquashFile struct {
|
||||
*bytes.Reader
|
||||
linkTarget string
|
||||
}
|
||||
|
||||
func (f *fakeSquashFile) Write([]byte) (int, error) { return 0, fs.ErrInvalid }
|
||||
func (f *fakeSquashFile) Close() error { return nil }
|
||||
func (f *fakeSquashFile) Stat() (fs.FileInfo, error) { return nil, fs.ErrInvalid }
|
||||
func (f *fakeSquashFile) Readlink() (string, error) { return f.linkTarget, nil }
|
||||
|
||||
// fakeSquashFS implements filesystem.FileSystem but only supports OpenFile; all other methods
|
||||
// are inherited from the embedded nil interface and will panic if unexpectedly called.
|
||||
type fakeSquashFS struct {
|
||||
filesystem.FileSystem
|
||||
openFile func(string, int) (filesystem.File, error)
|
||||
}
|
||||
|
||||
func (f fakeSquashFS) OpenFile(name string, flag int) (filesystem.File, error) {
|
||||
return f.openFile(name, flag)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user