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 }