github.com/anchore/syft@v1.38.2/syft/internal/fileresolver/path_skipper_test.go (about)

     1  package fileresolver
     2  
     3  import (
     4  	"io/fs"
     5  	"testing"
     6  
     7  	"github.com/moby/sys/mountinfo"
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/require"
    10  )
    11  
    12  func Test_newPathSkipper(t *testing.T) {
    13  	type expect struct {
    14  		path    string
    15  		wantErr assert.ErrorAssertionFunc
    16  	}
    17  	unixSubject := []*mountinfo.Info{
    18  		{
    19  			Mountpoint: "/proc",
    20  			FSType:     "procfs",
    21  		},
    22  		{
    23  			Mountpoint: "/sys",
    24  			FSType:     "sysfs",
    25  		},
    26  		{
    27  			Mountpoint: "/dev",
    28  			FSType:     "devfs",
    29  		},
    30  		{
    31  			Mountpoint: "/",
    32  			FSType:     "/dev/disk3s1s1",
    33  		},
    34  		{
    35  			Mountpoint: "/dev/shm",
    36  			FSType:     "shm",
    37  		},
    38  		{
    39  			Mountpoint: "/tmp",
    40  			FSType:     "tmpfs",
    41  		},
    42  	}
    43  
    44  	tests := []struct {
    45  		name   string
    46  		root   string
    47  		base   string
    48  		mounts []*mountinfo.Info
    49  		want   []expect
    50  	}{
    51  		{
    52  			name: "happy path",
    53  			root: "/somewhere",
    54  			mounts: []*mountinfo.Info{
    55  				{
    56  					Mountpoint: "/home/somewhere/else",
    57  					FSType:     "/dev/disk3s6",
    58  				},
    59  				{
    60  					Mountpoint: "/somewhere",
    61  					FSType:     "/dev/disk3s7",
    62  				},
    63  			},
    64  			want: []expect{
    65  				{
    66  					// within a known mountpoint with valid type (1)
    67  					path: "/somewhere/dev",
    68  				},
    69  				{
    70  					// is a known mountpoint with valid type
    71  					path: "/somewhere",
    72  				},
    73  				{
    74  					// within a known mountpoint with valid type (2)
    75  					path: "/home/somewhere/else/too",
    76  				},
    77  				{
    78  					// outside of any known mountpoint should not be an error
    79  					path: "/bogus",
    80  				},
    81  			},
    82  		},
    83  		{
    84  			name: "ignore paths within a scan target",
    85  			root: "/somewhere",
    86  			mounts: []*mountinfo.Info{
    87  				{
    88  					Mountpoint: "/somewhere/doesnt/matter/proc",
    89  					FSType:     "procfs",
    90  				},
    91  				{
    92  					Mountpoint: "/somewhere",
    93  					FSType:     "/dev/disk3s7",
    94  				},
    95  			},
    96  			want: []expect{
    97  				{
    98  					// within a known mountpoint with valid type (1)
    99  					path: "/somewhere/dev",
   100  				},
   101  				{
   102  					// is a known mountpoint with valid type
   103  					path: "/somewhere",
   104  				},
   105  				{
   106  					// mountpoint that should be ignored
   107  					path:    "/somewhere/doesnt/matter/proc",
   108  					wantErr: assertSkipErr(),
   109  				},
   110  				{
   111  					// within a mountpoint that should be ignored
   112  					path:    "/somewhere/doesnt/matter/proc",
   113  					wantErr: assertSkipErr(),
   114  				},
   115  			},
   116  		},
   117  		{
   118  			name: "nested mountpoints behave correctly",
   119  			root: "/somewhere",
   120  			mounts: []*mountinfo.Info{
   121  				{
   122  					Mountpoint: "/somewhere/dev",
   123  					FSType:     "devfs",
   124  				},
   125  				{
   126  					Mountpoint: "/somewhere/dev/includeme",
   127  					FSType:     "/dev/disk3s7",
   128  				},
   129  			},
   130  			want: []expect{
   131  				{
   132  					// is a known mountpoint with valid type
   133  					path:    "/somewhere/dev",
   134  					wantErr: assertSkipErr(),
   135  				},
   136  				{
   137  					// is a known mountpoint with valid type
   138  					path: "/somewhere/dev/includeme",
   139  				},
   140  				{
   141  					// within a known mountpoint with valid type
   142  					path: "/somewhere/dev/includeme/too!",
   143  				},
   144  			},
   145  		},
   146  		{
   147  			name: "keep some tmpfs mounts conditionally",
   148  			root: "/",
   149  			mounts: []*mountinfo.Info{
   150  				{
   151  					Mountpoint: "/run/somewhere",
   152  					FSType:     "tmpfs",
   153  				},
   154  				{
   155  					Mountpoint: "/run/terrafirma",
   156  					FSType:     "/dev/disk3s8",
   157  				},
   158  				{
   159  					Mountpoint: "/tmp",
   160  					FSType:     "tmpfs",
   161  				},
   162  				{
   163  					Mountpoint: "/else/othertmp",
   164  					FSType:     "tmpfs",
   165  				},
   166  				{
   167  					Mountpoint: "/else/othertmp/includeme",
   168  					FSType:     "/dev/disk3s7",
   169  				},
   170  			},
   171  			want: []expect{
   172  				{
   173  					// since /run is explicitly ignored, this should be skipped
   174  					path:    "/run/somewhere/else",
   175  					wantErr: assertSkipErr(),
   176  				},
   177  				{
   178  					path: "/run/terrafirma",
   179  				},
   180  				{
   181  					path: "/run/terrafirma/nested",
   182  				},
   183  				{
   184  					path: "/tmp",
   185  				},
   186  				{
   187  					path: "/else/othertmp/includeme",
   188  				},
   189  				{
   190  					path: "/else/othertmp/includeme/nested",
   191  				},
   192  				{
   193  					// no mount path, so we should include it
   194  					path: "/somewhere/dev/includeme",
   195  				},
   196  				{
   197  					// keep additional tmpfs mounts that are not explicitly ignored
   198  					path: "/else/othertmp",
   199  				},
   200  			},
   201  		},
   202  		{
   203  			name: "ignore known trixy tmpfs paths",
   204  			root: "/",
   205  			mounts: []*mountinfo.Info{
   206  				{
   207  					Mountpoint: "/",
   208  					FSType:     "/dev/disk3s7",
   209  				},
   210  				{
   211  					Mountpoint: "/dev",
   212  					FSType:     "tmpfs",
   213  				},
   214  				{
   215  					Mountpoint: "/run",
   216  					FSType:     "tmpfs",
   217  				},
   218  				{
   219  					Mountpoint: "/var/run",
   220  					FSType:     "tmpfs",
   221  				},
   222  				{
   223  					Mountpoint: "/var/lock",
   224  					FSType:     "tmpfs",
   225  				},
   226  				{
   227  					Mountpoint: "/sys",
   228  					FSType:     "tmpfs",
   229  				},
   230  				{
   231  					Mountpoint: "/tmp",
   232  					FSType:     "tmpfs",
   233  				},
   234  			},
   235  			want: []expect{
   236  				{
   237  					path:    "/dev",
   238  					wantErr: assertSkipErr(),
   239  				},
   240  				{
   241  					path:    "/run",
   242  					wantErr: assertSkipErr(),
   243  				},
   244  				{
   245  					path:    "/var/run",
   246  					wantErr: assertSkipErr(),
   247  				},
   248  				{
   249  					path:    "/var/lock",
   250  					wantErr: assertSkipErr(),
   251  				},
   252  				{
   253  					path:    "/sys",
   254  					wantErr: assertSkipErr(),
   255  				},
   256  				// show that we honor ignoring nested paths
   257  				{
   258  					path:    "/sys/nested",
   259  					wantErr: assertSkipErr(),
   260  				},
   261  				// show that paths outside of the known mountpoints are not skipped
   262  				{
   263  					path: "/stuff",
   264  				},
   265  				// show that we allow other tmpfs paths that are not on the blocklist
   266  				{
   267  					path: "/tmp/allowed",
   268  				},
   269  				// show sibling paths with same prefix (e.g. /sys vs /system) to that of not allowed paths are not skipped
   270  				{
   271  					path: "/system",
   272  				},
   273  			},
   274  		},
   275  		{
   276  			name:   "test unix paths",
   277  			mounts: unixSubject,
   278  			root:   "/",
   279  			want: []expect{
   280  				{
   281  					// relative path to proc is allowed
   282  					path: "proc/place",
   283  				},
   284  				{
   285  					// relative path within proc is not allowed
   286  					path:    "/proc/place",
   287  					wantErr: assertSkipErr(),
   288  				},
   289  				{
   290  					// path exactly to proc is not allowed
   291  					path:    "/proc",
   292  					wantErr: assertSkipErr(),
   293  				},
   294  				{
   295  					// similar to proc
   296  					path: "/pro/c",
   297  				},
   298  				{
   299  					// similar to proc
   300  					path: "/pro",
   301  				},
   302  				{
   303  					// dev is not allowed
   304  					path:    "/dev",
   305  					wantErr: assertSkipErr(),
   306  				},
   307  				{
   308  					// sys is not allowed
   309  					path:    "/sys",
   310  					wantErr: assertSkipErr(),
   311  				},
   312  			},
   313  		},
   314  		{
   315  			name:   "test unix paths with base",
   316  			mounts: unixSubject,
   317  			root:   "/",
   318  			base:   "/a/b/c",
   319  			want: []expect{
   320  				{
   321  					// do not consider base when matching paths (non-matching)
   322  					path: "/a/b/c/dev",
   323  				},
   324  				{
   325  					// do not consider base when matching paths (matching)
   326  					path:    "/dev",
   327  					wantErr: assertSkipErr(),
   328  				},
   329  			},
   330  		},
   331  		{
   332  			name: "mimic nixos setup",
   333  			root: "/",
   334  			mounts: []*mountinfo.Info{
   335  				{
   336  					Mountpoint: "/",
   337  					FSType:     "tmpfs", // this is an odd setup, but valid
   338  				},
   339  				{
   340  					Mountpoint: "/home",
   341  					FSType:     "/dev/disk3s7",
   342  				},
   343  			},
   344  			want: []expect{
   345  				{
   346  					path: "/home/somewhere",
   347  				},
   348  				{
   349  					path: "/home",
   350  				},
   351  				{
   352  					path: "/somewhere",
   353  				},
   354  				{
   355  					// still not allowed...
   356  					path:    "/run",
   357  					wantErr: assertSkipErr(),
   358  				},
   359  			},
   360  		},
   361  		{
   362  			name: "buildkit github ubuntu 22.04",
   363  			root: "/run/src/core/sbom",
   364  			mounts: []*mountinfo.Info{
   365  				{Mountpoint: "/", FSType: "overlay"},
   366  				{Mountpoint: "/proc", FSType: "proc"},
   367  				{Mountpoint: "/dev", FSType: "tmpfs"},
   368  				{Mountpoint: "/dev/pts", FSType: "devpts"},
   369  				{Mountpoint: "/dev/shm", FSType: "tmpfs"},
   370  				{Mountpoint: "/dev/mqueue", FSType: "mqueue"},
   371  				{Mountpoint: "/sys", FSType: "sysfs"},
   372  				{Mountpoint: "/etc/resolv.conf", FSType: "ext4"},
   373  				{Mountpoint: "/etc/hosts", FSType: "ext4"},
   374  				{Mountpoint: "/sys/fs/cgroup", FSType: "cgroup2"},
   375  				{Mountpoint: "/run/out", FSType: "ext4"},
   376  				{Mountpoint: "/run/src/core/sbom", FSType: "overlay"},
   377  				{Mountpoint: "/tmp", FSType: "tmpfs"},
   378  				{Mountpoint: "/dev/otel-grpc.sock", FSType: "overlay"},
   379  				{Mountpoint: "/proc/bus", FSType: "proc"},
   380  				{Mountpoint: "/proc/fs", FSType: "proc"},
   381  				{Mountpoint: "/proc/irq", FSType: "proc"},
   382  				{Mountpoint: "/proc/sys", FSType: "proc"},
   383  				{Mountpoint: "/proc/sysrq-trigger", FSType: "proc"},
   384  				{Mountpoint: "/proc/acpi", FSType: "tmpfs"},
   385  				{Mountpoint: "/proc/kcore", FSType: "tmpfs"},
   386  				{Mountpoint: "/proc/keys", FSType: "tmpfs"},
   387  				{Mountpoint: "/proc/latency_stats", FSType: "tmpfs"},
   388  				{Mountpoint: "/proc/timer_list", FSType: "tmpfs"},
   389  				{Mountpoint: "/sys/firmware", FSType: "tmpfs"},
   390  				{Mountpoint: "/proc/scsi", FSType: "tmpfs"},
   391  			},
   392  			want: []expect{
   393  				{
   394  					path: "/run/src/core/sbom",
   395  				},
   396  			},
   397  		},
   398  	}
   399  	for _, tt := range tests {
   400  		t.Run(tt.name, func(t *testing.T) {
   401  			if tt.base == "" {
   402  				tt.base = tt.root
   403  			}
   404  
   405  			require.NotEmpty(t, tt.want)
   406  			ps := newPathSkipperFromMounts(tt.root, tt.mounts)
   407  
   408  			for _, exp := range tt.want {
   409  				t.Run(exp.path, func(t *testing.T) {
   410  
   411  					got := ps.pathIndexVisitor(tt.base, exp.path, nil, nil)
   412  					if exp.wantErr == nil {
   413  						assert.NoError(t, got)
   414  						return
   415  					}
   416  					exp.wantErr(t, got)
   417  
   418  				})
   419  			}
   420  		})
   421  	}
   422  }
   423  
   424  func assertSkipErr() assert.ErrorAssertionFunc {
   425  	return assertErrorIs(fs.SkipDir)
   426  }
   427  
   428  func assertErrorIs(want error) assert.ErrorAssertionFunc {
   429  	return func(t assert.TestingT, got error, msgAndArgs ...interface{}) bool {
   430  		return assert.ErrorIs(t, got, want, msgAndArgs...)
   431  	}
   432  }