-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
cargo auditable
deps extractor for Rust bins to SCALIBR
PiperOrigin-RevId: 713069568
- Loading branch information
1 parent
1b5bfeb
commit 68433f2
Showing
13 changed files
with
599 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
173 changes: 173 additions & 0 deletions
173
extractor/filesystem/language/rust/cargoauditable/cargoauditable.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
// Copyright 2024 Google LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
// Package cargoauditable extracts dependencies from cargo auditable inside rust binaries. | ||
package cargoauditable | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"io" | ||
|
||
"github.com/google/osv-scalibr/extractor" | ||
"github.com/google/osv-scalibr/extractor/filesystem" | ||
"github.com/google/osv-scalibr/log" | ||
"github.com/google/osv-scalibr/plugin" | ||
"github.com/google/osv-scalibr/purl" | ||
"github.com/google/osv-scalibr/stats" | ||
"github.com/microsoft/go-rustaudit/v/v0/rustaudit" | ||
Check failure on line 30 in extractor/filesystem/language/rust/cargoauditable/cargoauditable.go GitHub Actions / tests (ubuntu-latest)
|
||
) | ||
|
||
const pluginName = "cargoauditable" | ||
const pluginVersion = 0 | ||
|
||
// defaultMaxFileSizeBytes is the maximum file size an extractor will unmarshal. | ||
// If Extract gets a bigger file, it will return an error. | ||
const defaultMaxFileSizeBytes = 0 | ||
|
||
// Config is the configuration for the Extractor. | ||
type Config struct { | ||
// Stats is a stats collector for reporting metrics. | ||
Stats stats.Collector | ||
// MaxFileSizeBytes is the maximum size of a file that can be extracted. | ||
// If this limit is greater than zero and a file is encountered that is larger | ||
// than this limit, the file is ignored by returning false for `FileRequired`. | ||
MaxFileSizeBytes int64 | ||
} | ||
|
||
// Extractor extracts extracts dependencies from cargo auditable inside rust binaries. | ||
type Extractor struct { | ||
stats stats.Collector | ||
maxFileSizeBytes int64 | ||
} | ||
|
||
// DefaultConfig returns a default configuration for the extractor. | ||
func DefaultConfig() Config { | ||
return Config{ | ||
Stats: nil, | ||
MaxFileSizeBytes: defaultMaxFileSizeBytes, | ||
} | ||
} | ||
|
||
// New returns a Cargo Auditable extractor. | ||
// | ||
// For most use cases, initialize with: | ||
// ``` | ||
// e := New(DefaultConfig()) | ||
// ``` | ||
func New(cfg Config) *Extractor { | ||
return &Extractor{ | ||
stats: cfg.Stats, | ||
maxFileSizeBytes: cfg.MaxFileSizeBytes, | ||
} | ||
} | ||
|
||
// Name of the extractor. | ||
func (e Extractor) Name() string { return pluginName } | ||
|
||
// Version of the extractor. | ||
func (e Extractor) Version() int { return pluginVersion } | ||
|
||
// Requirements for enabling the extractor. | ||
func (e Extractor) Requirements() *plugin.Capabilities { return &plugin.Capabilities{} } | ||
|
||
// FileRequired returns true if the specified file is marked executable. | ||
func (e Extractor) FileRequired(api filesystem.FileAPI) bool { | ||
path := api.Path() | ||
|
||
fileinfo, err := api.Stat() | ||
if err != nil { | ||
return false | ||
} | ||
|
||
if !filesystem.IsInterestingExecutable(path, fileinfo) { | ||
return false | ||
} | ||
|
||
sizeLimitExceeded := e.maxFileSizeBytes > 0 && fileinfo.Size() > e.maxFileSizeBytes | ||
result := stats.FileRequiredResultOK | ||
if sizeLimitExceeded { | ||
result = stats.FileRequiredResultSizeLimitExceeded | ||
} | ||
|
||
if e.stats != nil { | ||
e.stats.AfterFileRequired(pluginName, &stats.FileRequiredStats{ | ||
Path: path, | ||
Result: result, | ||
FileSizeBytes: fileinfo.Size(), | ||
}) | ||
} | ||
return !sizeLimitExceeded | ||
} | ||
|
||
// ToPURL converts an inventory created by this extractor into a PURL. | ||
func (e Extractor) ToPURL(i *extractor.Inventory) *purl.PackageURL { | ||
return &purl.PackageURL{ | ||
Type: purl.TypeCargo, | ||
Name: i.Name, | ||
Version: i.Version, | ||
} | ||
} | ||
|
||
// Ecosystem returns the OSV ecosystem ('crates.io') of the software extracted by this extractor. | ||
func (e Extractor) Ecosystem(_ *extractor.Inventory) string { | ||
return "crates.io" | ||
} | ||
|
||
// Extract extracts packages from cargo auditable inside rust binaries. | ||
func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) ([]*extractor.Inventory, error) { | ||
dependencyInfo, err := rustaudit.GetDependencyInfo(input.Reader.(io.ReaderAt)) | ||
if err != nil { | ||
if e.stats != nil { | ||
e.stats.AfterFileExtracted(pluginName, &stats.FileExtractedStats{ | ||
Path: input.Path, | ||
Result: filesystem.ExtractorErrorToFileExtractedResult(err), | ||
FileSizeBytes: input.Info.Size(), | ||
}) | ||
} | ||
|
||
// Most likely the file is simply not a rust binary or was built without cargo auditable enabled. | ||
if errors.Is(err, rustaudit.ErrUnknownFileFormat) || errors.Is(err, rustaudit.ErrNoRustDepInfo) { | ||
return []*extractor.Inventory{}, nil | ||
} | ||
|
||
log.Debugf("error getting dependency information from binary (%s) for extraction: %v", input.Path, err) | ||
return nil, fmt.Errorf("rustaudit.GetDependencyInfo(%q): %w", input.Path, err) | ||
} | ||
|
||
var inventory []*extractor.Inventory | ||
for _, dep := range dependencyInfo.Packages { | ||
// Cargo auditable also tracks build dependencies which we don't want to report. | ||
if dep.Kind == rustaudit.Runtime { | ||
inventory = append(inventory, &extractor.Inventory{ | ||
Name: dep.Name, | ||
Version: dep.Version, | ||
Locations: []string{input.Path}, | ||
}) | ||
} | ||
if e.stats != nil { | ||
e.stats.AfterFileExtracted(pluginName, &stats.FileExtractedStats{ | ||
Path: input.Path, | ||
Result: stats.FileExtractedResultSuccess, | ||
FileSizeBytes: input.Info.Size(), | ||
}) | ||
} | ||
} | ||
|
||
return inventory, nil | ||
} | ||
|
||
// Ensure Extractor implements the filesystem.Extractor interface. | ||
var _ filesystem.Extractor = Extractor{} |
Oops, something went wrong.