From e859d10eef8dfba2942f922fd45a04d99d934082 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 11 May 2023 14:41:17 +0300 Subject: [PATCH] feat: allow root break for mapfs (#4094) --- pkg/fanal/artifact/local/fs.go | 4 +++- pkg/mapfs/fs.go | 39 +++++++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/pkg/fanal/artifact/local/fs.go b/pkg/fanal/artifact/local/fs.go index 171d9e46ef78..b1d70021bff6 100644 --- a/pkg/fanal/artifact/local/fs.go +++ b/pkg/fanal/artifact/local/fs.go @@ -242,7 +242,9 @@ func (a Artifact) buildFS(dir, filePath string, info os.FileInfo, files *syncx.M // Create fs.FS for each post-analyzer that wants to analyze the current file for _, at := range atypes { - mfs, _ := files.LoadOrStore(at, mapfs.New()) + // Since filesystem scanning may require access outside the specified path, (e.g. Terraform modules) + // it allows "../" access with "WithUnderlyingRoot". + mfs, _ := files.LoadOrStore(at, mapfs.New(mapfs.WithUnderlyingRoot(dir))) if d := filepath.Dir(filePath); d != "." { if err := mfs.MkdirAll(d, os.ModePerm); err != nil && !errors.Is(err, fs.ErrExist) { return xerrors.Errorf("mapfs mkdir error: %w", err) diff --git a/pkg/mapfs/fs.go b/pkg/mapfs/fs.go index 114c5f06975a..d92a3fb48318 100644 --- a/pkg/mapfs/fs.go +++ b/pkg/mapfs/fs.go @@ -28,11 +28,26 @@ var _ allFS = &FS{} // FS is an in-memory filesystem type FS struct { root *file + + // When the underlyingRoot has a value, it allows access to the local filesystem outside of this in-memory filesystem. + // The set path is used as the starting point when accessing the local filesystem. + // In other words, although mapfs.Open("../foo") would normally result in an error, if this option is enabled, + // it will be executed as os.Open(filepath.Join(underlyingRoot, "../foo")). + underlyingRoot string +} + +type Option func(*FS) + +// WithUnderlyingRoot returns an option to set the underlying root path for the in-memory filesystem. +func WithUnderlyingRoot(root string) Option { + return func(fsys *FS) { + fsys.underlyingRoot = root + } } // New creates a new filesystem -func New() *FS { - return &FS{ +func New(opts ...Option) *FS { + fsys := &FS{ root: &file{ stat: fileStat{ name: ".", @@ -43,6 +58,10 @@ func New() *FS { files: syncx.Map[string, *file]{}, }, } + for _, opt := range opts { + opt(fsys) + } + return fsys } // Filter removes the specified skippedFiles and returns a new FS @@ -57,7 +76,7 @@ func (m *FS) Filter(skippedFiles []string) (*FS, error) { } func (m *FS) FilterFunc(fn func(path string, d fs.DirEntry) (bool, error)) (*FS, error) { - newFS := New() + newFS := New(WithUnderlyingRoot(m.underlyingRoot)) err := fs.WalkDir(m, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { return err @@ -103,6 +122,10 @@ func (m *FS) CopyFilesUnder(dir string) error { // Stat returns a FileInfo describing the file. func (m *FS) Stat(name string) (fs.FileInfo, error) { + if strings.HasPrefix(name, "../") && m.underlyingRoot != "" { + return os.Stat(filepath.Join(m.underlyingRoot, name)) + } + name = cleanPath(name) f, err := m.root.getFile(name) if err != nil { @@ -121,11 +144,17 @@ func (m *FS) Stat(name string) (fs.FileInfo, error) { // ReadDir reads the named directory // and returns a list of directory entries sorted by filename. func (m *FS) ReadDir(name string) ([]fs.DirEntry, error) { + if strings.HasPrefix(name, "../") && m.underlyingRoot != "" { + return os.ReadDir(filepath.Join(m.underlyingRoot, name)) + } return m.root.ReadDir(cleanPath(name)) } // Open opens the named file for reading. func (m *FS) Open(name string) (fs.File, error) { + if strings.HasPrefix(name, "../") && m.underlyingRoot != "" { + return os.Open(filepath.Join(m.underlyingRoot, name)) + } return m.root.Open(cleanPath(name)) } @@ -158,6 +187,10 @@ func (m *FS) MkdirAll(path string, perm fs.FileMode) error { // The caller is permitted to modify the returned byte slice. // This method should return a copy of the underlying data. func (m *FS) ReadFile(name string) ([]byte, error) { + if strings.HasPrefix(name, "../") && m.underlyingRoot != "" { + return os.ReadFile(filepath.Join(m.underlyingRoot, name)) + } + f, err := m.root.Open(cleanPath(name)) if err != nil { return nil, err