github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/os/kernel/vmlinuz/vmlinuz.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package vmlinuz extracts information about vmlinuz compressed kernel images. 16 package vmlinuz 17 18 import ( 19 "context" 20 "errors" 21 "fmt" 22 "path/filepath" 23 "strconv" 24 "strings" 25 26 "github.com/deitch/magic/pkg/magic" 27 "github.com/google/osv-scalibr/extractor" 28 "github.com/google/osv-scalibr/extractor/filesystem" 29 "github.com/google/osv-scalibr/extractor/filesystem/internal/units" 30 vmlinuzmeta "github.com/google/osv-scalibr/extractor/filesystem/os/kernel/vmlinuz/metadata" 31 "github.com/google/osv-scalibr/extractor/filesystem/os/osrelease" 32 scalibrfs "github.com/google/osv-scalibr/fs" 33 "github.com/google/osv-scalibr/inventory" 34 "github.com/google/osv-scalibr/log" 35 "github.com/google/osv-scalibr/plugin" 36 "github.com/google/osv-scalibr/stats" 37 ) 38 39 const ( 40 // Name is the unique name of this extractor. 41 Name = "os/kernel/vmlinuz" 42 43 // defaultMaxFileSizeBytes is the maximum file size an extractor will unmarshal. 44 // If Extract gets a bigger file, it will return an error. 45 defaultMaxFileSizeBytes = 30 * units.MiB 46 ) 47 48 // Config is the configuration for the Extractor. 49 type Config struct { 50 // Stats is a stats collector for reporting metrics. 51 Stats stats.Collector 52 // MaxFileSizeBytes is the maximum file size this extractor will unmarshal. If 53 // `FileRequired` gets a bigger file, it will return false, 54 MaxFileSizeBytes int64 55 } 56 57 // DefaultConfig returns the default configuration for the kernel vmlinuz extractor. 58 func DefaultConfig() Config { 59 return Config{ 60 Stats: nil, 61 MaxFileSizeBytes: defaultMaxFileSizeBytes, 62 } 63 } 64 65 // Extractor extracts information from kernel vmlinuz files (vmlinuz). 66 type Extractor struct { 67 stats stats.Collector 68 maxFileSizeBytes int64 69 } 70 71 // New returns a kernel vmlinuz extractor. 72 func New(cfg Config) *Extractor { 73 return &Extractor{ 74 stats: cfg.Stats, 75 maxFileSizeBytes: cfg.MaxFileSizeBytes, 76 } 77 } 78 79 // NewDefault returns an extractor with the default config settings. 80 func NewDefault() filesystem.Extractor { return New(DefaultConfig()) } 81 82 // Config returns the configuration of the extractor. 83 func (e Extractor) Config() Config { 84 return Config{ 85 Stats: e.stats, 86 MaxFileSizeBytes: e.maxFileSizeBytes, 87 } 88 } 89 90 // Name of the extractor. 91 func (e Extractor) Name() string { return Name } 92 93 // Version of the extractor. 94 func (e Extractor) Version() int { return 0 } 95 96 // Requirements of the extractor. 97 func (e Extractor) Requirements() *plugin.Capabilities { return &plugin.Capabilities{} } 98 99 // FileRequired returns true if the specified file matches the vmlinuz file patterns. 100 func (e Extractor) FileRequired(api filesystem.FileAPI) bool { 101 path := api.Path() 102 103 if !strings.HasPrefix(path, "boot/") { 104 return false 105 } 106 107 if !(filepath.Base(path) == "vmlinuz" || strings.HasPrefix(filepath.Base(path), "vmlinuz-")) { 108 return false 109 } 110 111 fileinfo, err := api.Stat() 112 if err != nil { 113 return false 114 } 115 if e.maxFileSizeBytes > 0 && fileinfo.Size() > e.maxFileSizeBytes { 116 e.reportFileRequired(path, fileinfo.Size(), stats.FileRequiredResultSizeLimitExceeded) 117 return false 118 } 119 120 e.reportFileRequired(path, fileinfo.Size(), stats.FileRequiredResultOK) 121 return true 122 } 123 124 func (e Extractor) reportFileRequired(path string, fileSizeBytes int64, result stats.FileRequiredResult) { 125 if e.stats == nil { 126 return 127 } 128 e.stats.AfterFileRequired(e.Name(), &stats.FileRequiredStats{ 129 Path: path, 130 Result: result, 131 FileSizeBytes: fileSizeBytes, 132 }) 133 } 134 135 // Extract extracts information from vmlinuz files passed through the scan input. 136 func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) { 137 pkgs, err := e.extractFromInput(input) 138 139 if e.stats != nil { 140 var fileSizeBytes int64 141 if input.Info != nil { 142 fileSizeBytes = input.Info.Size() 143 } 144 e.stats.AfterFileExtracted(e.Name(), &stats.FileExtractedStats{ 145 Path: input.Path, 146 Result: filesystem.ExtractorErrorToFileExtractedResult(err), 147 FileSizeBytes: fileSizeBytes, 148 }) 149 } 150 return inventory.Inventory{Packages: pkgs}, err 151 } 152 153 func (e Extractor) extractFromInput(input *filesystem.ScanInput) ([]*extractor.Package, error) { 154 packages := []*extractor.Package{} 155 156 m, err := osrelease.GetOSRelease(input.FS) 157 if err != nil { 158 log.Errorf("osrelease.ParseOsRelease(): %v", err) 159 } 160 161 r, err := scalibrfs.NewReaderAt(input.Reader) 162 if err != nil { 163 return nil, fmt.Errorf("NewReaderAt: %w", err) 164 } 165 166 magicType, err := magic.GetType(r) 167 if err != nil { 168 return nil, fmt.Errorf("error determining magic type: %w", err) 169 } 170 171 if len(magicType) == 0 || magicType[0] != "Linux kernel" { 172 return nil, errors.New("no match with linux kernel found") 173 } 174 175 metadata := parseVmlinuzMetadata(magicType) 176 177 metadata.OSID = m["ID"] 178 metadata.OSVersionCodename = m["VERSION_CODENAME"] 179 metadata.OSVersionID = m["VERSION_ID"] 180 181 p := &extractor.Package{ 182 Name: metadata.Name, 183 Version: metadata.Version, 184 Metadata: &metadata, 185 Locations: []string{input.Path}, 186 } 187 188 packages = append(packages, p) 189 190 return packages, nil 191 } 192 193 func parseVmlinuzMetadata(magicType []string) vmlinuzmeta.Metadata { 194 var m vmlinuzmeta.Metadata 195 196 m.Name = "Linux Kernel" 197 198 for _, t := range magicType { 199 switch { 200 // Architecture 201 case strings.HasPrefix(t, "x86 "): 202 m.Architecture = "x86" 203 case strings.HasPrefix(t, "ARM64 "): 204 m.Architecture = "arm64" 205 case strings.HasPrefix(t, "ARM "): 206 m.Architecture = "arm" 207 208 // Format 209 case t == "bzImage": 210 m.Format = "bzImage" 211 case t == "zImage": 212 m.Format = "zImage" 213 214 // Version and extended version 215 case strings.HasPrefix(t, "version "): 216 m.ExtendedVersion = strings.TrimPrefix(t, "version ") 217 if fields := strings.Fields(m.ExtendedVersion); len(fields) > 0 { 218 m.Version = fields[0] 219 } 220 221 // RW-rootFS 222 case strings.Contains(t, "rootFS") && strings.HasPrefix(t, "RW-"): 223 m.RWRootFS = true 224 225 // Swap device 226 case strings.HasPrefix(t, "swap_dev "): 227 swapHex := strings.TrimPrefix(t, "swap_dev 0X") 228 swapConv, err := strconv.ParseInt(swapHex, 16, 32) 229 if err != nil { 230 log.Errorf("Failed to parse swap device: %v", err) 231 continue 232 } 233 m.SwapDevice = int32(swapConv) 234 235 // Root device 236 case strings.HasPrefix(t, "root_dev "): 237 rootHex := strings.TrimPrefix(t, "swap_dev 0X") 238 rootConv, err := strconv.ParseInt(rootHex, 16, 32) 239 if err != nil { 240 log.Errorf("Failed to parse swap device: %v", err) 241 continue 242 } 243 m.RootDevice = int32(rootConv) 244 245 // Video mode 246 case strings.Contains(t, "VGA") || strings.Contains(t, "Video"): 247 m.VideoMode = t 248 } 249 } 250 return m 251 }