github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/walker/vm.go (about)

     1  package walker
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"io/fs"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/masahiro331/go-disk"
    11  	"github.com/masahiro331/go-disk/gpt"
    12  	"github.com/masahiro331/go-disk/mbr"
    13  	"github.com/masahiro331/go-disk/types"
    14  	"golang.org/x/exp/slices"
    15  	"golang.org/x/xerrors"
    16  
    17  	dio "github.com/aquasecurity/go-dep-parser/pkg/io"
    18  	"github.com/devseccon/trivy/pkg/fanal/vm/filesystem"
    19  	"github.com/devseccon/trivy/pkg/log"
    20  )
    21  
    22  var requiredDiskName = []string{
    23  	"Linux",    // AmazonLinux image name
    24  	"p.lxroot", // SLES image name
    25  	"primary",  // Common image name
    26  	"0",        // Common image name
    27  	"1",        // Common image name
    28  	"2",        // Common image name
    29  	"3",        // Common image name
    30  }
    31  
    32  func AppendPermitDiskName(s ...string) {
    33  	requiredDiskName = append(requiredDiskName, s...)
    34  }
    35  
    36  type VM struct {
    37  	walker
    38  	threshold int64
    39  	analyzeFn WalkFunc
    40  }
    41  
    42  func NewVM(skipFiles, skipDirs []string) *VM {
    43  	threshold := defaultSizeThreshold
    44  	return &VM{
    45  		walker:    newWalker(skipFiles, skipDirs),
    46  		threshold: threshold,
    47  	}
    48  }
    49  
    50  func (w *VM) Walk(vreader *io.SectionReader, root string, fn WalkFunc) error {
    51  	// This function will be called on each file.
    52  	w.analyzeFn = fn
    53  
    54  	driver, err := disk.NewDriver(vreader)
    55  	if err != nil {
    56  		return xerrors.Errorf("failed to new disk driver: %w", err)
    57  	}
    58  
    59  	for {
    60  		partition, err := driver.Next()
    61  		if err != nil {
    62  			if err == io.EOF {
    63  				break
    64  			}
    65  			return xerrors.Errorf("failed to get a next partition: %w", err)
    66  		}
    67  
    68  		// skip boot partition
    69  		if shouldSkip(partition) {
    70  			continue
    71  		}
    72  
    73  		// Walk each partition
    74  		if err = w.diskWalk(root, partition); err != nil {
    75  			log.Logger.Warnf("Partition error: %s", err.Error())
    76  		}
    77  	}
    78  	return nil
    79  }
    80  
    81  // Inject disk partitioning processes from externally with diskWalk.
    82  func (w *VM) diskWalk(root string, partition types.Partition) error {
    83  	log.Logger.Debugf("Found partition: %s", partition.Name())
    84  
    85  	sr := partition.GetSectionReader()
    86  
    87  	// Trivy does not support LVM scanning. It is skipped at the moment.
    88  	foundLVM, err := w.detectLVM(sr)
    89  	if err != nil {
    90  		return xerrors.Errorf("LVM detection error: %w", err)
    91  	} else if foundLVM {
    92  		log.Logger.Errorf("LVM is not supported, skip %s.img", partition.Name())
    93  		return nil
    94  	}
    95  
    96  	// Auto-detect filesystem such as ext4 and xfs
    97  	fsys, clean, err := filesystem.New(sr)
    98  	if err != nil {
    99  		return xerrors.Errorf("filesystem error: %w", err)
   100  	}
   101  	defer clean()
   102  
   103  	err = fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
   104  		// Walk filesystem
   105  		return w.fsWalk(fsys, path, d, err)
   106  	})
   107  	if err != nil {
   108  		return xerrors.Errorf("filesystem walk error: %w", err)
   109  	}
   110  	return nil
   111  }
   112  
   113  func (w *VM) fsWalk(fsys fs.FS, path string, d fs.DirEntry, err error) error {
   114  	if err != nil {
   115  		return xerrors.Errorf("fs.Walk error: %w", err)
   116  	}
   117  	fi, err := d.Info()
   118  	if err != nil {
   119  		return xerrors.Errorf("dir entry info error: %w", err)
   120  	}
   121  	pathName := strings.TrimPrefix(filepath.Clean(path), "/")
   122  	switch {
   123  	case fi.IsDir():
   124  		if w.shouldSkipDir(pathName) {
   125  			return filepath.SkipDir
   126  		}
   127  		return nil
   128  	case !fi.Mode().IsRegular():
   129  		return nil
   130  	case w.shouldSkipFile(pathName):
   131  		return nil
   132  	case fi.Mode()&0x1000 == 0x1000 ||
   133  		fi.Mode()&0x2000 == 0x2000 ||
   134  		fi.Mode()&0x6000 == 0x6000 ||
   135  		fi.Mode()&0xA000 == 0xA000 ||
   136  		fi.Mode()&0xc000 == 0xc000:
   137  		// 	0x1000:	S_IFIFO (FIFO)
   138  		// 	0x2000:	S_IFCHR (Character device)
   139  		// 	0x6000:	S_IFBLK (Block device)
   140  		// 	0xA000:	S_IFLNK (Symbolic link)
   141  		// 	0xC000:	S_IFSOCK (Socket)
   142  		return nil
   143  	}
   144  
   145  	cvf := newCachedVMFile(fsys, pathName, w.threshold)
   146  	defer cvf.Clean()
   147  
   148  	if err = w.analyzeFn(path, fi, cvf.Open); err != nil {
   149  		return xerrors.Errorf("failed to analyze file: %w", err)
   150  	}
   151  	return nil
   152  }
   153  
   154  type cachedVMFile struct {
   155  	fs        fs.FS
   156  	filePath  string
   157  	threshold int64
   158  
   159  	cf *cachedFile
   160  }
   161  
   162  func newCachedVMFile(fsys fs.FS, filePath string, threshold int64) *cachedVMFile {
   163  	return &cachedVMFile{
   164  		fs:        fsys,
   165  		filePath:  filePath,
   166  		threshold: threshold,
   167  	}
   168  }
   169  
   170  func (cvf *cachedVMFile) Open() (dio.ReadSeekCloserAt, error) {
   171  	if cvf.cf != nil {
   172  		return cvf.cf.Open()
   173  	}
   174  
   175  	f, err := cvf.fs.Open(cvf.filePath)
   176  	if err != nil {
   177  		return nil, xerrors.Errorf("file open error: %w", err)
   178  	}
   179  	fi, err := f.Stat()
   180  	if err != nil {
   181  		return nil, xerrors.Errorf("file stat error: %w", err)
   182  	}
   183  
   184  	cvf.cf = newCachedFile(fi.Size(), f, cvf.threshold)
   185  	return cvf.cf.Open()
   186  }
   187  
   188  func (cvf *cachedVMFile) Clean() error {
   189  	if cvf.cf == nil {
   190  		return nil
   191  	}
   192  	return cvf.cf.Clean()
   193  }
   194  
   195  func (w *VM) detectLVM(sr io.SectionReader) (bool, error) {
   196  	buf := make([]byte, 512)
   197  	_, err := sr.ReadAt(buf, 512)
   198  	if err != nil {
   199  		return false, xerrors.Errorf("read header block error: %w", err)
   200  	}
   201  	_, err = sr.Seek(0, io.SeekStart)
   202  	if err != nil {
   203  		return false, xerrors.Errorf("seek error: %w", err)
   204  	}
   205  
   206  	// LABELONE is LVM signature
   207  	if string(buf[:8]) == "LABELONE" {
   208  		return true, nil
   209  	}
   210  	return false, nil
   211  }
   212  
   213  func shouldSkip(partition types.Partition) bool {
   214  	// skip empty partition
   215  	if bytes.Equal(partition.GetType(), []byte{0x00}) {
   216  		return true
   217  	}
   218  
   219  	if !slices.Contains(requiredDiskName, partition.Name()) {
   220  		return true
   221  	}
   222  
   223  	switch p := partition.(type) {
   224  	case *gpt.PartitionEntry:
   225  		return p.Bootable()
   226  	case *mbr.Partition:
   227  		return false
   228  	}
   229  	return false
   230  }