Skip to content

Commit

Permalink
Add cargo auditable deps extractor for Rust bins to SCALIBR
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 713069568
  • Loading branch information
rpbeltran authored and copybara-github committed Jan 27, 2025
1 parent 170a25a commit 5abb23f
Show file tree
Hide file tree
Showing 10 changed files with 511 additions and 1 deletion.
179 changes: 179 additions & 0 deletions extractor/filesystem/language/rust/cargoauditable/cargoauditable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright 2025 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"
)

// defaultMaxFileSizeBytes is the maximum file size an extractor will unmarshal.
// If Extract gets a bigger file, it will return an error.
const defaultMaxFileSizeBytes = 0

// defaultExtractBuildDependencies is whether to extract build dependencies or only runtime ones.
const defaultExtractBuildDependencies = false

// 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
// ExtractBuildDependencies is whether to extract build dependencies or only runtime ones.
ExtractBuildDependencies bool
}

// Extractor for extracting dependencies from cargo auditable inside rust binaries.
type Extractor struct {
stats stats.Collector
maxFileSizeBytes int64
extractBuildDependencies bool
}

// DefaultConfig returns a default configuration for the extractor.
func DefaultConfig() Config {
return Config{
Stats: nil,
MaxFileSizeBytes: defaultMaxFileSizeBytes,
ExtractBuildDependencies: defaultExtractBuildDependencies,
}
}

// 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,
extractBuildDependencies: cfg.ExtractBuildDependencies,
}
}

// Name of the extractor.
func (e Extractor) Name() string { return "rust/cargoauditable" }

// Version of the extractor.
func (e Extractor) Version() int { return 0 }

// 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(api) {
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(e.Name(), &stats.FileRequiredStats{
Path: path,
Result: result,
FileSizeBytes: fileinfo.Size(),
})
}
return !sizeLimitExceeded
}

// Extract extracts packages from cargo auditable inside rust binaries.
func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) ([]*extractor.Inventory, error) {
reader, ok := input.Reader.(io.ReaderAt)
if !ok {
return nil, fmt.Errorf("input.Reader is not a ReaderAt")
}

dependencyInfo, err := rustaudit.GetDependencyInfo(reader)
e.reportFileExtracted(input, filesystem.ExtractorErrorToFileExtractedResult(err))
// Most errors are just that the file is not a cargo auditable rust binary.
if err != nil {
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)
}

inventory := []*extractor.Inventory{}
for _, dep := range dependencyInfo.Packages {
// Cargo auditable also tracks build-only dependencies which we may not want to report.
// Note: the main package is reported as a runtime dependency.
if dep.Kind == rustaudit.Runtime || e.extractBuildDependencies {
inventory = append(inventory, &extractor.Inventory{
Name: dep.Name,
Version: dep.Version,
Locations: []string{input.Path},
})
}
}
return inventory, nil
}

func (e Extractor) reportFileExtracted(input *filesystem.ScanInput, result stats.FileExtractedResult) {
if e.stats == nil {
return
}
e.stats.AfterFileExtracted(e.Name(), &stats.FileExtractedStats{
Path: input.Path,
Result: result,
FileSizeBytes: input.Info.Size(),
})
}

// 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"
}

// Ensure Extractor implements the filesystem.Extractor interface.
var _ filesystem.Extractor = Extractor{}
Loading

0 comments on commit 5abb23f

Please sign in to comment.