mirror of
https://github.com/anchore/syft
synced 2024-11-10 06:14:16 +00:00
fix: only skip tmpfs mounts for some paths (#2918)
* fix: only skip tmpfs mounts for some paths Signed-off-by: Will Murphy <will.murphy@anchore.com> * refactor and add tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add regression test for archive processing Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * bump to golang 1.22 Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * remove rule 1 and add more tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Will Murphy <will.murphy@anchore.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
cb09dd9e19
commit
557ad73ee6
10 changed files with 669 additions and 199 deletions
2
.github/actions/bootstrap/action.yaml
vendored
2
.github/actions/bootstrap/action.yaml
vendored
|
@ -5,7 +5,7 @@ inputs:
|
|||
go-version:
|
||||
description: "Go version to install"
|
||||
required: true
|
||||
default: "1.21.x"
|
||||
default: "1.22.x"
|
||||
go-dependencies:
|
||||
description: "Download go dependencies"
|
||||
required: true
|
||||
|
|
2
go.mod
2
go.mod
|
@ -1,6 +1,6 @@
|
|||
module github.com/anchore/syft
|
||||
|
||||
go 1.21.0
|
||||
go 1.22.0
|
||||
|
||||
require (
|
||||
github.com/CycloneDX/cyclonedx-go v0.8.0
|
||||
|
|
|
@ -9,13 +9,11 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/sys/mountinfo"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/stereoscope/pkg/filetree"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
|
@ -43,7 +41,8 @@ func newDirectoryIndexer(path, base string, visitors ...PathIndexVisitor) *direc
|
|||
[]PathIndexVisitor{
|
||||
requireFileInfo,
|
||||
disallowByFileType,
|
||||
newUnixSystemMountFinder().disallowUnixSystemRuntimePath},
|
||||
skipPathsByMountTypeAndName(path),
|
||||
},
|
||||
visitors...,
|
||||
),
|
||||
errPaths: make(map[string]error),
|
||||
|
@ -450,57 +449,6 @@ func (r *directoryIndexer) disallowRevisitingVisitor(_, path string, _ os.FileIn
|
|||
return nil
|
||||
}
|
||||
|
||||
type unixSystemMountFinder struct {
|
||||
disallowedMountPaths []string
|
||||
}
|
||||
|
||||
func newUnixSystemMountFinder() unixSystemMountFinder {
|
||||
infos, err := mountinfo.GetMounts(nil)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warnf("unable to get system mounts")
|
||||
return unixSystemMountFinder{}
|
||||
}
|
||||
|
||||
return unixSystemMountFinder{
|
||||
disallowedMountPaths: keepUnixSystemMountPaths(infos),
|
||||
}
|
||||
}
|
||||
|
||||
func keepUnixSystemMountPaths(infos []*mountinfo.Info) []string {
|
||||
var mountPaths []string
|
||||
for _, info := range infos {
|
||||
if info == nil {
|
||||
continue
|
||||
}
|
||||
// we're only interested in ignoring the logical filesystems typically found at these mount points:
|
||||
// - /proc
|
||||
// - procfs
|
||||
// - proc
|
||||
// - /sys
|
||||
// - sysfs
|
||||
// - /dev
|
||||
// - devfs - BSD/darwin flavored systems and old linux systems
|
||||
// - devtmpfs - driver core maintained /dev tmpfs
|
||||
// - udev - userspace implementation that replaced devfs
|
||||
// - tmpfs - used for /dev in special instances (within a container)
|
||||
|
||||
switch info.FSType {
|
||||
case "proc", "procfs", "sysfs", "devfs", "devtmpfs", "udev", "tmpfs":
|
||||
log.WithFields("mountpoint", info.Mountpoint).Debug("ignoring system mountpoint")
|
||||
|
||||
mountPaths = append(mountPaths, info.Mountpoint)
|
||||
}
|
||||
}
|
||||
return mountPaths
|
||||
}
|
||||
|
||||
func (f unixSystemMountFinder) disallowUnixSystemRuntimePath(_, path string, _ os.FileInfo, _ error) error {
|
||||
if internal.HasAnyOfPrefixes(path, f.disallowedMountPaths...) {
|
||||
return fs.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func disallowByFileType(_, _ string, info os.FileInfo, _ error) error {
|
||||
if info == nil {
|
||||
// we can't filter out by filetype for non-existent files
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/moby/sys/mountinfo"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -462,145 +461,3 @@ func relativePath(basePath, givenPath string) string {
|
|||
|
||||
return relPath
|
||||
}
|
||||
|
||||
func Test_disallowUnixSystemRuntimePath(t *testing.T) {
|
||||
unixSubject := unixSystemMountFinder{
|
||||
// mock out detecting the mount points
|
||||
disallowedMountPaths: []string{"/proc", "/sys", "/dev"},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
base string
|
||||
expected error
|
||||
}{
|
||||
{
|
||||
name: "relative path to proc is allowed",
|
||||
path: "proc/place",
|
||||
},
|
||||
{
|
||||
name: "relative path within proc is not allowed",
|
||||
path: "/proc/place",
|
||||
expected: fs.SkipDir,
|
||||
},
|
||||
{
|
||||
name: "path exactly to proc is not allowed",
|
||||
path: "/proc",
|
||||
expected: fs.SkipDir,
|
||||
},
|
||||
{
|
||||
name: "similar to proc",
|
||||
path: "/pro/c",
|
||||
},
|
||||
{
|
||||
name: "similar to proc",
|
||||
path: "/pro",
|
||||
},
|
||||
{
|
||||
name: "dev is not allowed",
|
||||
path: "/dev",
|
||||
expected: fs.SkipDir,
|
||||
},
|
||||
{
|
||||
name: "sys is not allowed",
|
||||
path: "/sys",
|
||||
expected: fs.SkipDir,
|
||||
},
|
||||
{
|
||||
name: "unrelated allowed path",
|
||||
path: "/something/sys",
|
||||
},
|
||||
{
|
||||
name: "do not consider base when matching paths (non-matching)",
|
||||
base: "/a/b/c",
|
||||
path: "/a/b/c/dev",
|
||||
},
|
||||
{
|
||||
name: "do not consider base when matching paths (matching)",
|
||||
base: "/a/b/c",
|
||||
path: "/dev",
|
||||
expected: fs.SkipDir,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.path, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, unixSubject.disallowUnixSystemRuntimePath(test.base, test.path, nil, nil))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_keepUnixSystemMountPaths(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
infos []*mountinfo.Info
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "all valid filesystems",
|
||||
infos: []*mountinfo.Info{
|
||||
{
|
||||
Mountpoint: "/etc/hostname",
|
||||
FSType: "/dev/vda1",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/sys/fs/cgroup",
|
||||
FSType: "cgroup",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/",
|
||||
FSType: "overlay",
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "no valid filesystems",
|
||||
infos: []*mountinfo.Info{
|
||||
{
|
||||
Mountpoint: "/proc",
|
||||
FSType: "proc",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/proc-2",
|
||||
FSType: "procfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/sys",
|
||||
FSType: "sysfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/dev",
|
||||
FSType: "devfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/dev-u",
|
||||
FSType: "udev",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/dev-tmp",
|
||||
FSType: "devtmpfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/run",
|
||||
FSType: "tmpfs",
|
||||
},
|
||||
},
|
||||
want: []string{
|
||||
"/proc",
|
||||
"/proc-2",
|
||||
"/sys",
|
||||
"/dev",
|
||||
"/dev-u",
|
||||
"/dev-tmp",
|
||||
"/run",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, keepUnixSystemMountPaths(tt.infos))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
157
syft/internal/fileresolver/path_skipper.go
Normal file
157
syft/internal/fileresolver/path_skipper.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
package fileresolver
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/sys/mountinfo"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
type pathSkipper struct {
|
||||
// scanTarget is the root path that is being scanned (without any base-path logic applied).
|
||||
scanTarget string
|
||||
|
||||
// ignorableMountTypes is a set of mount types that should be ignored. Optionally a list of paths (the map values)
|
||||
// can be provided that this mount type should be ignored at. For example in some containers /dev is mounted
|
||||
// as a tmpfs and should be ignored, but /tmp should not be ignored. An empty list of paths means that paths
|
||||
// within the mount type should always be ignored.
|
||||
ignorableMountTypes map[string][]string
|
||||
|
||||
// current mount paths for the current system
|
||||
mounts []*mountinfo.Info
|
||||
mountsByType map[string][]*mountinfo.Info
|
||||
}
|
||||
|
||||
// skipPathsByMountTypeAndName accepts the root path and returns a PathIndexVisitor that will skip paths based
|
||||
// the filesystem type, the mountpoint, and configured blocklist paths for each filesystem type.
|
||||
// This will help syft dodge filesystem topologies that have the potential to make the search space much bigger in
|
||||
// areas known to not traditionally contain files of interest (installed software). It is meant to allow scanning
|
||||
// "/" on a unix host to succeed, while also not causing any files in a narrow directory scan to be skipped unnecessarily.
|
||||
func skipPathsByMountTypeAndName(root string) PathIndexVisitor {
|
||||
infos, err := mountinfo.GetMounts(nil)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warnf("unable to get system mounts")
|
||||
return func(_ string, _ string, _ os.FileInfo, _ error) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return newPathSkipperFromMounts(root, infos).pathIndexVisitor
|
||||
}
|
||||
|
||||
func newPathSkipperFromMounts(root string, infos []*mountinfo.Info) pathSkipper {
|
||||
// we're only interested in ignoring the logical filesystems typically found at these mount points:
|
||||
// - /proc
|
||||
// - procfs
|
||||
// - proc
|
||||
// - /sys
|
||||
// - sysfs
|
||||
// - /dev
|
||||
// - devfs - BSD/darwin flavored systems and old linux systems
|
||||
// - devtmpfs - driver core maintained /dev tmpfs
|
||||
// - udev - userspace implementation that replaced devfs
|
||||
// - tmpfs - used for /dev in special instances (within a container)
|
||||
ignorableMountTypes := map[string][]string{
|
||||
"proc": nil,
|
||||
"procfs": nil,
|
||||
"sysfs": nil,
|
||||
"devfs": nil,
|
||||
"devtmpfs": nil,
|
||||
"udev": nil,
|
||||
// note: there should be no order required (e.g. search /sys/thing before /sys) since that would imply that
|
||||
// we could not ignore a nested path within a path that would be ignored anyway.
|
||||
"tmpfs": {"/run", "/dev", "/var/run", "/var/lock", "/sys"},
|
||||
}
|
||||
|
||||
// The longest path is the most specific path, e.g.
|
||||
// if / is mounted as tmpfs, but /home/syft/permanent is mounted as ext4,
|
||||
// then the mount type for /home/syft/permanent/foo is ext4, and the mount info
|
||||
// stating that /home/syft/permanent is ext4 has the longer mount point.
|
||||
sort.Slice(infos, func(i, j int) bool {
|
||||
return len(infos[i].Mountpoint) > len(infos[j].Mountpoint)
|
||||
})
|
||||
|
||||
mountsByType := make(map[string][]*mountinfo.Info)
|
||||
|
||||
for _, mi := range infos {
|
||||
mountsByType[mi.FSType] = append(mountsByType[mi.FSType], mi)
|
||||
}
|
||||
|
||||
return pathSkipper{
|
||||
scanTarget: root,
|
||||
ignorableMountTypes: ignorableMountTypes,
|
||||
mounts: infos,
|
||||
mountsByType: mountsByType,
|
||||
}
|
||||
}
|
||||
|
||||
func (ps pathSkipper) pathIndexVisitor(_ string, givenPath string, _ os.FileInfo, _ error) error {
|
||||
for _, mi := range ps.mounts {
|
||||
conditionalPaths, ignorable := ps.ignorableMountTypes[mi.FSType]
|
||||
|
||||
if len(conditionalPaths) == 0 {
|
||||
// Rule 1: ignore any path within a mount point that is of the given filesystem type unconditionally
|
||||
if !containsPath(givenPath, mi.Mountpoint) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !ignorable {
|
||||
// we've matched on the most specific path at this point, which means we should stop searching
|
||||
// mount points for this path
|
||||
break
|
||||
}
|
||||
|
||||
log.WithFields(
|
||||
"path", givenPath,
|
||||
"mountpoint", mi.Mountpoint,
|
||||
"fs", mi.FSType,
|
||||
).Debug("ignoring path based on mountpoint filesystem type")
|
||||
|
||||
return fs.SkipDir
|
||||
}
|
||||
|
||||
// Rule 2: ignore any path within a mount point that is of the given filesystem type, only if
|
||||
// the path is on a known blocklist of paths for that filesystem type.
|
||||
// For example: /dev can be mounted as a tmpfs, which should always be skipped.
|
||||
for _, conditionalPath := range conditionalPaths {
|
||||
if !containsPath(givenPath, conditionalPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
log.WithFields(
|
||||
"path", givenPath,
|
||||
"mountpoint", mi.Mountpoint,
|
||||
"fs", mi.FSType,
|
||||
"condition", conditionalPath,
|
||||
).Debug("ignoring path based on mountpoint filesystem type")
|
||||
|
||||
return fs.SkipDir
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func containsPath(p1, p2 string) bool {
|
||||
p1Clean := simpleClean(p1)
|
||||
p2Clean := simpleClean(p2)
|
||||
if p1Clean == p2Clean {
|
||||
return true
|
||||
}
|
||||
return strings.HasPrefix(p1Clean, p2Clean+"/")
|
||||
}
|
||||
|
||||
func simpleClean(p string) string {
|
||||
p = strings.TrimSpace(p)
|
||||
if p == "" {
|
||||
return "."
|
||||
}
|
||||
if p == "/" {
|
||||
return "/"
|
||||
}
|
||||
return strings.TrimSuffix(p, "/")
|
||||
}
|
395
syft/internal/fileresolver/path_skipper_test.go
Normal file
395
syft/internal/fileresolver/path_skipper_test.go
Normal file
|
@ -0,0 +1,395 @@
|
|||
package fileresolver
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"testing"
|
||||
|
||||
"github.com/moby/sys/mountinfo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_newPathSkipper(t *testing.T) {
|
||||
type expect struct {
|
||||
path string
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}
|
||||
unixSubject := []*mountinfo.Info{
|
||||
{
|
||||
Mountpoint: "/proc",
|
||||
FSType: "procfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/sys",
|
||||
FSType: "sysfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/dev",
|
||||
FSType: "devfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/",
|
||||
FSType: "/dev/disk3s1s1",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/dev/shm",
|
||||
FSType: "shm",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/tmp",
|
||||
FSType: "tmpfs",
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
root string
|
||||
base string
|
||||
mounts []*mountinfo.Info
|
||||
want []expect
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
root: "/somewhere",
|
||||
mounts: []*mountinfo.Info{
|
||||
{
|
||||
Mountpoint: "/home/somewhere/else",
|
||||
FSType: "/dev/disk3s6",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/somewhere",
|
||||
FSType: "/dev/disk3s7",
|
||||
},
|
||||
},
|
||||
want: []expect{
|
||||
{
|
||||
// within a known mountpoint with valid type (1)
|
||||
path: "/somewhere/dev",
|
||||
},
|
||||
{
|
||||
// is a known mountpoint with valid type
|
||||
path: "/somewhere",
|
||||
},
|
||||
{
|
||||
// within a known mountpoint with valid type (2)
|
||||
path: "/home/somewhere/else/too",
|
||||
},
|
||||
{
|
||||
// outside of any known mountpoint should not be an error
|
||||
path: "/bogus",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ignore paths within a scan target",
|
||||
root: "/somewhere",
|
||||
mounts: []*mountinfo.Info{
|
||||
{
|
||||
Mountpoint: "/somewhere/doesnt/matter/proc",
|
||||
FSType: "procfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/somewhere",
|
||||
FSType: "/dev/disk3s7",
|
||||
},
|
||||
},
|
||||
want: []expect{
|
||||
{
|
||||
// within a known mountpoint with valid type (1)
|
||||
path: "/somewhere/dev",
|
||||
},
|
||||
{
|
||||
// is a known mountpoint with valid type
|
||||
path: "/somewhere",
|
||||
},
|
||||
{
|
||||
// mountpoint that should be ignored
|
||||
path: "/somewhere/doesnt/matter/proc",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
{
|
||||
// within a mountpoint that should be ignored
|
||||
path: "/somewhere/doesnt/matter/proc",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested mountpoints behave correctly",
|
||||
root: "/somewhere",
|
||||
mounts: []*mountinfo.Info{
|
||||
{
|
||||
Mountpoint: "/somewhere/dev",
|
||||
FSType: "devfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/somewhere/dev/includeme",
|
||||
FSType: "/dev/disk3s7",
|
||||
},
|
||||
},
|
||||
want: []expect{
|
||||
{
|
||||
// is a known mountpoint with valid type
|
||||
path: "/somewhere/dev",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
{
|
||||
// is a known mountpoint with valid type
|
||||
path: "/somewhere/dev/includeme",
|
||||
},
|
||||
{
|
||||
// within a known mountpoint with valid type
|
||||
path: "/somewhere/dev/includeme/too!",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "keep some tmpfs mounts conditionally",
|
||||
root: "/",
|
||||
mounts: []*mountinfo.Info{
|
||||
{
|
||||
Mountpoint: "/run/somewhere",
|
||||
FSType: "tmpfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/run/terrafirma",
|
||||
FSType: "/dev/disk3s8",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/tmp",
|
||||
FSType: "tmpfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/else/othertmp",
|
||||
FSType: "tmpfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/else/othertmp/includeme",
|
||||
FSType: "/dev/disk3s7",
|
||||
},
|
||||
},
|
||||
want: []expect{
|
||||
{
|
||||
// since /run is explicitly ignored, this should be skipped
|
||||
path: "/run/somewhere/else",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
{
|
||||
path: "/run/terrafirma",
|
||||
},
|
||||
{
|
||||
path: "/run/terrafirma/nested",
|
||||
},
|
||||
{
|
||||
path: "/tmp",
|
||||
},
|
||||
{
|
||||
path: "/else/othertmp/includeme",
|
||||
},
|
||||
{
|
||||
path: "/else/othertmp/includeme/nested",
|
||||
},
|
||||
{
|
||||
// no mount path, so we should include it
|
||||
path: "/somewhere/dev/includeme",
|
||||
},
|
||||
{
|
||||
// keep additional tmpfs mounts that are not explicitly ignored
|
||||
path: "/else/othertmp",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ignore known trixy tmpfs paths",
|
||||
root: "/",
|
||||
mounts: []*mountinfo.Info{
|
||||
{
|
||||
Mountpoint: "/",
|
||||
FSType: "/dev/disk3s7",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/dev",
|
||||
FSType: "tmpfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/run",
|
||||
FSType: "tmpfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/var/run",
|
||||
FSType: "tmpfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/var/lock",
|
||||
FSType: "tmpfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/sys",
|
||||
FSType: "tmpfs",
|
||||
},
|
||||
{
|
||||
Mountpoint: "/tmp",
|
||||
FSType: "tmpfs",
|
||||
},
|
||||
},
|
||||
want: []expect{
|
||||
{
|
||||
path: "/dev",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
{
|
||||
path: "/run",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
{
|
||||
path: "/var/run",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
{
|
||||
path: "/var/lock",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
{
|
||||
path: "/sys",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
// show that we honor ignoring nested paths
|
||||
{
|
||||
path: "/sys/nested",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
// show that paths outside of the known mountpoints are not skipped
|
||||
{
|
||||
path: "/stuff",
|
||||
},
|
||||
// show that we allow other tmpfs paths that are not on the blocklist
|
||||
{
|
||||
path: "/tmp/allowed",
|
||||
},
|
||||
// show sibling paths with same prefix (e.g. /sys vs /system) to that of not allowed paths are not skipped
|
||||
{
|
||||
path: "/system",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test unix paths",
|
||||
mounts: unixSubject,
|
||||
root: "/",
|
||||
want: []expect{
|
||||
{
|
||||
// relative path to proc is allowed
|
||||
path: "proc/place",
|
||||
},
|
||||
{
|
||||
// relative path within proc is not allowed
|
||||
path: "/proc/place",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
{
|
||||
// path exactly to proc is not allowed
|
||||
path: "/proc",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
{
|
||||
// similar to proc
|
||||
path: "/pro/c",
|
||||
},
|
||||
{
|
||||
// similar to proc
|
||||
path: "/pro",
|
||||
},
|
||||
{
|
||||
// dev is not allowed
|
||||
path: "/dev",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
{
|
||||
// sys is not allowed
|
||||
path: "/sys",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test unix paths with base",
|
||||
mounts: unixSubject,
|
||||
root: "/",
|
||||
base: "/a/b/c",
|
||||
want: []expect{
|
||||
{
|
||||
// do not consider base when matching paths (non-matching)
|
||||
path: "/a/b/c/dev",
|
||||
},
|
||||
{
|
||||
// do not consider base when matching paths (matching)
|
||||
path: "/dev",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mimic nixos setup",
|
||||
root: "/",
|
||||
mounts: []*mountinfo.Info{
|
||||
{
|
||||
Mountpoint: "/",
|
||||
FSType: "tmpfs", // this is an odd setup, but valid
|
||||
},
|
||||
{
|
||||
Mountpoint: "/home",
|
||||
FSType: "/dev/disk3s7",
|
||||
},
|
||||
},
|
||||
want: []expect{
|
||||
{
|
||||
path: "/home/somewhere",
|
||||
},
|
||||
{
|
||||
path: "/home",
|
||||
},
|
||||
{
|
||||
path: "/somewhere",
|
||||
},
|
||||
{
|
||||
// still not allowed...
|
||||
path: "/run",
|
||||
wantErr: assertSkipErr(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.base == "" {
|
||||
tt.base = tt.root
|
||||
}
|
||||
|
||||
require.NotEmpty(t, tt.want)
|
||||
ps := newPathSkipperFromMounts(tt.root, tt.mounts)
|
||||
|
||||
for _, exp := range tt.want {
|
||||
t.Run(exp.path, func(t *testing.T) {
|
||||
|
||||
got := ps.pathIndexVisitor(tt.base, exp.path, nil, nil)
|
||||
if exp.wantErr == nil {
|
||||
assert.NoError(t, got)
|
||||
return
|
||||
}
|
||||
exp.wantErr(t, got)
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertSkipErr() assert.ErrorAssertionFunc {
|
||||
return assertErrorIs(fs.SkipDir)
|
||||
}
|
||||
|
||||
func assertErrorIs(want error) assert.ErrorAssertionFunc {
|
||||
return func(t assert.TestingT, got error, msgAndArgs ...interface{}) bool {
|
||||
return assert.ErrorIs(t, got, want, msgAndArgs...)
|
||||
}
|
||||
}
|
60
test/cli/archive_test.go
Normal file
60
test/cli/archive_test.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestArchiveScan(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
archiveFixture string
|
||||
env map[string]string
|
||||
assertions []traitAssertion
|
||||
}{
|
||||
{
|
||||
name: "scan an archive within the temp dir",
|
||||
args: []string{
|
||||
"scan",
|
||||
"-o",
|
||||
"json",
|
||||
"file:" + createArchive(t, "test-fixtures/archive", t.TempDir()),
|
||||
},
|
||||
assertions: []traitAssertion{
|
||||
assertSuccessfulReturnCode,
|
||||
assertJsonReport,
|
||||
assertPackageCount(1),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
cmd, stdout, stderr := runSyft(t, test.env, test.args...)
|
||||
for _, traitAssertionFn := range test.assertions {
|
||||
traitAssertionFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
||||
}
|
||||
logOutputOnFailure(t, cmd, stdout, stderr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createArchive(t *testing.T, path string, destDir string) string {
|
||||
// create a tarball of the test fixtures (not by shelling out)
|
||||
archivePath := filepath.Join(destDir, "test.tar")
|
||||
|
||||
fh, err := os.Create(archivePath)
|
||||
require.NoError(t, err)
|
||||
defer fh.Close()
|
||||
|
||||
writer := tar.NewWriter(fh)
|
||||
require.NoError(t, writer.AddFS(os.DirFS(path)))
|
||||
require.NoError(t, writer.Close())
|
||||
|
||||
return archivePath
|
||||
}
|
47
test/cli/test-fixtures/archive/dist-info/METADATA
Normal file
47
test/cli/test-fixtures/archive/dist-info/METADATA
Normal file
|
@ -0,0 +1,47 @@
|
|||
Metadata-Version: 2.1
|
||||
Name: Pygments
|
||||
Version: 2.6.1
|
||||
Summary: Pygments is a syntax highlighting package written in Python.
|
||||
Home-page: https://pygments.org/
|
||||
Author: Georg Brandl
|
||||
Author-email: georg@python.org
|
||||
License: BSD License
|
||||
Keywords: syntax highlighting
|
||||
Platform: any
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Intended Audience :: End Users/Desktop
|
||||
Classifier: Intended Audience :: System Administrators
|
||||
Classifier: Development Status :: 6 - Mature
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Topic :: Text Processing :: Filters
|
||||
Classifier: Topic :: Utilities
|
||||
Requires-Python: >=3.5
|
||||
|
||||
|
||||
Pygments
|
||||
~~~~~~~~
|
||||
|
||||
Pygments is a syntax highlighting package written in Python.
|
||||
|
||||
It is a generic syntax highlighter suitable for use in code hosting, forums,
|
||||
wikis or other applications that need to prettify source code. Highlights
|
||||
are:
|
||||
|
||||
* a wide range of over 500 languages and other text formats is supported
|
||||
* special attention is paid to details, increasing quality by a fair amount
|
||||
* support for new languages and formats are added easily
|
||||
* a number of output formats, presently HTML, LaTeX, RTF, SVG, all image formats that PIL supports and ANSI sequences
|
||||
* it is usable as a command-line tool and as a library
|
||||
|
||||
:copyright: Copyright 2006-2019 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
|
5
test/cli/test-fixtures/archive/dist-info/RECORD
Normal file
5
test/cli/test-fixtures/archive/dist-info/RECORD
Normal file
|
@ -0,0 +1,5 @@
|
|||
../../../bin/pygmentize,sha256=dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8,220
|
||||
Pygments-2.6.1.dist-info/AUTHORS,sha256=PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY,8449
|
||||
Pygments-2.6.1.dist-info/RECORD,,
|
||||
pygments/__pycache__/__init__.cpython-38.pyc,,
|
||||
pygments/util.py,sha256=586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA,10778
|
1
test/cli/test-fixtures/archive/dist-info/top_level.txt
Normal file
1
test/cli/test-fixtures/archive/dist-info/top_level.txt
Normal file
|
@ -0,0 +1 @@
|
|||
top-level-pkg
|
Loading…
Reference in a new issue