github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/rust/cargoauditable/cargoauditable.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 cargoauditable extracts dependencies from cargo auditable inside rust binaries. 16 package cargoauditable 17 18 import ( 19 "context" 20 "errors" 21 "fmt" 22 "io" 23 24 "github.com/google/osv-scalibr/extractor" 25 "github.com/google/osv-scalibr/extractor/filesystem" 26 "github.com/google/osv-scalibr/inventory" 27 "github.com/google/osv-scalibr/log" 28 "github.com/google/osv-scalibr/plugin" 29 "github.com/google/osv-scalibr/purl" 30 "github.com/google/osv-scalibr/stats" 31 "github.com/rust-secure-code/go-rustaudit" 32 ) 33 34 const ( 35 // Name is the unique name of this extractor. 36 Name = "rust/cargoauditable" 37 ) 38 39 // defaultMaxFileSizeBytes is the maximum file size an extractor will unmarshal. 40 // If Extract gets a bigger file, it will return an error. 41 const defaultMaxFileSizeBytes = 0 42 43 // defaultExtractBuildDependencies is whether to extract build dependencies or only runtime ones. 44 const defaultExtractBuildDependencies = false 45 46 // Config is the configuration for the Extractor. 47 type Config struct { 48 // Stats is a stats collector for reporting metrics. 49 Stats stats.Collector 50 // MaxFileSizeBytes is the maximum size of a file that can be extracted. 51 // If this limit is greater than zero and a file is encountered that is larger 52 // than this limit, the file is ignored by returning false for `FileRequired`. 53 MaxFileSizeBytes int64 54 // ExtractBuildDependencies is whether to extract build dependencies or only runtime ones. 55 ExtractBuildDependencies bool 56 } 57 58 // Extractor for extracting dependencies from cargo auditable inside rust binaries. 59 type Extractor struct { 60 stats stats.Collector 61 maxFileSizeBytes int64 62 extractBuildDependencies bool 63 } 64 65 // DefaultConfig returns a default configuration for the extractor. 66 func DefaultConfig() Config { 67 return Config{ 68 Stats: nil, 69 MaxFileSizeBytes: defaultMaxFileSizeBytes, 70 ExtractBuildDependencies: defaultExtractBuildDependencies, 71 } 72 } 73 74 // New returns a Cargo Auditable extractor. 75 // 76 // For most use cases, initialize with: 77 // ``` 78 // e := New(DefaultConfig()) 79 // ``` 80 func New(cfg Config) *Extractor { 81 return &Extractor{ 82 stats: cfg.Stats, 83 maxFileSizeBytes: cfg.MaxFileSizeBytes, 84 extractBuildDependencies: cfg.ExtractBuildDependencies, 85 } 86 } 87 88 // NewDefault returns an extractor with the default config settings. 89 func NewDefault() filesystem.Extractor { return New(DefaultConfig()) } 90 91 // Name of the extractor. 92 func (e Extractor) Name() string { return Name } 93 94 // Version of the extractor. 95 func (e Extractor) Version() int { return 0 } 96 97 // Requirements for enabling the extractor. 98 func (e Extractor) Requirements() *plugin.Capabilities { return &plugin.Capabilities{} } 99 100 // FileRequired returns true if the specified file is marked executable. 101 func (e Extractor) FileRequired(api filesystem.FileAPI) bool { 102 path := api.Path() 103 104 fileinfo, err := api.Stat() 105 if err != nil { 106 return false 107 } 108 109 if !filesystem.IsInterestingExecutable(api) { 110 return false 111 } 112 113 sizeLimitExceeded := e.maxFileSizeBytes > 0 && fileinfo.Size() > e.maxFileSizeBytes 114 result := stats.FileRequiredResultOK 115 if sizeLimitExceeded { 116 result = stats.FileRequiredResultSizeLimitExceeded 117 } 118 119 if e.stats != nil { 120 e.stats.AfterFileRequired(e.Name(), &stats.FileRequiredStats{ 121 Path: path, 122 Result: result, 123 FileSizeBytes: fileinfo.Size(), 124 }) 125 } 126 return !sizeLimitExceeded 127 } 128 129 // Extract extracts packages from cargo auditable inside rust binaries. 130 func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) { 131 reader, ok := input.Reader.(io.ReaderAt) 132 if !ok { 133 return inventory.Inventory{}, errors.New("input.Reader is not a ReaderAt") 134 } 135 136 dependencyInfo, err := rustaudit.GetDependencyInfo(reader) 137 e.reportFileExtracted(input, filesystem.ExtractorErrorToFileExtractedResult(err)) 138 // Most errors are just that the file is not a cargo auditable rust binary. 139 if err != nil { 140 if errors.Is(err, rustaudit.ErrUnknownFileFormat) || errors.Is(err, rustaudit.ErrNoRustDepInfo) { 141 return inventory.Inventory{}, nil 142 } 143 log.Debugf("error getting dependency information from binary (%s) for extraction: %v", input.Path, err) 144 return inventory.Inventory{}, fmt.Errorf("rustaudit.GetDependencyInfo: %w", err) 145 } 146 147 pkgs := []*extractor.Package{} 148 for _, dep := range dependencyInfo.Packages { 149 // Cargo auditable also tracks build-only dependencies which we may not want to report. 150 // Note: the main package is reported as a runtime dependency. 151 if dep.Kind == rustaudit.Runtime || e.extractBuildDependencies { 152 pkgs = append(pkgs, &extractor.Package{ 153 Name: dep.Name, 154 Version: dep.Version, 155 PURLType: purl.TypeCargo, 156 Locations: []string{input.Path}, 157 }) 158 } 159 } 160 return inventory.Inventory{Packages: pkgs}, nil 161 } 162 163 func (e Extractor) reportFileExtracted(input *filesystem.ScanInput, result stats.FileExtractedResult) { 164 if e.stats == nil { 165 return 166 } 167 e.stats.AfterFileExtracted(e.Name(), &stats.FileExtractedStats{ 168 Path: input.Path, 169 Result: result, 170 FileSizeBytes: input.Info.Size(), 171 }) 172 } 173 174 // Ensure Extractor implements the filesystem.Extractor interface. 175 var _ filesystem.Extractor = Extractor{}