feat: report unknowns in sbom (#2998)

Signed-off-by: Keith Zantow <kzantow@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Keith Zantow 2024-10-07 16:11:37 -04:00 committed by GitHub
parent 4d7ed9f749
commit ccbee94b87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
145 changed files with 4420 additions and 233 deletions

View File

@ -53,6 +53,9 @@ type Catalog struct {
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"` Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
Source sourceConfig `yaml:"source" json:"source" mapstructure:"source"` Source sourceConfig `yaml:"source" json:"source" mapstructure:"source"`
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"` Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
// configuration for inclusion of unknown information within elements
Unknowns unknownsConfig `yaml:"unknowns" mapstructure:"unknowns"`
} }
var _ interface { var _ interface {
@ -71,6 +74,7 @@ func DefaultCatalog() Catalog {
Java: defaultJavaConfig(), Java: defaultJavaConfig(),
File: defaultFileConfig(), File: defaultFileConfig(),
Relationships: defaultRelationshipsConfig(), Relationships: defaultRelationshipsConfig(),
Unknowns: defaultUnknowns(),
Source: defaultSourceConfig(), Source: defaultSourceConfig(),
Parallelism: 1, Parallelism: 1,
} }
@ -82,6 +86,7 @@ func (cfg Catalog) ToSBOMConfig(id clio.Identification) *syft.CreateSBOMConfig {
WithParallelism(cfg.Parallelism). WithParallelism(cfg.Parallelism).
WithRelationshipsConfig(cfg.ToRelationshipsConfig()). WithRelationshipsConfig(cfg.ToRelationshipsConfig()).
WithComplianceConfig(cfg.ToComplianceConfig()). WithComplianceConfig(cfg.ToComplianceConfig()).
WithUnknownsConfig(cfg.ToUnknownsConfig()).
WithSearchConfig(cfg.ToSearchConfig()). WithSearchConfig(cfg.ToSearchConfig()).
WithPackagesConfig(cfg.ToPackagesConfig()). WithPackagesConfig(cfg.ToPackagesConfig()).
WithFilesConfig(cfg.ToFilesConfig()). WithFilesConfig(cfg.ToFilesConfig()).
@ -114,6 +119,13 @@ func (cfg Catalog) ToComplianceConfig() cataloging.ComplianceConfig {
} }
} }
func (cfg Catalog) ToUnknownsConfig() cataloging.UnknownsConfig {
return cataloging.UnknownsConfig{
IncludeExecutablesWithoutPackages: cfg.Unknowns.ExecutablesWithoutPackages,
IncludeUnexpandedArchives: cfg.Unknowns.UnexpandedArchives,
}
}
func (cfg Catalog) ToFilesConfig() filecataloging.Config { func (cfg Catalog) ToFilesConfig() filecataloging.Config {
hashers, err := intFile.Hashers(cfg.File.Metadata.Digests...) hashers, err := intFile.Hashers(cfg.File.Metadata.Digests...)
if err != nil { if err != nil {

View File

@ -0,0 +1,31 @@
package options
import (
"github.com/anchore/clio"
"github.com/anchore/syft/syft/cataloging"
)
type unknownsConfig struct {
RemoveWhenPackagesDefined bool `json:"remove-when-packages-defined" yaml:"remove-when-packages-defined" mapstructure:"remove-when-packages-defined"`
ExecutablesWithoutPackages bool `json:"executables-without-packages" yaml:"executables-without-packages" mapstructure:"executables-without-packages"`
UnexpandedArchives bool `json:"unexpanded-archives" yaml:"unexpanded-archives" mapstructure:"unexpanded-archives"`
}
var _ interface {
clio.FieldDescriber
} = (*unknownsConfig)(nil)
func (o *unknownsConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.RemoveWhenPackagesDefined, `remove unknown errors on files with discovered packages`)
descriptions.Add(&o.ExecutablesWithoutPackages, `include executables without any identified packages`)
descriptions.Add(&o.UnexpandedArchives, `include archives which were not expanded and searched`)
}
func defaultUnknowns() unknownsConfig {
def := cataloging.DefaultUnknownsConfig()
return unknownsConfig{
RemoveWhenPackagesDefined: def.RemoveWhenPackagesDefined,
ExecutablesWithoutPackages: def.IncludeExecutablesWithoutPackages,
UnexpandedArchives: def.IncludeUnexpandedArchives,
}
}

View File

@ -3,5 +3,5 @@ package internal
const ( const (
// JSONSchemaVersion is the current schema version output by the JSON encoder // JSONSchemaVersion is the current schema version output by the JSON encoder
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment. // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "16.0.17" JSONSchemaVersion = "16.0.18"
) )

View File

@ -1,7 +1,6 @@
package file package file
import ( import (
"fmt"
"os" "os"
"sort" "sort"
"strings" "strings"
@ -19,12 +18,13 @@ func NewZipFileManifest(archivePath string) (ZipFileManifest, error) {
zipReader, err := OpenZip(archivePath) zipReader, err := OpenZip(archivePath)
manifest := make(ZipFileManifest) manifest := make(ZipFileManifest)
if err != nil { if err != nil {
return manifest, fmt.Errorf("unable to open zip archive (%s): %w", archivePath, err) log.Debugf("unable to open zip archive (%s): %v", archivePath, err)
return manifest, err
} }
defer func() { defer func() {
err = zipReader.Close() err = zipReader.Close()
if err != nil { if err != nil {
log.Warnf("unable to close zip archive (%s): %+v", archivePath, err) log.Debugf("unable to close zip archive (%s): %+v", archivePath, err)
} }
}() }()

View File

@ -8,6 +8,8 @@ import (
"io" "io"
"math" "math"
"os" "os"
"github.com/anchore/syft/internal/log"
) )
// directoryEndLen, readByf, directoryEnd, and findSignatureInBlock were copied from the golang stdlib, specifically: // directoryEndLen, readByf, directoryEnd, and findSignatureInBlock were copied from the golang stdlib, specifically:
@ -46,7 +48,8 @@ func OpenZip(filepath string) (*ZipReadCloser, error) {
// need to find the start of the archive and keep track of this offset. // need to find the start of the archive and keep track of this offset.
offset, err := findArchiveStartOffset(f, fi.Size()) offset, err := findArchiveStartOffset(f, fi.Size())
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot find beginning of zip archive=%q : %w", filepath, err) log.Debugf("cannot find beginning of zip archive=%q : %v", filepath, err)
return nil, err
} }
if _, err := f.Seek(0, io.SeekStart); err != nil { if _, err := f.Seek(0, io.SeekStart); err != nil {
@ -62,7 +65,8 @@ func OpenZip(filepath string) (*ZipReadCloser, error) {
r, err := zip.NewReader(io.NewSectionReader(f, offset64, size), size) r, err := zip.NewReader(io.NewSectionReader(f, offset64, size), size)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to open ZipReadCloser @ %q: %w", filepath, err) log.Debugf("unable to open ZipReadCloser @ %q: %v", filepath, err)
return nil, err
} }
return &ZipReadCloser{ return &ZipReadCloser{

View File

@ -11,8 +11,10 @@ import (
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/sbomsync" "github.com/anchore/syft/internal/sbomsync"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/event/monitor" "github.com/anchore/syft/syft/event/monitor"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/sbom"
) )
type Executor struct { type Executor struct {
@ -35,6 +37,12 @@ func NewTaskExecutor(tasks []Task, numWorkers int) *Executor {
} }
func (p *Executor) Execute(ctx context.Context, resolver file.Resolver, s sbomsync.Builder, prog *monitor.CatalogerTaskProgress) error { func (p *Executor) Execute(ctx context.Context, resolver file.Resolver, s sbomsync.Builder, prog *monitor.CatalogerTaskProgress) error {
var lock sync.Mutex
withLock := func(fn func()) {
lock.Lock()
defer lock.Unlock()
fn()
}
var errs error var errs error
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
for i := 0; i < p.numWorkers; i++ { for i := 0; i < p.numWorkers; i++ {
@ -48,9 +56,16 @@ func (p *Executor) Execute(ctx context.Context, resolver file.Resolver, s sbomsy
return return
} }
if err := runTaskSafely(ctx, tsk, resolver, s); err != nil { err := runTaskSafely(ctx, tsk, resolver, s)
errs = multierror.Append(errs, fmt.Errorf("failed to run task: %w", err)) unknowns, err := unknown.ExtractCoordinateErrors(err)
prog.SetError(err) if len(unknowns) > 0 {
appendUnknowns(s, tsk.Name(), unknowns)
}
if err != nil {
withLock(func() {
errs = multierror.Append(errs, fmt.Errorf("failed to run task: %w", err))
prog.SetError(err)
})
} }
prog.Increment() prog.Increment()
} }
@ -62,6 +77,19 @@ func (p *Executor) Execute(ctx context.Context, resolver file.Resolver, s sbomsy
return errs return errs
} }
func appendUnknowns(builder sbomsync.Builder, taskName string, unknowns []unknown.CoordinateError) {
if accessor, ok := builder.(sbomsync.Accessor); ok {
accessor.WriteToSBOM(func(sb *sbom.SBOM) {
for _, u := range unknowns {
if sb.Artifacts.Unknowns == nil {
sb.Artifacts.Unknowns = map[file.Coordinates][]string{}
}
sb.Artifacts.Unknowns[u.Coordinates] = append(sb.Artifacts.Unknowns[u.Coordinates], formatUnknown(u.Reason.Error(), taskName))
}
})
}
}
func runTaskSafely(ctx context.Context, t Task, resolver file.Resolver, s sbomsync.Builder) (err error) { func runTaskSafely(ctx context.Context, t Task, resolver file.Resolver, s sbomsync.Builder) (err error) {
// handle individual cataloger panics // handle individual cataloger panics
defer func() { defer func() {

View File

@ -3,7 +3,6 @@ package task
import ( import (
"context" "context"
"crypto" "crypto"
"fmt"
"github.com/anchore/syft/internal/sbomsync" "github.com/anchore/syft/internal/sbomsync"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
@ -32,15 +31,12 @@ func NewFileDigestCatalogerTask(selection file.Selection, hashers ...crypto.Hash
} }
result, err := digestsCataloger.Catalog(ctx, resolver, coordinates...) result, err := digestsCataloger.Catalog(ctx, resolver, coordinates...)
if err != nil {
return fmt.Errorf("unable to catalog file digests: %w", err)
}
accessor.WriteToSBOM(func(sbom *sbom.SBOM) { accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
sbom.Artifacts.FileDigests = result sbom.Artifacts.FileDigests = result
}) })
return nil return err
} }
return NewTask("file-digest-cataloger", fn) return NewTask("file-digest-cataloger", fn)
@ -62,15 +58,12 @@ func NewFileMetadataCatalogerTask(selection file.Selection) Task {
} }
result, err := metadataCataloger.Catalog(ctx, resolver, coordinates...) result, err := metadataCataloger.Catalog(ctx, resolver, coordinates...)
if err != nil {
return err
}
accessor.WriteToSBOM(func(sbom *sbom.SBOM) { accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
sbom.Artifacts.FileMetadata = result sbom.Artifacts.FileMetadata = result
}) })
return nil return err
} }
return NewTask("file-metadata-cataloger", fn) return NewTask("file-metadata-cataloger", fn)
@ -87,15 +80,12 @@ func NewFileContentCatalogerTask(cfg filecontent.Config) Task {
accessor := builder.(sbomsync.Accessor) accessor := builder.(sbomsync.Accessor)
result, err := cat.Catalog(ctx, resolver) result, err := cat.Catalog(ctx, resolver)
if err != nil {
return err
}
accessor.WriteToSBOM(func(sbom *sbom.SBOM) { accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
sbom.Artifacts.FileContents = result sbom.Artifacts.FileContents = result
}) })
return nil return err
} }
return NewTask("file-content-cataloger", fn) return NewTask("file-content-cataloger", fn)
@ -112,15 +102,12 @@ func NewExecutableCatalogerTask(selection file.Selection, cfg executable.Config)
accessor := builder.(sbomsync.Accessor) accessor := builder.(sbomsync.Accessor)
result, err := cat.Catalog(resolver) result, err := cat.Catalog(resolver)
if err != nil {
return err
}
accessor.WriteToSBOM(func(sbom *sbom.SBOM) { accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
sbom.Artifacts.Executables = result sbom.Artifacts.Executables = result
}) })
return nil return err
} }
return NewTask("file-executable-cataloger", fn) return NewTask("file-executable-cataloger", fn)

View File

@ -103,9 +103,6 @@ func NewPackageTask(cfg CatalogingFactoryConfig, c pkg.Cataloger, tags ...string
t := bus.StartCatalogerTask(info, -1, "") t := bus.StartCatalogerTask(info, -1, "")
pkgs, relationships, err := c.Catalog(ctx, resolver) pkgs, relationships, err := c.Catalog(ctx, resolver)
if err != nil {
return fmt.Errorf("unable to catalog packages with %q: %w", catalogerName, err)
}
log.WithFields("cataloger", catalogerName).Debugf("discovered %d packages", len(pkgs)) log.WithFields("cataloger", catalogerName).Debugf("discovered %d packages", len(pkgs))
@ -120,7 +117,7 @@ func NewPackageTask(cfg CatalogingFactoryConfig, c pkg.Cataloger, tags ...string
t.SetCompleted() t.SetCompleted()
log.WithFields("name", catalogerName).Trace("package cataloger completed") log.WithFields("name", catalogerName).Trace("package cataloger completed")
return nil return err
} }
tags = append(tags, pkgcataloging.PackageTag) tags = append(tags, pkgcataloging.PackageTag)

View File

@ -0,0 +1,114 @@
package task
import (
"context"
"strings"
"github.com/mholt/archiver/v3"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/sbomsync"
"github.com/anchore/syft/syft/cataloging"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
)
const unknownsLabelerTaskName = "unknowns-labeler"
func NewUnknownsLabelerTask(cfg cataloging.UnknownsConfig) Task {
return NewTask(unknownsLabelerTaskName, unknownsLabelerTask{cfg}.processUnknowns)
}
type unknownsLabelerTask struct {
cataloging.UnknownsConfig
}
// processUnknowns removes unknown entries that have valid packages reported for the locations
func (c unknownsLabelerTask) processUnknowns(_ context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
accessor := builder.(sbomsync.Accessor)
accessor.WriteToSBOM(func(s *sbom.SBOM) {
c.finalize(resolver, s)
})
return nil
}
func (c unknownsLabelerTask) finalize(resolver file.Resolver, s *sbom.SBOM) {
hasPackageReference := coordinateReferenceLookup(resolver, s)
if c.RemoveWhenPackagesDefined {
for coords := range s.Artifacts.Unknowns {
if !hasPackageReference(coords) {
continue
}
delete(s.Artifacts.Unknowns, coords)
}
}
if s.Artifacts.Unknowns == nil {
s.Artifacts.Unknowns = map[file.Coordinates][]string{}
}
if c.IncludeExecutablesWithoutPackages {
for coords := range s.Artifacts.Executables {
if !hasPackageReference(coords) {
s.Artifacts.Unknowns[coords] = append(s.Artifacts.Unknowns[coords], formatUnknown("no package identified in executable file", unknownsLabelerTaskName))
}
}
}
if c.IncludeUnexpandedArchives {
for coords := range s.Artifacts.FileMetadata {
unarchiver, notArchiveErr := archiver.ByExtension(coords.RealPath)
if unarchiver != nil && notArchiveErr == nil && !hasPackageReference(coords) {
s.Artifacts.Unknowns[coords] = append(s.Artifacts.Unknowns[coords], "archive not cataloged")
}
}
}
}
func formatUnknown(err string, task ...string) string {
return strings.Join(task, "/") + ": " + err
}
func coordinateReferenceLookup(resolver file.Resolver, s *sbom.SBOM) func(coords file.Coordinates) bool {
allPackageCoords := file.NewCoordinateSet()
// include all directly included locations that result in packages
for p := range s.Artifacts.Packages.Enumerate() {
allPackageCoords.Add(p.Locations.CoordinateSet().ToSlice()...)
}
// include owned files, for example specified by package managers.
// relationships for these owned files may be disabled, but we always want to include them
for p := range s.Artifacts.Packages.Enumerate() {
if f, ok := p.Metadata.(pkg.FileOwner); ok {
for _, ownedFilePath := range f.OwnedFiles() {
// resolve these owned files, as they may have symlinks
// but coordinates we will test against are always absolute paths
locations, err := resolver.FilesByPath(ownedFilePath)
if err != nil {
log.Debugf("unable to resolve owned file '%s': %v", ownedFilePath, err)
}
for _, loc := range locations {
allPackageCoords.Add(loc.Coordinates)
}
}
}
}
// include relationships
for _, r := range s.Relationships {
_, fromPkgOk := r.From.(pkg.Package)
fromFile, fromFileOk := r.From.(file.Coordinates)
_, toPkgOk := r.To.(pkg.Package)
toFile, toFileOk := r.To.(file.Coordinates)
if fromPkgOk && toFileOk {
allPackageCoords.Add(toFile)
} else if fromFileOk && toPkgOk {
allPackageCoords.Add(fromFile)
}
}
return allPackageCoords.Contains
}

View File

@ -0,0 +1,201 @@
package unknown
import (
"errors"
"fmt"
"strings"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/file"
)
type hasCoordinates interface {
GetCoordinates() file.Coordinates
}
type CoordinateError struct {
Coordinates file.Coordinates
Reason error
}
var _ error = (*CoordinateError)(nil)
func (u *CoordinateError) Error() string {
if u.Coordinates.FileSystemID == "" {
return fmt.Sprintf("%s: %v", u.Coordinates.RealPath, u.Reason)
}
return fmt.Sprintf("%s (%s): %v", u.Coordinates.RealPath, u.Coordinates.FileSystemID, u.Reason)
}
// New returns a new CoordinateError unless the reason is a CoordinateError itself, in which case
// reason will be returned directly or if reason is nil, nil will be returned
func New(coords hasCoordinates, reason error) *CoordinateError {
if reason == nil {
return nil
}
coordinates := coords.GetCoordinates()
reasonCoordinateError := &CoordinateError{}
if errors.As(reason, &reasonCoordinateError) {
// if the reason is already a coordinate error, it is potentially for a different location,
// so we do not want to surface this location having an error
return reasonCoordinateError
}
return &CoordinateError{
Coordinates: coordinates,
Reason: reason,
}
}
// Newf returns a new CoordinateError with a reason of an error created from given format and args
func Newf(coords hasCoordinates, format string, args ...any) *CoordinateError {
return New(coords, fmt.Errorf(format, args...))
}
// Append returns an error joined to the first error/set of errors, with a new CoordinateError appended to the end
func Append(errs error, coords hasCoordinates, reason error) error {
return Join(errs, New(coords, reason))
}
// Appendf returns an error joined to the first error/set of errors, with a new CoordinateError appended to the end,
// created from the given reason and args
func Appendf(errs error, coords hasCoordinates, format string, args ...any) error {
return Append(errs, coords, fmt.Errorf(format, args...))
}
// Join joins the provided sets of errors together in a flattened manner, taking into account nested errors created
// from other sources, including errors.Join, multierror.Append, and unknown.Join
func Join(errs ...error) error {
var out []error
for _, err := range errs {
// append errors, de-duplicated
for _, e := range flatten(err) {
if containsErr(out, e) {
continue
}
out = append(out, e)
}
}
if len(out) == 1 {
return out[0]
}
if len(out) == 0 {
return nil
}
return errors.Join(out...)
}
// Joinf joins the provided sets of errors together in a flattened manner, taking into account nested errors created
// from other sources, including errors.Join, multierror.Append, and unknown.Join and appending a new error,
// created from the format and args provided -- the error is NOT a CoordinateError
func Joinf(errs error, format string, args ...any) error {
return Join(errs, fmt.Errorf(format, args...))
}
// IfEmptyf returns a new Errorf-formatted error, only when the provided slice is empty or nil when
// the slice has entries
func IfEmptyf[T any](emptyTest []T, format string, args ...any) error {
if len(emptyTest) == 0 {
return fmt.Errorf(format, args...)
}
return nil
}
// ExtractCoordinateErrors extracts all coordinate errors returned, and any _additional_ errors in the graph
// are encapsulated in the second, error return parameter
func ExtractCoordinateErrors(err error) (coordinateErrors []CoordinateError, remainingErrors error) {
remainingErrors = visitErrors(err, func(e error) error {
if coordinateError, _ := e.(*CoordinateError); coordinateError != nil {
coordinateErrors = append(coordinateErrors, *coordinateError)
return nil
}
return e
})
return coordinateErrors, remainingErrors
}
func flatten(errs ...error) []error {
var out []error
for _, err := range errs {
if err == nil {
continue
}
// turn all errors nested under a coordinate error to individual coordinate errors
if e, ok := err.(*CoordinateError); ok {
if e == nil {
continue
}
for _, r := range flatten(e.Reason) {
out = append(out, New(e.Coordinates, r))
}
} else
// from multierror.Append
if e, ok := err.(interface{ WrappedErrors() []error }); ok {
if e == nil {
continue
}
out = append(out, flatten(e.WrappedErrors()...)...)
} else
// from errors.Join
if e, ok := err.(interface{ Unwrap() []error }); ok {
if e == nil {
continue
}
out = append(out, flatten(e.Unwrap()...)...)
} else {
out = append(out, err)
}
}
return out
}
// containsErr returns true if a duplicate error is found
func containsErr(out []error, err error) bool {
defer func() {
if err := recover(); err != nil {
log.Tracef("error comparing errors: %v", err)
}
}()
for _, e := range out {
if e == err {
return true
}
}
return false
}
// visitErrors visits every wrapped error. the returned error replaces the provided error, null errors are omitted from
// the object graph
func visitErrors(err error, fn func(error) error) error {
// unwrap errors from errors.Join
if errs, ok := err.(interface{ Unwrap() []error }); ok {
var out []error
for _, e := range errs.Unwrap() {
out = append(out, visitErrors(e, fn))
}
// errors.Join omits nil errors and will return nil if all passed errors are nil
return errors.Join(out...)
}
// unwrap errors from multierror.Append -- these also implement Unwrap() error, so check this first
if errs, ok := err.(interface{ WrappedErrors() []error }); ok {
var out []error
for _, e := range errs.WrappedErrors() {
out = append(out, visitErrors(e, fn))
}
// errors.Join omits nil errors and will return nil if all passed errors are nil
return errors.Join(out...)
}
// unwrap singly wrapped errors
if e, ok := err.(interface{ Unwrap() error }); ok {
wrapped := e.Unwrap()
got := visitErrors(wrapped, fn)
if got == nil {
return nil
}
if wrapped.Error() != got.Error() {
prefix := strings.TrimSuffix(err.Error(), wrapped.Error())
return fmt.Errorf("%s%w", prefix, got)
}
return err
}
return fn(err)
}

View File

@ -0,0 +1,236 @@
package unknown
import (
"errors"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file"
)
func Test_visitErrors(t *testing.T) {
tests := []struct {
name string
in error
transform func(error) error
expected string
}{
{
name: "return",
in: fmt.Errorf("err1"),
transform: func(e error) error {
return e
},
expected: "err1",
},
{
name: "omit",
in: fmt.Errorf("err1"),
transform: func(_ error) error {
return nil
},
expected: "<nil>",
},
{
name: "wrapped return",
in: fmt.Errorf("wrapped: %w", fmt.Errorf("err1")),
transform: func(e error) error {
return e
},
expected: "wrapped: err1",
},
{
name: "wrapped omit",
in: fmt.Errorf("wrapped: %w", fmt.Errorf("err1")),
transform: func(e error) error {
if e.Error() == "err1" {
return nil
}
return e
},
expected: "<nil>",
},
{
name: "joined return",
in: errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2")),
transform: func(e error) error {
return e
},
expected: "err1\nerr2",
},
{
name: "joined omit",
in: errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2")),
transform: func(_ error) error {
return nil
},
expected: "<nil>",
},
{
name: "joined omit first",
in: errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2")),
transform: func(e error) error {
if e.Error() == "err1" {
return nil
}
return e
},
expected: "err2",
},
{
name: "joined wrapped return",
in: errors.Join(fmt.Errorf("wrapped: %w", fmt.Errorf("err1")), fmt.Errorf("err2")),
transform: func(e error) error {
return e
},
expected: "wrapped: err1\nerr2",
},
{
name: "joined wrapped omit first",
in: errors.Join(fmt.Errorf("wrapped: %w", fmt.Errorf("err1")), fmt.Errorf("err2")),
transform: func(e error) error {
if e.Error() == "err1" {
return nil
}
return e
},
expected: "err2",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
gotErr := visitErrors(test.in, test.transform)
got := fmt.Sprintf("%v", gotErr)
require.Equal(t, test.expected, got)
})
}
}
func Test_Join(t *testing.T) {
err1 := fmt.Errorf("err1")
err2 := fmt.Errorf("err2")
tests := []struct {
name string ``
in []error
expected string
}{
{
name: "basic",
in: []error{fmt.Errorf("err")},
expected: "err",
},
{
name: "wrapped",
in: []error{fmt.Errorf("outer: %w", fmt.Errorf("err"))},
expected: "outer: err",
},
{
name: "wrapped joined",
in: []error{errors.Join(fmt.Errorf("outer: %w", fmt.Errorf("err1")), fmt.Errorf("err2"))},
expected: "outer: err1\nerr2",
},
{
name: "duplicates",
in: []error{err1, err1, err2},
expected: "err1\nerr2",
},
{
name: "nested duplicates",
in: []error{errors.Join(err1, err2), err1, err2},
expected: "err1\nerr2",
},
{
name: "nested duplicates coords",
in: []error{New(file.NewLocation("l1"), errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2"))), fmt.Errorf("err1"), fmt.Errorf("err2")},
expected: "l1: err1\nl1: err2\nerr1\nerr2",
},
{
name: "all nil",
in: []error{nil, nil, nil},
expected: "",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got := Join(test.in...)
if test.expected == "" {
require.Nil(t, got)
return
}
require.NotNil(t, got)
require.Equal(t, test.expected, got.Error())
})
}
}
func Test_flatten(t *testing.T) {
coords := file.Coordinates{
RealPath: "real/path",
}
e1 := fmt.Errorf("e1")
e2 := fmt.Errorf("e2")
c1 := New(coords, fmt.Errorf("c1"))
c2 := New(coords, fmt.Errorf("c2"))
tests := []struct {
name string ``
in error
expected string
}{
{
name: "basic",
in: errors.Join(e1, e2),
expected: "e1//e2",
},
{
name: "coords",
in: New(coords, e1),
expected: "real/path: e1",
},
{
name: "coords with joined children",
in: New(coords, errors.Join(e1, e2)),
expected: "real/path: e1//real/path: e2",
},
{
name: "very nested",
in: errors.Join(errors.Join(errors.Join(errors.Join(e1, c1), e2), c2), e2),
expected: "e1//real/path: c1//e2//real/path: c2//e2",
},
}
toString := func(errs ...error) string {
var parts []string
for _, e := range errs {
parts = append(parts, e.Error())
}
return strings.Join(parts, "//")
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got := flatten(test.in)
require.NotNil(t, got)
require.Equal(t, test.expected, toString(got...))
})
}
}
func Test_Append(t *testing.T) {
e1 := New(file.NewLocation("l1"), fmt.Errorf("e1"))
e2 := Append(e1, file.NewLocation("l2"), fmt.Errorf("e2"))
e3 := Appendf(e2, file.NewLocation("l3"), "%s", "e3")
require.Equal(t, "l1: e1\nl2: e2\nl3: e3", e3.Error())
e1 = New(file.NewLocation("l1"), nil)
require.Nil(t, e1)
e2 = Append(e1, file.NewLocation("l2"), fmt.Errorf("e2"))
e3 = Appendf(e2, file.NewLocation("l3"), "%s", "e3")
require.Equal(t, "l2: e2\nl3: e3", e3.Error())
e1 = New(file.NewLocation("l1"), fmt.Errorf("e1"))
e2 = Append(e1, file.NewLocation("l2"), nil)
e3 = Appendf(e2, file.NewLocation("l3"), "%s", "e3")
require.Equal(t, "l1: e1\nl3: e3", e3.Error())
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "anchore.io/schema/syft/json/16.0.17/document", "$id": "anchore.io/schema/syft/json/16.0.18/document",
"$ref": "#/$defs/Document", "$ref": "#/$defs/Document",
"$defs": { "$defs": {
"AlpmDbEntry": { "AlpmDbEntry": {
@ -777,6 +777,12 @@
}, },
"executable": { "executable": {
"$ref": "#/$defs/Executable" "$ref": "#/$defs/Executable"
},
"unknowns": {
"items": {
"type": "string"
},
"type": "array"
} }
}, },
"type": "object", "type": "object",

View File

@ -0,0 +1,15 @@
package cataloging
type UnknownsConfig struct {
RemoveWhenPackagesDefined bool
IncludeExecutablesWithoutPackages bool
IncludeUnexpandedArchives bool
}
func DefaultUnknownsConfig() UnknownsConfig {
return UnknownsConfig{
RemoveWhenPackagesDefined: true,
IncludeExecutablesWithoutPackages: true,
IncludeUnexpandedArchives: true,
}
}

View File

@ -22,6 +22,7 @@ type CreateSBOMConfig struct {
Compliance cataloging.ComplianceConfig Compliance cataloging.ComplianceConfig
Search cataloging.SearchConfig Search cataloging.SearchConfig
Relationships cataloging.RelationshipsConfig Relationships cataloging.RelationshipsConfig
Unknowns cataloging.UnknownsConfig
DataGeneration cataloging.DataGenerationConfig DataGeneration cataloging.DataGenerationConfig
Packages pkgcataloging.Config Packages pkgcataloging.Config
Files filecataloging.Config Files filecataloging.Config
@ -113,6 +114,12 @@ func (c *CreateSBOMConfig) WithRelationshipsConfig(cfg cataloging.RelationshipsC
return c return c
} }
// WithUnknownsConfig allows for defining the specific behavior dealing with unknowns
func (c *CreateSBOMConfig) WithUnknownsConfig(cfg cataloging.UnknownsConfig) *CreateSBOMConfig {
c.Unknowns = cfg
return c
}
// WithDataGenerationConfig allows for defining what data elements that cannot be discovered from the underlying // WithDataGenerationConfig allows for defining what data elements that cannot be discovered from the underlying
// target being scanned that should be generated after package creation. // target being scanned that should be generated after package creation.
func (c *CreateSBOMConfig) WithDataGenerationConfig(cfg cataloging.DataGenerationConfig) *CreateSBOMConfig { func (c *CreateSBOMConfig) WithDataGenerationConfig(cfg cataloging.DataGenerationConfig) *CreateSBOMConfig {
@ -173,6 +180,7 @@ func (c *CreateSBOMConfig) makeTaskGroups(src source.Description) ([][]task.Task
// generate package and file tasks based on the configuration // generate package and file tasks based on the configuration
environmentTasks := c.environmentTasks() environmentTasks := c.environmentTasks()
relationshipsTasks := c.relationshipTasks(src) relationshipsTasks := c.relationshipTasks(src)
unknownTasks := c.unknownsTasks()
fileTasks := c.fileTasks() fileTasks := c.fileTasks()
pkgTasks, selectionEvidence, err := c.packageTasks(src) pkgTasks, selectionEvidence, err := c.packageTasks(src)
if err != nil { if err != nil {
@ -192,6 +200,11 @@ func (c *CreateSBOMConfig) makeTaskGroups(src source.Description) ([][]task.Task
taskGroups = append(taskGroups, relationshipsTasks) taskGroups = append(taskGroups, relationshipsTasks)
} }
// all unknowns tasks should happen after all scanning is complete
if len(unknownTasks) > 0 {
taskGroups = append(taskGroups, unknownTasks)
}
// identifying the environment (i.e. the linux release) must be done first as this is required for package cataloging // identifying the environment (i.e. the linux release) must be done first as this is required for package cataloging
taskGroups = append( taskGroups = append(
[][]task.Task{ [][]task.Task{
@ -338,6 +351,18 @@ func (c *CreateSBOMConfig) environmentTasks() []task.Task {
return tsks return tsks
} }
// unknownsTasks returns a set of tasks that perform any necessary post-processing
// to identify SBOM elements as unknowns
func (c *CreateSBOMConfig) unknownsTasks() []task.Task {
var tasks []task.Task
if t := task.NewUnknownsLabelerTask(c.Unknowns); t != nil {
tasks = append(tasks, t)
}
return tasks
}
func (c *CreateSBOMConfig) validate() error { func (c *CreateSBOMConfig) validate() error {
if c.Relationships.ExcludeBinaryPackagesWithFileOwnershipOverlap { if c.Relationships.ExcludeBinaryPackagesWithFileOwnershipOverlap {
if !c.Relationships.PackageFileOwnershipOverlap { if !c.Relationships.PackageFileOwnershipOverlap {

View File

@ -90,6 +90,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
pkgCatalogerNamesWithTagOrName(t, "image"), pkgCatalogerNamesWithTagOrName(t, "image"),
fileCatalogerNames(true, true, true), fileCatalogerNames(true, true, true),
relationshipCatalogerNames(), relationshipCatalogerNames(),
unknownsTaskNames(),
}, },
wantManifest: &catalogerManifest{ wantManifest: &catalogerManifest{
Requested: pkgcataloging.SelectionRequest{ Requested: pkgcataloging.SelectionRequest{
@ -108,6 +109,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
pkgCatalogerNamesWithTagOrName(t, "directory"), pkgCatalogerNamesWithTagOrName(t, "directory"),
fileCatalogerNames(true, true, true), fileCatalogerNames(true, true, true),
relationshipCatalogerNames(), relationshipCatalogerNames(),
unknownsTaskNames(),
}, },
wantManifest: &catalogerManifest{ wantManifest: &catalogerManifest{
Requested: pkgcataloging.SelectionRequest{ Requested: pkgcataloging.SelectionRequest{
@ -127,6 +129,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
pkgCatalogerNamesWithTagOrName(t, "directory"), pkgCatalogerNamesWithTagOrName(t, "directory"),
fileCatalogerNames(true, true, true), fileCatalogerNames(true, true, true),
relationshipCatalogerNames(), relationshipCatalogerNames(),
unknownsTaskNames(),
}, },
wantManifest: &catalogerManifest{ wantManifest: &catalogerManifest{
Requested: pkgcataloging.SelectionRequest{ Requested: pkgcataloging.SelectionRequest{
@ -145,6 +148,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
pkgCatalogerNamesWithTagOrName(t, "image"), pkgCatalogerNamesWithTagOrName(t, "image"),
fileCatalogerNames(false, true, true), // note: the digest cataloger is not included fileCatalogerNames(false, true, true), // note: the digest cataloger is not included
relationshipCatalogerNames(), relationshipCatalogerNames(),
unknownsTaskNames(),
}, },
wantManifest: &catalogerManifest{ wantManifest: &catalogerManifest{
Requested: pkgcataloging.SelectionRequest{ Requested: pkgcataloging.SelectionRequest{
@ -163,6 +167,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
pkgCatalogerNamesWithTagOrName(t, "image"), pkgCatalogerNamesWithTagOrName(t, "image"),
// note: there are no file catalogers in their own group // note: there are no file catalogers in their own group
relationshipCatalogerNames(), relationshipCatalogerNames(),
unknownsTaskNames(),
}, },
wantManifest: &catalogerManifest{ wantManifest: &catalogerManifest{
Requested: pkgcataloging.SelectionRequest{ Requested: pkgcataloging.SelectionRequest{
@ -184,6 +189,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
fileCatalogerNames(true, true, true)..., fileCatalogerNames(true, true, true)...,
), ),
relationshipCatalogerNames(), relationshipCatalogerNames(),
unknownsTaskNames(),
}, },
wantManifest: &catalogerManifest{ wantManifest: &catalogerManifest{
Requested: pkgcataloging.SelectionRequest{ Requested: pkgcataloging.SelectionRequest{
@ -204,6 +210,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "persistent"), addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "persistent"),
fileCatalogerNames(true, true, true), fileCatalogerNames(true, true, true),
relationshipCatalogerNames(), relationshipCatalogerNames(),
unknownsTaskNames(),
}, },
wantManifest: &catalogerManifest{ wantManifest: &catalogerManifest{
Requested: pkgcataloging.SelectionRequest{ Requested: pkgcataloging.SelectionRequest{
@ -224,6 +231,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
addTo(pkgCatalogerNamesWithTagOrName(t, "directory"), "persistent"), addTo(pkgCatalogerNamesWithTagOrName(t, "directory"), "persistent"),
fileCatalogerNames(true, true, true), fileCatalogerNames(true, true, true),
relationshipCatalogerNames(), relationshipCatalogerNames(),
unknownsTaskNames(),
}, },
wantManifest: &catalogerManifest{ wantManifest: &catalogerManifest{
Requested: pkgcataloging.SelectionRequest{ Requested: pkgcataloging.SelectionRequest{
@ -244,6 +252,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
addTo(pkgIntersect("image", "javascript"), "persistent"), addTo(pkgIntersect("image", "javascript"), "persistent"),
fileCatalogerNames(true, true, true), fileCatalogerNames(true, true, true),
relationshipCatalogerNames(), relationshipCatalogerNames(),
unknownsTaskNames(),
}, },
wantManifest: &catalogerManifest{ wantManifest: &catalogerManifest{
Requested: pkgcataloging.SelectionRequest{ Requested: pkgcataloging.SelectionRequest{
@ -265,6 +274,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "user-provided"), addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "user-provided"),
fileCatalogerNames(true, true, true), fileCatalogerNames(true, true, true),
relationshipCatalogerNames(), relationshipCatalogerNames(),
unknownsTaskNames(),
}, },
wantManifest: &catalogerManifest{ wantManifest: &catalogerManifest{
Requested: pkgcataloging.SelectionRequest{ Requested: pkgcataloging.SelectionRequest{
@ -285,6 +295,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
pkgCatalogerNamesWithTagOrName(t, "image"), pkgCatalogerNamesWithTagOrName(t, "image"),
fileCatalogerNames(true, true, true), fileCatalogerNames(true, true, true),
relationshipCatalogerNames(), relationshipCatalogerNames(),
unknownsTaskNames(),
}, },
wantManifest: &catalogerManifest{ wantManifest: &catalogerManifest{
Requested: pkgcataloging.SelectionRequest{ Requested: pkgcataloging.SelectionRequest{
@ -385,6 +396,10 @@ func relationshipCatalogerNames() []string {
return []string{"relationships-cataloger"} return []string{"relationships-cataloger"}
} }
func unknownsTaskNames() []string {
return []string{"unknowns-labeler"}
}
func environmentCatalogerNames() []string { func environmentCatalogerNames() []string {
return []string{"environment-cataloger"} return []string{"environment-cataloger"}
} }

View File

@ -15,6 +15,7 @@ import (
"github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/mimetype" "github.com/anchore/syft/internal/mimetype"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/event/monitor" "github.com/anchore/syft/syft/event/monitor"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/unionreader" "github.com/anchore/syft/syft/internal/unionreader"
@ -45,6 +46,8 @@ func NewCataloger(cfg Config) *Cataloger {
} }
func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.Executable, error) { func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.Executable, error) {
var errs error
locs, err := resolver.FilesByMIMEType(i.config.MIMETypes...) locs, err := resolver.FilesByMIMEType(i.config.MIMETypes...)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to get file locations for binaries: %w", err) return nil, fmt.Errorf("unable to get file locations for binaries: %w", err)
@ -61,7 +64,10 @@ func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.E
for _, loc := range locs { for _, loc := range locs {
prog.AtomicStage.Set(loc.Path()) prog.AtomicStage.Set(loc.Path())
exec := processExecutableLocation(loc, resolver) exec, err := processExecutableLocation(loc, resolver)
if err != nil {
errs = unknown.Append(errs, loc, err)
}
if exec != nil { if exec != nil {
prog.Increment() prog.Increment()
@ -74,30 +80,24 @@ func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.E
prog.AtomicStage.Set(fmt.Sprintf("%s executables", humanize.Comma(prog.Current()))) prog.AtomicStage.Set(fmt.Sprintf("%s executables", humanize.Comma(prog.Current())))
prog.SetCompleted() prog.SetCompleted()
return results, nil return results, errs
} }
func processExecutableLocation(loc file.Location, resolver file.Resolver) *file.Executable { func processExecutableLocation(loc file.Location, resolver file.Resolver) (*file.Executable, error) {
reader, err := resolver.FileContentsByLocation(loc) reader, err := resolver.FileContentsByLocation(loc)
if err != nil { if err != nil {
// TODO: known-unknowns
log.WithFields("error", err).Warnf("unable to get file contents for %q", loc.RealPath) log.WithFields("error", err).Warnf("unable to get file contents for %q", loc.RealPath)
return nil return nil, fmt.Errorf("unable to get file contents: %w", err)
} }
defer internal.CloseAndLogError(reader, loc.RealPath) defer internal.CloseAndLogError(reader, loc.RealPath)
uReader, err := unionreader.GetUnionReader(reader) uReader, err := unionreader.GetUnionReader(reader)
if err != nil { if err != nil {
// TODO: known-unknowns
log.WithFields("error", err).Warnf("unable to get union reader for %q", loc.RealPath) log.WithFields("error", err).Warnf("unable to get union reader for %q", loc.RealPath)
return nil return nil, fmt.Errorf("unable to get union reader: %w", err)
} }
exec, err := processExecutable(loc, uReader) return processExecutable(loc, uReader)
if err != nil {
log.WithFields("error", err).Warnf("unable to process executable %q", loc.RealPath)
}
return exec
} }
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress { func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {
@ -153,10 +153,12 @@ func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file
format, err := findExecutableFormat(reader) format, err := findExecutableFormat(reader)
if err != nil { if err != nil {
log.Debugf("unable to determine executable kind for %v: %v", loc.RealPath, err)
return nil, fmt.Errorf("unable to determine executable kind: %w", err) return nil, fmt.Errorf("unable to determine executable kind: %w", err)
} }
if format == "" { if format == "" {
// this is not an "unknown", so just log -- this binary does not have parseable data in it
log.Debugf("unable to determine executable format for %q", loc.RealPath) log.Debugf("unable to determine executable format for %q", loc.RealPath)
return nil, nil return nil, nil
} }
@ -165,16 +167,19 @@ func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file
switch format { switch format {
case file.ELF: case file.ELF:
if err := findELFFeatures(&data, reader); err != nil { if err = findELFFeatures(&data, reader); err != nil {
log.WithFields("error", err).Tracef("unable to determine ELF features for %q", loc.RealPath) log.WithFields("error", err).Tracef("unable to determine ELF features for %q", loc.RealPath)
err = fmt.Errorf("unable to determine ELF features: %w", err)
} }
case file.PE: case file.PE:
if err := findPEFeatures(&data, reader); err != nil { if err = findPEFeatures(&data, reader); err != nil {
log.WithFields("error", err).Tracef("unable to determine PE features for %q", loc.RealPath) log.WithFields("error", err).Tracef("unable to determine PE features for %q", loc.RealPath)
err = fmt.Errorf("unable to determine PE features: %w", err)
} }
case file.MachO: case file.MachO:
if err := findMachoFeatures(&data, reader); err != nil { if err = findMachoFeatures(&data, reader); err != nil {
log.WithFields("error", err).Tracef("unable to determine Macho features for %q", loc.RealPath) log.WithFields("error", err).Tracef("unable to determine Macho features for %q", loc.RealPath)
err = fmt.Errorf("unable to determine Macho features: %w", err)
} }
} }
@ -183,7 +188,7 @@ func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file
data.ImportedLibraries = []string{} data.ImportedLibraries = []string{}
} }
return &data, nil return &data, err
} }
func findExecutableFormat(reader unionreader.UnionReader) (file.ExecutableFormat, error) { func findExecutableFormat(reader unionreader.UnionReader) (file.ExecutableFormat, error) {

View File

@ -8,6 +8,7 @@ import (
"github.com/scylladb/go-set/strset" "github.com/scylladb/go-set/strset"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/unionreader" "github.com/anchore/syft/syft/internal/unionreader"
) )
@ -20,8 +21,8 @@ func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) erro
libs, err := f.ImportedLibraries() libs, err := f.ImportedLibraries()
if err != nil { if err != nil {
// TODO: known-unknowns
log.WithFields("error", err).Trace("unable to read imported libraries from elf file") log.WithFields("error", err).Trace("unable to read imported libraries from elf file")
err = unknown.Joinf(err, "unable to read imported libraries from elf file: %w", err)
libs = nil libs = nil
} }
@ -34,7 +35,7 @@ func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) erro
data.HasEntrypoint = elfHasEntrypoint(f) data.HasEntrypoint = elfHasEntrypoint(f)
data.HasExports = elfHasExports(f) data.HasExports = elfHasExports(f)
return nil return err
} }
func findELFSecurityFeatures(f *elf.File) *file.ELFSecurityFeatures { func findELFSecurityFeatures(f *elf.File) *file.ELFSecurityFeatures {
@ -62,7 +63,6 @@ func checkElfStackCanary(file *elf.File) *bool {
func hasAnyDynamicSymbols(file *elf.File, symbolNames ...string) *bool { func hasAnyDynamicSymbols(file *elf.File, symbolNames ...string) *bool {
dynSyms, err := file.DynamicSymbols() dynSyms, err := file.DynamicSymbols()
if err != nil { if err != nil {
// TODO: known-unknowns
log.WithFields("error", err).Trace("unable to read dynamic symbols from elf file") log.WithFields("error", err).Trace("unable to read dynamic symbols from elf file")
return nil return nil
} }
@ -129,7 +129,6 @@ func hasBindNowDynTagOrFlag(f *elf.File) bool {
func hasElfDynFlag(f *elf.File, flag elf.DynFlag) bool { func hasElfDynFlag(f *elf.File, flag elf.DynFlag) bool {
vals, err := f.DynValue(elf.DT_FLAGS) vals, err := f.DynValue(elf.DT_FLAGS)
if err != nil { if err != nil {
// TODO: known-unknowns
log.WithFields("error", err).Trace("unable to read DT_FLAGS from elf file") log.WithFields("error", err).Trace("unable to read DT_FLAGS from elf file")
return false return false
} }
@ -144,7 +143,6 @@ func hasElfDynFlag(f *elf.File, flag elf.DynFlag) bool {
func hasElfDynFlag1(f *elf.File, flag elf.DynFlag1) bool { func hasElfDynFlag1(f *elf.File, flag elf.DynFlag1) bool {
vals, err := f.DynValue(elf.DT_FLAGS_1) vals, err := f.DynValue(elf.DT_FLAGS_1)
if err != nil { if err != nil {
// TODO: known-unknowns
log.WithFields("error", err).Trace("unable to read DT_FLAGS_1 from elf file") log.WithFields("error", err).Trace("unable to read DT_FLAGS_1 from elf file")
return false return false
} }
@ -203,7 +201,6 @@ func checkLLVMControlFlowIntegrity(file *elf.File) *bool {
// look for any symbols that are functions and end with ".cfi" // look for any symbols that are functions and end with ".cfi"
dynSyms, err := file.Symbols() dynSyms, err := file.Symbols()
if err != nil { if err != nil {
// TODO: known-unknowns
log.WithFields("error", err).Trace("unable to read symbols from elf file") log.WithFields("error", err).Trace("unable to read symbols from elf file")
return nil return nil
} }
@ -225,7 +222,6 @@ var fortifyPattern = regexp.MustCompile(`__\w+_chk@.+`)
func checkClangFortifySource(file *elf.File) *bool { func checkClangFortifySource(file *elf.File) *bool {
dynSyms, err := file.Symbols() dynSyms, err := file.Symbols()
if err != nil { if err != nil {
// TODO: known-unknowns
log.WithFields("error", err).Trace("unable to read symbols from elf file") log.WithFields("error", err).Trace("unable to read symbols from elf file")
return nil return nil
} }
@ -254,7 +250,7 @@ func elfHasExports(f *elf.File) bool {
// really anything that is not marked with 'U' (undefined) is considered an export. // really anything that is not marked with 'U' (undefined) is considered an export.
symbols, err := f.DynamicSymbols() symbols, err := f.DynamicSymbols()
if err != nil { if err != nil {
// TODO: known-unknowns? log.WithFields("error", err).Trace("unable to get ELF dynamic symbols")
return false return false
} }

View File

@ -27,6 +27,7 @@ func Test_findELFSecurityFeatures(t *testing.T) {
name string name string
fixture string fixture string
want *file.ELFSecurityFeatures want *file.ELFSecurityFeatures
wantErr require.ErrorAssertionFunc
wantStripped bool wantStripped bool
}{ }{
{ {
@ -221,6 +222,7 @@ func Test_elfHasExports(t *testing.T) {
f, err := elf.NewFile(readerForFixture(t, tt.fixture)) f, err := elf.NewFile(readerForFixture(t, tt.fixture))
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, tt.want, elfHasExports(f)) assert.Equal(t, tt.want, elfHasExports(f))
require.NoError(t, err)
}) })
} }
} }

View File

@ -13,6 +13,7 @@ import (
"github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/bus"
intFile "github.com/anchore/syft/internal/file" intFile "github.com/anchore/syft/internal/file"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/event/monitor" "github.com/anchore/syft/syft/event/monitor"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
) )
@ -46,6 +47,7 @@ func NewCataloger(cfg Config) *Cataloger {
func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file.Coordinates]string, error) { func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file.Coordinates]string, error) {
results := make(map[file.Coordinates]string) results := make(map[file.Coordinates]string)
var locations []file.Location var locations []file.Location
var errs error
locations, err := resolver.FilesByGlob(i.globs...) locations, err := resolver.FilesByGlob(i.globs...)
if err != nil { if err != nil {
@ -59,8 +61,9 @@ func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file
metadata, err := resolver.FileMetadataByLocation(location) metadata, err := resolver.FileMetadataByLocation(location)
if err != nil { if err != nil {
errs = unknown.Append(errs, location, err)
prog.SetError(err) prog.SetError(err)
return nil, err continue
} }
if i.skipFilesAboveSizeInBytes > 0 && metadata.Size() > i.skipFilesAboveSizeInBytes { if i.skipFilesAboveSizeInBytes > 0 && metadata.Size() > i.skipFilesAboveSizeInBytes {
@ -69,12 +72,12 @@ func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file
result, err := i.catalogLocation(resolver, location) result, err := i.catalogLocation(resolver, location)
if internal.IsErrPathPermission(err) { if internal.IsErrPathPermission(err) {
log.Debugf("file contents cataloger skipping - %+v", err) errs = unknown.Append(errs, location, fmt.Errorf("permission error reading file contents: %w", err))
continue continue
} }
if err != nil { if err != nil {
prog.SetError(err) errs = unknown.Append(errs, location, err)
return nil, err continue
} }
prog.Increment() prog.Increment()
@ -87,7 +90,7 @@ func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file
prog.AtomicStage.Set(fmt.Sprintf("%s files", humanize.Comma(prog.Current()))) prog.AtomicStage.Set(fmt.Sprintf("%s files", humanize.Comma(prog.Current())))
prog.SetCompleted() prog.SetCompleted()
return results, nil return results, errs
} }
func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) (string, error) { func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) (string, error) {

View File

@ -13,6 +13,7 @@ import (
"github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/bus"
intFile "github.com/anchore/syft/internal/file" intFile "github.com/anchore/syft/internal/file"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/event/monitor" "github.com/anchore/syft/syft/event/monitor"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
intCataloger "github.com/anchore/syft/syft/file/cataloger/internal" intCataloger "github.com/anchore/syft/syft/file/cataloger/internal"
@ -33,6 +34,7 @@ func NewCataloger(hashes []crypto.Hash) *Cataloger {
func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates][]file.Digest, error) { func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates][]file.Digest, error) {
results := make(map[file.Coordinates][]file.Digest) results := make(map[file.Coordinates][]file.Digest)
var locations []file.Location var locations []file.Location
var errs error
if len(coordinates) == 0 { if len(coordinates) == 0 {
locations = intCataloger.AllRegularFiles(ctx, resolver) locations = intCataloger.AllRegularFiles(ctx, resolver)
@ -58,12 +60,14 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
if internal.IsErrPathPermission(err) { if internal.IsErrPathPermission(err) {
log.Debugf("file digests cataloger skipping %q: %+v", location.RealPath, err) log.Debugf("file digests cataloger skipping %q: %+v", location.RealPath, err)
errs = unknown.Append(errs, location, err)
continue continue
} }
if err != nil { if err != nil {
prog.SetError(err) prog.SetError(err)
return nil, fmt.Errorf("failed to process file %q: %w", location.RealPath, err) errs = unknown.Append(errs, location, err)
continue
} }
prog.Increment() prog.Increment()
@ -76,7 +80,7 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
prog.AtomicStage.Set(fmt.Sprintf("%s files", humanize.Comma(prog.Current()))) prog.AtomicStage.Set(fmt.Sprintf("%s files", humanize.Comma(prog.Current())))
prog.SetCompleted() prog.SetCompleted()
return results, nil return results, errs
} }
func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) ([]file.Digest, error) { func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) ([]file.Digest, error) {

View File

@ -8,6 +8,7 @@ import (
"github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/event/monitor" "github.com/anchore/syft/syft/event/monitor"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
) )
@ -20,6 +21,7 @@ func NewCataloger() *Cataloger {
} }
func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates]file.Metadata, error) { func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates]file.Metadata, error) {
var errs error
results := make(map[file.Coordinates]file.Metadata) results := make(map[file.Coordinates]file.Metadata)
var locations <-chan file.Location var locations <-chan file.Location
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
@ -34,7 +36,7 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
for _, c := range coordinates { for _, c := range coordinates {
locs, err := resolver.FilesByPath(c.RealPath) locs, err := resolver.FilesByPath(c.RealPath)
if err != nil { if err != nil {
log.Warn("unable to get file locations for path %q: %w", c.RealPath, err) errs = unknown.Append(errs, c, err)
continue continue
} }
for _, loc := range locs { for _, loc := range locs {
@ -71,7 +73,7 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
prog.AtomicStage.Set(fmt.Sprintf("%s locations", humanize.Comma(prog.Current()))) prog.AtomicStage.Set(fmt.Sprintf("%s locations", humanize.Comma(prog.Current())))
prog.SetCompleted() prog.SetCompleted()
return results, nil return results, errs
} }
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress { func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {

View File

@ -39,3 +39,7 @@ func (c Coordinates) String() string {
} }
return fmt.Sprintf("Location<%s>", str) return fmt.Sprintf("Location<%s>", str)
} }
func (c Coordinates) GetCoordinates() Coordinates {
return c
}

View File

@ -221,6 +221,7 @@ func Test_encodeDecodeFileMetadata(t *testing.T) {
}, },
}, },
}, },
Unknowns: map[file.Coordinates][]string{},
Executables: map[file.Coordinates]file.Executable{ Executables: map[file.Coordinates]file.Executable{
c: { c: {
Format: file.ELF, Format: file.ELF,

View File

@ -13,6 +13,7 @@ type File struct {
Digests []file.Digest `json:"digests,omitempty"` Digests []file.Digest `json:"digests,omitempty"`
Licenses []FileLicense `json:"licenses,omitempty"` Licenses []FileLicense `json:"licenses,omitempty"`
Executable *file.Executable `json:"executable,omitempty"` Executable *file.Executable `json:"executable,omitempty"`
Unknowns []string `json:"unknowns,omitempty"`
} }
type FileMetadataEntry struct { type FileMetadataEntry struct {

View File

@ -101,6 +101,11 @@ func toFile(s sbom.SBOM) []model.File {
contents = contentsForLocation contents = contentsForLocation
} }
var unknowns []string
if unknownsForLocation, exists := artifacts.Unknowns[coordinates]; exists {
unknowns = unknownsForLocation
}
var licenses []model.FileLicense var licenses []model.FileLicense
for _, l := range artifacts.FileLicenses[coordinates] { for _, l := range artifacts.FileLicenses[coordinates] {
var evidence *model.FileLicenseEvidence var evidence *model.FileLicenseEvidence
@ -132,6 +137,7 @@ func toFile(s sbom.SBOM) []model.File {
Contents: contents, Contents: contents,
Licenses: licenses, Licenses: licenses,
Executable: executable, Executable: executable,
Unknowns: unknowns,
}) })
} }

View File

@ -38,6 +38,7 @@ func toSyftModel(doc model.Document) *sbom.SBOM {
FileContents: fileArtifacts.FileContents, FileContents: fileArtifacts.FileContents,
FileLicenses: fileArtifacts.FileLicenses, FileLicenses: fileArtifacts.FileLicenses,
Executables: fileArtifacts.Executables, Executables: fileArtifacts.Executables,
Unknowns: fileArtifacts.Unknowns,
LinuxDistribution: toSyftLinuxRelease(doc.Distro), LinuxDistribution: toSyftLinuxRelease(doc.Distro),
}, },
Source: *toSyftSourceData(doc.Source), Source: *toSyftSourceData(doc.Source),
@ -66,6 +67,7 @@ func deduplicateErrors(errors []error) []string {
return errorMessages return errorMessages
} }
//nolint:funlen
func toSyftFiles(files []model.File) sbom.Artifacts { func toSyftFiles(files []model.File) sbom.Artifacts {
ret := sbom.Artifacts{ ret := sbom.Artifacts{
FileMetadata: make(map[file.Coordinates]file.Metadata), FileMetadata: make(map[file.Coordinates]file.Metadata),
@ -73,6 +75,7 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
FileContents: make(map[file.Coordinates]string), FileContents: make(map[file.Coordinates]string),
FileLicenses: make(map[file.Coordinates][]file.License), FileLicenses: make(map[file.Coordinates][]file.License),
Executables: make(map[file.Coordinates]file.Executable), Executables: make(map[file.Coordinates]file.Executable),
Unknowns: make(map[file.Coordinates][]string),
} }
for _, f := range files { for _, f := range files {
@ -130,6 +133,10 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
if f.Executable != nil { if f.Executable != nil {
ret.Executables[coord] = *f.Executable ret.Executables[coord] = *f.Executable
} }
if len(f.Unknowns) > 0 {
ret.Unknowns[coord] = f.Unknowns
}
} }
return ret return ret

View File

@ -234,6 +234,7 @@ func Test_toSyftFiles(t *testing.T) {
FileMetadata: map[file.Coordinates]file.Metadata{}, FileMetadata: map[file.Coordinates]file.Metadata{},
FileDigests: map[file.Coordinates][]file.Digest{}, FileDigests: map[file.Coordinates][]file.Digest{},
Executables: map[file.Coordinates]file.Executable{}, Executables: map[file.Coordinates]file.Executable{},
Unknowns: make(map[file.Coordinates][]string),
}, },
}, },
{ {
@ -349,6 +350,7 @@ func Test_toSyftFiles(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
tt.want.FileContents = make(map[file.Coordinates]string) tt.want.FileContents = make(map[file.Coordinates]string)
tt.want.FileLicenses = make(map[file.Coordinates][]file.License) tt.want.FileLicenses = make(map[file.Coordinates][]file.License)
tt.want.Unknowns = make(map[file.Coordinates][]string)
assert.Equal(t, tt.want, toSyftFiles(tt.files)) assert.Equal(t, tt.want, toSyftFiles(tt.files))
}) })
} }

View File

@ -190,6 +190,14 @@ func TestApkDBCataloger(t *testing.T) {
} }
func Test_corruptDb(t *testing.T) {
pkgtest.NewCatalogTester().
FromDirectory(t, "test-fixtures/corrupt").
WithCompareOptions(cmpopts.IgnoreFields(pkg.ApkDBEntry{}, "Files", "GitCommit", "Checksum")).
WithError().
TestCataloger(t, NewDBCataloger())
}
func TestCatalogerDependencyTree(t *testing.T) { func TestCatalogerDependencyTree(t *testing.T) {
assertion := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) { assertion := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
expected := map[string][]string{ expected := map[string][]string{

View File

@ -12,6 +12,7 @@ import (
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/linux"
@ -38,6 +39,7 @@ type parsedData struct {
func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
scanner := bufio.NewScanner(reader) scanner := bufio.NewScanner(reader)
var errs error
var apks []parsedData var apks []parsedData
var currentEntry parsedData var currentEntry parsedData
entryParsingInProgress := false entryParsingInProgress := false
@ -81,10 +83,12 @@ func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environm
field := parseApkField(line) field := parseApkField(line)
if field == nil { if field == nil {
log.Warnf("unable to parse field data from line %q", line) log.Warnf("unable to parse field data from line %q", line)
errs = unknown.Appendf(errs, reader, "unable to parse field data from line %q", line)
continue continue
} }
if len(field.name) == 0 { if len(field.name) == 0 {
log.Warnf("failed to parse field name from line %q", line) log.Warnf("failed to parse field name from line %q", line)
errs = unknown.Appendf(errs, reader, "failed to parse field name from line %q", line)
continue continue
} }
if len(field.value) == 0 { if len(field.value) == 0 {
@ -131,7 +135,7 @@ func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environm
pkgs = append(pkgs, newPackage(apk, r, reader.Location)) pkgs = append(pkgs, newPackage(apk, r, reader.Location))
} }
return pkgs, nil, nil return pkgs, nil, errs
} }
func findReleases(resolver file.Resolver, dbPath string) []linux.Release { func findReleases(resolver file.Resolver, dbPath string) []linux.Release {

View File

@ -0,0 +1,43 @@
this is a corrupt db
C:Q1v4QhLje3kWlC8DJj+ZfJTjlJRSU=
P:alpine-baselayout-data
V:3.2.0-r22
A:x86_64
S:11435
I:73728
o:alpine-baselayout
t:1655134784
c:cb70ca5c6d6db0399d2dd09189c5d57827bce5cd
r:alpine-baselayout
F:etc
R:fstab
Z:Q11Q7hNe8QpDS531guqCdrXBzoA/o=
R:group
Z:Q13K+olJg5ayzHSVNUkggZJXuB+9Y=
R:hostname
Z:Q16nVwYVXP/tChvUPdukVD2ifXOmc=
R:hosts
Z:Q1BD6zJKZTRWyqGnPi4tSfd3krsMU=
R:inittab
Z:Q1TsthbhW7QzWRe1E/NKwTOuD4pHc=
R:modules
Z:Q1toogjUipHGcMgECgPJX64SwUT1M=
R:mtab
a:0:0:777
Z:Q1kiljhXXH1LlQroHsEJIkPZg2eiw=
R:passwd
Z:Q1TchuuLUfur0izvfZQZxgN/LJhB8=
R:profile
Z:Q1F3DgXUP+jNZDknmQPPb5t9FSfDg=
R:protocols
Z:Q1omKlp3vgGq2ZqYzyD/KHNdo8rDc=
R:services
Z:Q19WLCv5ItKg4MH7RWfNRh1I7byQc=
R:shadow
a:0:42:640
Z:Q1ltrPIAW2zHeDiajsex2Bdmq3uqA=
R:shells
Z:Q1ojm2YdpCJ6B/apGDaZ/Sdb2xJkA=
R:sysctl.conf
Z:Q14upz3tfnNxZkIEsUhWn7Xoiw96g=

View File

@ -11,6 +11,14 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
) )
func TestAlpmUnknowns(t *testing.T) {
pkgtest.NewCatalogTester().
FromDirectory(t, "test-fixtures/installed").
WithCompareOptions(cmpopts.IgnoreFields(pkg.AlpmFileRecord{}, "Time")).
WithError().
TestCataloger(t, NewDBCataloger())
}
func TestAlpmCataloger(t *testing.T) { func TestAlpmCataloger(t *testing.T) {
gmpDbLocation := file.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc") gmpDbLocation := file.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc")
treeSitterDbLocation := file.NewLocation("var/lib/pacman/local/tree-sitter-0.22.6-1/desc") treeSitterDbLocation := file.NewLocation("var/lib/pacman/local/tree-sitter-0.22.6-1/desc")

View File

@ -17,6 +17,7 @@ import (
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -41,6 +42,8 @@ type parsedData struct {
// parseAlpmDB parses the arch linux pacman database flat-files and returns the packages and relationships found within. // parseAlpmDB parses the arch linux pacman database flat-files and returns the packages and relationships found within.
func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var errs error
data, err := parseAlpmDBEntry(reader) data, err := parseAlpmDBEntry(reader)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -52,24 +55,25 @@ func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environ
base := path.Dir(reader.RealPath) base := path.Dir(reader.RealPath)
var locs []file.Location
// replace the files found the pacman database with the files from the mtree These contain more metadata and // replace the files found the pacman database with the files from the mtree These contain more metadata and
// thus more useful. // thus more useful.
files, fileLoc := fetchPkgFiles(base, resolver) files, fileLoc, err := fetchPkgFiles(base, resolver)
backups, backupLoc := fetchBackupFiles(base, resolver) errs = unknown.Join(errs, err)
if err == nil {
var locs []file.Location
if fileLoc != nil {
locs = append(locs, fileLoc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)) locs = append(locs, fileLoc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
data.Files = files data.Files = files
} }
backups, backupLoc, err := fetchBackupFiles(base, resolver)
if backupLoc != nil { errs = unknown.Join(errs, err)
if err == nil {
locs = append(locs, backupLoc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)) locs = append(locs, backupLoc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
data.Backup = backups data.Backup = backups
} }
if data.Package == "" { if data.Package == "" {
return nil, nil, nil return nil, nil, errs
} }
return []pkg.Package{ return []pkg.Package{
@ -79,63 +83,56 @@ func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environ
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
locs..., locs...,
), ),
}, nil, nil }, nil, errs
} }
func fetchPkgFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, *file.Location) { func fetchPkgFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, file.Location, error) {
// TODO: probably want to use MTREE and PKGINFO here // TODO: probably want to use MTREE and PKGINFO here
target := path.Join(base, "mtree") target := path.Join(base, "mtree")
loc, err := getLocation(target, resolver) loc, err := getLocation(target, resolver)
if err != nil { if err != nil {
log.WithFields("error", err, "path", target).Trace("failed to find mtree file") log.WithFields("error", err, "path", target).Trace("failed to find mtree file")
return []pkg.AlpmFileRecord{}, nil return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to find mtree file: %w", err))
} }
if loc == nil { reader, err := resolver.FileContentsByLocation(loc)
return []pkg.AlpmFileRecord{}, nil
}
reader, err := resolver.FileContentsByLocation(*loc)
if err != nil { if err != nil {
return []pkg.AlpmFileRecord{}, nil return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to get contents: %w", err))
} }
defer internal.CloseAndLogError(reader, loc.RealPath) defer internal.CloseAndLogError(reader, loc.RealPath)
pkgFiles, err := parseMtree(reader) pkgFiles, err := parseMtree(reader)
if err != nil { if err != nil {
log.WithFields("error", err, "path", target).Trace("failed to parse mtree file") log.WithFields("error", err, "path", target).Trace("failed to parse mtree file")
return []pkg.AlpmFileRecord{}, nil return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to parse mtree: %w", err))
} }
return pkgFiles, loc return pkgFiles, loc, nil
} }
func fetchBackupFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, *file.Location) { func fetchBackupFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, file.Location, error) {
// We only really do this to get any backup database entries from the files database // We only really do this to get any backup database entries from the files database
target := filepath.Join(base, "files") target := filepath.Join(base, "files")
loc, err := getLocation(target, resolver) loc, err := getLocation(target, resolver)
if err != nil { if err != nil {
log.WithFields("error", err, "path", target).Trace("failed to find alpm files") log.WithFields("error", err, "path", target).Trace("failed to find alpm files")
return []pkg.AlpmFileRecord{}, nil return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to find alpm files: %w", err))
}
if loc == nil {
return []pkg.AlpmFileRecord{}, nil
} }
reader, err := resolver.FileContentsByLocation(*loc) reader, err := resolver.FileContentsByLocation(loc)
if err != nil { if err != nil {
return []pkg.AlpmFileRecord{}, nil return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to get contents: %w", err))
} }
defer internal.CloseAndLogError(reader, loc.RealPath) defer internal.CloseAndLogError(reader, loc.RealPath)
filesMetadata, err := parseAlpmDBEntry(reader) filesMetadata, err := parseAlpmDBEntry(reader)
if err != nil { if err != nil {
return []pkg.AlpmFileRecord{}, nil return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to parse alpm db entry: %w", err))
} }
if filesMetadata != nil { if filesMetadata != nil {
return filesMetadata.Backup, loc return filesMetadata.Backup, loc, nil
} }
return []pkg.AlpmFileRecord{}, loc return []pkg.AlpmFileRecord{}, loc, nil
} }
func parseAlpmDBEntry(reader io.Reader) (*parsedData, error) { func parseAlpmDBEntry(reader io.Reader) (*parsedData, error) {
@ -171,20 +168,21 @@ func newScanner(reader io.Reader) *bufio.Scanner {
return scanner return scanner
} }
func getLocation(path string, resolver file.Resolver) (*file.Location, error) { func getLocation(path string, resolver file.Resolver) (file.Location, error) {
loc := file.NewLocation(path)
locs, err := resolver.FilesByPath(path) locs, err := resolver.FilesByPath(path)
if err != nil { if err != nil {
return nil, err return loc, err
} }
if len(locs) == 0 { if len(locs) == 0 {
return nil, fmt.Errorf("could not find file: %s", path) return loc, fmt.Errorf("could not find file: %s", path)
} }
if len(locs) > 1 { if len(locs) > 1 {
log.WithFields("path", path).Trace("multiple files found for path, using first path") log.WithFields("path", path).Trace("multiple files found for path, using first path")
} }
return &locs[0], nil return locs[0], nil
} }
func parseDatabase(b *bufio.Scanner) (*parsedData, error) { func parseDatabase(b *bufio.Scanner) (*parsedData, error) {

View File

@ -0,0 +1,34 @@
%NME%
tree-sitter
%VER.6-1
%BASE%
tree-sitter
%DESC%
Incremental parsing library
%BUILDDATE%
1714945746
%INSTALLDATE%
1715026360
%PACKA@archlinux.org>
%SIZE%
223539
%REASON%
1
%LICENSE%
MIT
%VALIDATION%
pgp
%PROVIDE
libtree-sitter.so=0-64

View File

@ -6,8 +6,10 @@ package binary
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -59,12 +61,14 @@ func (c cataloger) Name() string {
func (c cataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { func (c cataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
var packages []pkg.Package var packages []pkg.Package
var relationships []artifact.Relationship var relationships []artifact.Relationship
var errs error
for _, cls := range c.classifiers { for _, cls := range c.classifiers {
log.WithFields("classifier", cls.Class).Trace("cataloging binaries") log.WithFields("classifier", cls.Class).Trace("cataloging binaries")
newPkgs, err := catalog(resolver, cls) newPkgs, err := catalog(resolver, cls)
if err != nil { if err != nil {
log.WithFields("error", err, "classifier", cls.Class).Warn("unable to catalog binary package: %w", err) log.WithFields("error", err, "classifier", cls.Class).Debugf("unable to catalog binary package: %v", err)
errs = unknown.Join(errs, fmt.Errorf("%s: %w", cls.Class, err))
continue continue
} }
newPackages: newPackages:
@ -82,7 +86,7 @@ func (c cataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Pac
} }
} }
return packages, relationships, nil return packages, relationships, errs
} }
// mergePackages merges information from the extra package into the target package // mergePackages merges information from the extra package into the target package
@ -98,6 +102,7 @@ func mergePackages(target *pkg.Package, extra *pkg.Package) {
} }
func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, err error) { func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, err error) {
var errs error
locations, err := resolver.FilesByGlob(cls.FileGlob) locations, err := resolver.FilesByGlob(cls.FileGlob)
if err != nil { if err != nil {
return nil, err return nil, err
@ -105,11 +110,12 @@ func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, er
for _, location := range locations { for _, location := range locations {
pkgs, err := cls.EvidenceMatcher(cls, matcherContext{resolver: resolver, location: location}) pkgs, err := cls.EvidenceMatcher(cls, matcherContext{resolver: resolver, location: location})
if err != nil { if err != nil {
return nil, err errs = unknown.Append(errs, location, err)
continue
} }
packages = append(packages, pkgs...) packages = append(packages, pkgs...)
} }
return packages, nil return packages, errs
} }
// packagesMatch returns true if the binary packages "match" based on basic criteria // packagesMatch returns true if the binary packages "match" based on basic criteria

View File

@ -1668,7 +1668,7 @@ func Test_Cataloger_ResilientToErrors(t *testing.T) {
resolver := &panicyResolver{} resolver := &panicyResolver{}
_, _, err := c.Catalog(context.Background(), resolver) _, _, err := c.Catalog(context.Background(), resolver)
assert.NoError(t, err) assert.Error(t, err)
assert.True(t, resolver.searchCalled) assert.True(t, resolver.searchCalled)
} }

View File

@ -11,6 +11,7 @@ import (
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/mimetype" "github.com/anchore/syft/internal/mimetype"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/unionreader" "github.com/anchore/syft/syft/internal/unionreader"
@ -52,6 +53,7 @@ func (c *elfPackageCataloger) Name() string {
} }
func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
var errs error
locations, err := resolver.FilesByMIMEType(mimetype.ExecutableMIMETypeSet.List()...) locations, err := resolver.FilesByMIMEType(mimetype.ExecutableMIMETypeSet.List()...)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("unable to get binary files by mime type: %w", err) return nil, nil, fmt.Errorf("unable to get binary files by mime type: %w", err)
@ -62,7 +64,8 @@ func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver)
for _, location := range locations { for _, location := range locations {
notes, key, err := parseElfPackageNotes(resolver, location, c) notes, key, err := parseElfPackageNotes(resolver, location, c)
if err != nil { if err != nil {
return nil, nil, err errs = unknown.Append(errs, location, err)
continue
} }
if notes == nil { if notes == nil {
continue continue
@ -87,7 +90,7 @@ func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver)
// why not return relationships? We have an executable cataloger that will note the dynamic libraries imported by // why not return relationships? We have an executable cataloger that will note the dynamic libraries imported by
// each binary. After all files and packages are processed there is a final task that creates package-to-package // each binary. After all files and packages are processed there is a final task that creates package-to-package
// and package-to-file relationships based on the dynamic libraries imported by each binary. // and package-to-file relationships based on the dynamic libraries imported by each binary.
return pkgs, nil, nil return pkgs, nil, errs
} }
func parseElfPackageNotes(resolver file.Resolver, location file.Location, c *elfPackageCataloger) (*elfBinaryPackageNotes, elfPackageKey, error) { func parseElfPackageNotes(resolver file.Resolver, location file.Location, c *elfPackageCataloger) (*elfBinaryPackageNotes, elfPackageKey, error) {
@ -104,7 +107,7 @@ func parseElfPackageNotes(resolver file.Resolver, location file.Location, c *elf
if err != nil { if err != nil {
log.WithFields("file", location.Path(), "error", err).Trace("unable to parse ELF notes") log.WithFields("file", location.Path(), "error", err).Trace("unable to parse ELF notes")
return nil, elfPackageKey{}, nil return nil, elfPackageKey{}, err
} }
if notes == nil { if notes == nil {
@ -173,7 +176,7 @@ func getELFNotes(r file.LocationReadCloser) (*elfBinaryPackageNotes, error) {
if len(notes) > headerSize { if len(notes) > headerSize {
var metadata elfBinaryPackageNotes var metadata elfBinaryPackageNotes
newPayload := bytes.TrimRight(notes[headerSize:], "\x00") newPayload := bytes.TrimRight(notes[headerSize:], "\x00")
if err := json.Unmarshal(newPayload, &metadata); err == nil { if err = json.Unmarshal(newPayload, &metadata); err == nil {
return &metadata, nil return &metadata, nil
} }
log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to unmarshal ELF package notes as JSON") log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to unmarshal ELF package notes as JSON")

View File

@ -3,6 +3,8 @@ package binary
import ( import (
"testing" "testing"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
@ -14,6 +16,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
name string name string
fixture string fixture string
expected []pkg.Package expected []pkg.Package
wantErr require.ErrorAssertionFunc
}{ }{
{ {
name: "go case", name: "go case",
@ -63,6 +66,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
}, },
}, },
}, },
wantErr: require.Error,
}, },
{ {
name: "fedora 64 bit binaries", name: "fedora 64 bit binaries",
@ -116,6 +120,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
WithImageResolver(t, v.fixture). WithImageResolver(t, v.fixture).
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
Expects(v.expected, nil). Expects(v.expected, nil).
WithErrorAssertion(v.wantErr).
TestCataloger(t, NewELFPackageCataloger()) TestCataloger(t, NewELFPackageCataloger())
}) })
} }

View File

@ -5,9 +5,11 @@ RUN dnf update -y; \
dnf clean all dnf clean all
RUN mkdir -p /usr/local/bin/elftests/elfbinwithnestedlib RUN mkdir -p /usr/local/bin/elftests/elfbinwithnestedlib
RUN mkdir -p /usr/local/bin/elftests/elfbinwithsisterlib RUN mkdir -p /usr/local/bin/elftests/elfbinwithsisterlib
RUN mkdir -p /usr/local/bin/elftests/elfbinwithcorrupt
COPY ./elfbinwithnestedlib /usr/local/bin/elftests/elfbinwithnestedlib COPY ./elfbinwithnestedlib /usr/local/bin/elftests/elfbinwithnestedlib
COPY ./elfbinwithsisterlib /usr/local/bin/elftests/elfbinwithsisterlib COPY ./elfbinwithsisterlib /usr/local/bin/elftests/elfbinwithsisterlib
COPY ./elfbinwithcorrupt /usr/local/bin/elftests/elfbinwithcorrupt
ENV LD_LIBRARY_PATH=/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib ENV LD_LIBRARY_PATH=/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib
@ -16,6 +18,8 @@ RUN make
WORKDIR /usr/local/bin/elftests/elfbinwithsisterlib WORKDIR /usr/local/bin/elftests/elfbinwithsisterlib
RUN make RUN make
WORKDIR /usr/local/bin/elftests/elfbinwithcorrupt
RUN make
# let's make the test image smaller, since we only require the built binaries and supporting libraries # let's make the test image smaller, since we only require the built binaries and supporting libraries
FROM busybox:1.36.1-musl FROM busybox:1.36.1-musl

View File

@ -0,0 +1,6 @@
#include <iostream>
#include "hello_world.h"
void print_hello_world() {
std::cout << "Hello, World!" << std::endl;
}

View File

@ -0,0 +1,8 @@
#ifndef HELLO_WORLD_H
#define HELLO_WORLD_H
// Function declaration for printing "Hello, World!" to stdout
void print_hello_world();
#endif // HELLO_WORLD_H

View File

@ -0,0 +1,48 @@
LDFLAGS := -L/lib64 -lstdc++
SRC_DIR := ./
BUILD_DIR := ../build
BIN_DIR := ../bin
LIB_DIR := $(BIN_DIR)/lib
LIB_NAME := hello_world
LIB_SRC := $(SRC_DIR)/hello_world.cpp
LIB_OBJ := $(BUILD_DIR)/$(LIB_NAME).o
LIB_SO := $(LIB_DIR)/lib$(LIB_NAME).so
EXECUTABLE := elfbinwithnestedlib
EXEC_SRC := $(SRC_DIR)/testbin.cpp
EXEC_OBJ := $(BUILD_DIR)/$(EXECUTABLE).o
all: testfixture
$(LIB_SO): $(LIB_OBJ) | $(LIB_DIR)
$(CC) -shared -o $@ $<
echo '{ corrupt json "system": "syftsys","name": "libhello_world.so","version": "0.01","pure:0.01"}' | objcopy --add-section .note.package=/dev/stdin --set-section-flags .note.package=noload,readonly $@
$(LIB_OBJ): $(LIB_SRC) | $(BUILD_DIR)
$(CC) $(CFLAGS) -fPIC -c $< -o $@
$(EXEC_OBJ): $(EXEC_SRC) | $(BUILD_DIR)
$(CC) $(CFLAGS) -c $< -o $@
$(BIN_DIR):
mkdir -p $(BIN_DIR)
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
$(LIB_DIR):
mkdir -p $(LIB_DIR)
$(BIN_DIR)/$(EXECUTABLE): $(EXEC_OBJ) $(LIB_SO) | $(BIN_DIR)
$(CC) $(CFLAGS) -o $@ $^ -L$(LIB_DIR) -l$(LIB_NAME) $(LDFLAGS)
echo '{corrupt json ..._syfttestfixture:0.01"}' | objcopy --add-section .note.package=/dev/stdin --set-section-flags .note.package=noload,readonly $@
testfixture: $(BIN_DIR)/$(EXECUTABLE)
clean:
rm -rf $(BUILD_DIR) $(LIB_DIR) $(BIN_DIR) $(EXECUTABLE)
.PHONY: all clean

View File

@ -0,0 +1,8 @@
#include "hello_world.h"
int main() {
// Call the function from the shared library
print_hello_world();
return 0;
}

View File

@ -0,0 +1,18 @@
CC = g++
CFLAGS = -std=c++17 -Wall -Wextra -pedantic
BUILD_DIR := ./build
BIN_DIR := ./bin
LIB_DIR := $(BIN_DIR)/lib
all: testfixtures
testfixtures:
$(MAKE) -C elfsrc
clean:
rm -rf $(BUILD_DIR) $(BIN_DIR)
.PHONY: all clean testfixtures

View File

@ -8,6 +8,7 @@ import (
"io" "io"
"strings" "strings"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -25,7 +26,7 @@ func parseConanfile(_ context.Context, _ file.Resolver, _ *generic.Environment,
line, err := r.ReadString('\n') line, err := r.ReadString('\n')
switch { switch {
case errors.Is(err, io.EOF): case errors.Is(err, io.EOF):
return pkgs, nil, nil return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
case err != nil: case err != nil:
return nil, nil, fmt.Errorf("failed to parse conanfile.txt file: %w", err) return nil, nil, fmt.Errorf("failed to parse conanfile.txt file: %w", err)
} }

View File

@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"strings" "strings"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -72,7 +73,7 @@ func parseConanLock(_ context.Context, _ file.Resolver, _ *generic.Environment,
} }
} }
return pkgs, relationships, nil return pkgs, relationships, unknown.IfEmptyf(pkgs, "unable to determine packages")
} }
// handleConanLockV1 handles the parsing of conan lock v1 files (aka v0.4) // handleConanLockV1 handles the parsing of conan lock v1 files (aka v0.4)

View File

@ -373,3 +373,10 @@ func TestParseConanLockV2(t *testing.T) {
pkgtest.TestFileParser(t, fixture, parseConanLock, expected, expectedRelationships) pkgtest.TestFileParser(t, fixture, parseConanLock, expected, expectedRelationships)
} }
func Test_corruptConanlock(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/corrupt/conan.lock").
WithError().
TestParser(t, parseConanLock)
}

View File

@ -0,0 +1,10 @@
{
corrupt json
version": "0.5",
"requires": [
"sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7%1675278904.0791488",
"matrix/1.1#905c3f0babc520684c84127378fefdd0%1675278901.7527816"
],
"build_requires": [],
"python_requires": []
}

View File

@ -9,6 +9,7 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -85,7 +86,7 @@ func parsePubspecLock(_ context.Context, _ file.Resolver, _ *generic.Environment
) )
} }
return pkgs, nil, nil return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
} }
func (p *pubspecLockPackage) getVcsURL() string { func (p *pubspecLockPackage) getVcsURL() string {

View File

@ -106,3 +106,10 @@ func TestParsePubspecLock(t *testing.T) {
pkgtest.TestFileParser(t, fixture, parsePubspecLock, expected, expectedRelationships) pkgtest.TestFileParser(t, fixture, parsePubspecLock, expected, expectedRelationships)
} }
func Test_corruptPubspecLock(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/corrupt/pubspec.lock").
WithError().
TestParser(t, parsePubspecLock)
}

View File

@ -0,0 +1,7 @@
pa
kages:
ale:
dependency: transitive
descr
s ps:
dart: ">=2.12.0 <3.0.0"

View File

@ -15,6 +15,7 @@ import (
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -39,7 +40,7 @@ func parseDpkgDB(_ context.Context, resolver file.Resolver, env *generic.Environ
pkgs = append(pkgs, p) pkgs = append(pkgs, p)
} }
return pkgs, nil, nil return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
} }
func findDpkgInfoFiles(name string, resolver file.Resolver, dbLocation file.Location) []file.Location { func findDpkgInfoFiles(name string, resolver file.Resolver, dbLocation file.Location) []file.Location {

View File

@ -257,6 +257,17 @@ func Test_parseDpkgStatus(t *testing.T) {
} }
} }
func Test_corruptEntry(t *testing.T) {
f, err := os.Open("test-fixtures/var/lib/dpkg/status.d/corrupt")
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, f.Close()) })
reader := bufio.NewReader(f)
_, err = parseDpkgStatus(reader)
require.Error(t, err)
}
func TestSourceVersionExtract(t *testing.T) { func TestSourceVersionExtract(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -312,7 +323,7 @@ func Test_parseDpkgStatus_negativeCases(t *testing.T) {
{ {
name: "no more packages", name: "no more packages",
input: `Package: apt`, input: `Package: apt`,
wantErr: require.NoError, wantErr: requireAs(errors.New("unable to determine packages")),
}, },
{ {
name: "duplicated key", name: "duplicated key",

View File

@ -0,0 +1,6 @@
Pakij: apt
Stratus: install ok installed
Prioority: required
Section: admin
Insterface to the configuration settings
* apt-key as an interface to manage authentication keys

View File

@ -8,6 +8,7 @@ import (
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/relationship" "github.com/anchore/syft/internal/relationship"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -126,5 +127,5 @@ func parseDotnetDeps(_ context.Context, _ file.Resolver, _ *generic.Environment,
// this will only consider package-to-package relationships. // this will only consider package-to-package relationships.
relationship.Sort(relationships) relationship.Sort(relationships)
return pkgs, relationships, nil return pkgs, relationships, unknown.IfEmptyf(pkgs, "unable to determine packages")
} }

View File

@ -9,6 +9,13 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
) )
func Test_corruptDotnetDeps(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/glob-paths/src/something.deps.json").
WithError().
TestParser(t, parseDotnetDeps)
}
func TestParseDotnetDeps(t *testing.T) { func TestParseDotnetDeps(t *testing.T) {
fixture := "test-fixtures/TestLibrary.deps.json" fixture := "test-fixtures/TestLibrary.deps.json"
fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture)) fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture))

View File

@ -28,30 +28,26 @@ func parseDotnetPortableExecutable(_ context.Context, _ file.Resolver, _ *generi
peFile, err := pe.NewBytes(by, &pe.Options{}) peFile, err := pe.NewBytes(by, &pe.Options{})
if err != nil { if err != nil {
// TODO: known-unknown
log.Tracef("unable to create PE instance for file '%s': %v", f.RealPath, err) log.Tracef("unable to create PE instance for file '%s': %v", f.RealPath, err)
return nil, nil, nil return nil, nil, err
} }
err = peFile.Parse() err = peFile.Parse()
if err != nil { if err != nil {
// TODO: known-unknown
log.Tracef("unable to parse PE file '%s': %v", f.RealPath, err) log.Tracef("unable to parse PE file '%s': %v", f.RealPath, err)
return nil, nil, nil return nil, nil, err
} }
versionResources, err := peFile.ParseVersionResources() versionResources, err := peFile.ParseVersionResources()
if err != nil { if err != nil {
// TODO: known-unknown
log.Tracef("unable to parse version resources in PE file: %s: %v", f.RealPath, err) log.Tracef("unable to parse version resources in PE file: %s: %v", f.RealPath, err)
return nil, nil, nil return nil, nil, fmt.Errorf("unable to parse version resources in PE file: %w", err)
} }
dotNetPkg, err := buildDotNetPackage(versionResources, f) dotNetPkg, err := buildDotNetPackage(versionResources, f)
if err != nil { if err != nil {
// TODO: known-unknown log.Tracef("unable to build dotnet package for: %v %v", f.RealPath, err)
log.Tracef("unable to build dotnet package: %v", err) return nil, nil, err
return nil, nil, nil
} }
return []pkg.Package{dotNetPkg}, nil, nil return []pkg.Package{dotNetPkg}, nil, nil
@ -60,12 +56,12 @@ func parseDotnetPortableExecutable(_ context.Context, _ file.Resolver, _ *generi
func buildDotNetPackage(versionResources map[string]string, f file.LocationReadCloser) (dnpkg pkg.Package, err error) { func buildDotNetPackage(versionResources map[string]string, f file.LocationReadCloser) (dnpkg pkg.Package, err error) {
name := findName(versionResources) name := findName(versionResources)
if name == "" { if name == "" {
return dnpkg, fmt.Errorf("unable to find PE name in file: %s", f.RealPath) return dnpkg, fmt.Errorf("unable to find PE name in file")
} }
version := findVersion(versionResources) version := findVersion(versionResources)
if version == "" { if version == "" {
return dnpkg, fmt.Errorf("unable to find PE version in file: %s", f.RealPath) return dnpkg, fmt.Errorf("unable to find PE version in file")
} }
metadata := pkg.DotnetPortableExecutableEntry{ metadata := pkg.DotnetPortableExecutableEntry{

View File

@ -297,6 +297,13 @@ func TestParseDotnetPortableExecutable(t *testing.T) {
} }
} }
func Test_corruptDotnetPE(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/glob-paths/src/something.exe").
WithError().
TestParser(t, parseDotnetPortableExecutable)
}
func Test_extractVersion(t *testing.T) { func Test_extractVersion(t *testing.T) {
tests := []struct { tests := []struct {
input string input string

View File

@ -9,6 +9,7 @@ import (
"regexp" "regexp"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -22,25 +23,33 @@ var mixLockDelimiter = regexp.MustCompile(`[%{}\n" ,:]+`)
// parseMixLock parses a mix.lock and returns the discovered Elixir packages. // parseMixLock parses a mix.lock and returns the discovered Elixir packages.
func parseMixLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { func parseMixLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var errs error
r := bufio.NewReader(reader) r := bufio.NewReader(reader)
var packages []pkg.Package var packages []pkg.Package
lineNum := 0
for { for {
lineNum++
line, err := r.ReadString('\n') line, err := r.ReadString('\n')
switch { switch {
case errors.Is(err, io.EOF): case errors.Is(err, io.EOF):
return packages, nil, nil if errs == nil {
errs = unknown.IfEmptyf(packages, "unable to determine packages")
}
return packages, nil, errs
case err != nil: case err != nil:
return nil, nil, fmt.Errorf("failed to parse mix.lock file: %w", err) return nil, nil, fmt.Errorf("failed to parse mix.lock file: %w", err)
} }
tokens := mixLockDelimiter.Split(line, -1) tokens := mixLockDelimiter.Split(line, -1)
if len(tokens) < 6 { if len(tokens) < 6 {
errs = unknown.Appendf(errs, reader, "unable to read mix lock line %d: %s", lineNum, line)
continue continue
} }
name, version, hash, hashExt := tokens[1], tokens[4], tokens[5], tokens[len(tokens)-2] name, version, hash, hashExt := tokens[1], tokens[4], tokens[5], tokens[len(tokens)-2]
if name == "" { if name == "" {
log.WithFields("path", reader.RealPath).Debug("skipping empty package name from mix.lock file") log.WithFields("path", reader.RealPath).Debug("skipping empty package name from mix.lock file")
errs = unknown.Appendf(errs, reader, "skipping empty package name from mix.lock file, for line: %d: %s", lineNum, line)
continue continue
} }

View File

@ -2,6 +2,7 @@ package erlang
import ( import (
"context" "context"
"fmt"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
@ -17,7 +18,7 @@ func parseOTPApp(_ context.Context, _ file.Resolver, _ *generic.Environment, rea
// there are multiple file formats that use the *.app extension, so it's possible that this is not an OTP app file at all // there are multiple file formats that use the *.app extension, so it's possible that this is not an OTP app file at all
// ... which means we should not return an error here // ... which means we should not return an error here
log.WithFields("error", err).Trace("unable to parse Erlang OTP app") log.WithFields("error", err).Trace("unable to parse Erlang OTP app")
return nil, nil, nil return nil, nil, fmt.Errorf("unable to parse Erlang OTP app")
} }
var packages []pkg.Package var packages []pkg.Package

View File

@ -41,3 +41,10 @@ func TestParseOTPApplication(t *testing.T) {
}) })
} }
} }
func Test_corruptOtpApp(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/corrupt/rabbitmq.app").
WithError().
TestParser(t, parseOTPApp)
}

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -95,7 +96,7 @@ func parseRebarLock(_ context.Context, _ file.Resolver, _ *generic.Environment,
p.SetID() p.SetID()
packages = append(packages, *p) packages = append(packages, *p)
} }
return packages, nil, nil return packages, nil, unknown.IfEmptyf(packages, "unable to determine packages")
} }
// integrity check // integrity check

View File

@ -255,3 +255,10 @@ func TestParseRebarLock(t *testing.T) {
}) })
} }
} }
func Test_corruptRebarLock(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/corrupt/rebar.lock").
WithError().
TestParser(t, parseRebarLock)
}

View File

@ -0,0 +1,9 @@
cation, 'rabbit', [
{description, "RabbitMQ"},
{vsn, "3.12.10"},
{id, "v3.12.9-9-g1f61ca8"},
{modules, ['amqqueue','background_gc']},
{optional_itmq-server#1593
{channel_max, 2047}
]}
]}.

View File

@ -0,0 +1,11 @@
{"1.2.0",
[{<<"certifi{pkg,<<"certifi">>,<<"2.9.0">>},0},
{<<"idna">>,{pkg,<<"idpkg,<<"parse_trans">>,<<"3.3.1">>},0},
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.6">>},0},
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},0}]}.
[
{pkg_hash,[
{<<"certifi">>, <<"6F2A475689DD47F19FB74334859D460A2DC4E3252A3324BD2111B8F0429E7E21">>},
{<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>},
{<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>},
{<<"mimerl

View File

@ -6,6 +6,7 @@ import (
"github.com/anchore/go-logger" "github.com/anchore/go-logger"
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/linux"
@ -151,6 +152,7 @@ func (c *Cataloger) Name() string {
func (c *Cataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { func (c *Cataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
var packages []pkg.Package var packages []pkg.Package
var relationships []artifact.Relationship var relationships []artifact.Relationship
var errs error
logger := log.Nested("cataloger", c.upstreamCataloger) logger := log.Nested("cataloger", c.upstreamCataloger)
@ -166,7 +168,8 @@ func (c *Cataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.
discoveredPackages, discoveredRelationships, err := invokeParser(ctx, resolver, location, logger, parser, &env) discoveredPackages, discoveredRelationships, err := invokeParser(ctx, resolver, location, logger, parser, &env)
if err != nil { if err != nil {
continue // logging is handled within invokeParser // parsers may return errors and valid packages / relationships
errs = unknown.Append(errs, location, err)
} }
for _, p := range discoveredPackages { for _, p := range discoveredPackages {
@ -176,7 +179,7 @@ func (c *Cataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.
relationships = append(relationships, discoveredRelationships...) relationships = append(relationships, discoveredRelationships...)
} }
return c.process(ctx, resolver, packages, relationships, nil) return c.process(ctx, resolver, packages, relationships, errs)
} }
func (c *Cataloger) process(ctx context.Context, resolver file.Resolver, pkgs []pkg.Package, rels []artifact.Relationship, err error) ([]pkg.Package, []artifact.Relationship, error) { func (c *Cataloger) process(ctx context.Context, resolver file.Resolver, pkgs []pkg.Package, rels []artifact.Relationship, err error) ([]pkg.Package, []artifact.Relationship, error) {
@ -196,11 +199,11 @@ func invokeParser(ctx context.Context, resolver file.Resolver, location file.Loc
discoveredPackages, discoveredRelationships, err := parser(ctx, resolver, env, file.NewLocationReadCloser(location, contentReader)) discoveredPackages, discoveredRelationships, err := parser(ctx, resolver, env, file.NewLocationReadCloser(location, contentReader))
if err != nil { if err != nil {
logger.WithFields("location", location.RealPath, "error", err).Warnf("cataloger failed") // these errors are propagated up, and are likely to be coordinate errors
return nil, nil, err logger.WithFields("location", location.RealPath, "error", err).Trace("cataloger returned errors")
} }
return discoveredPackages, discoveredRelationships, nil return discoveredPackages, discoveredRelationships, err
} }
// selectFiles takes a set of file trees and resolves and file references of interest for future cataloging // selectFiles takes a set of file trees and resolves and file references of interest for future cataloging

View File

@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -187,3 +188,27 @@ func TestClosesFileOnParserPanic(t *testing.T) {
}) })
require.True(t, spy.closed) require.True(t, spy.closed)
} }
func Test_genericCatalogerReturnsErrors(t *testing.T) {
genericErrorReturning := NewCataloger("error returning").WithParserByGlobs(func(ctx context.Context, resolver file.Resolver, environment *Environment, locationReader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
return []pkg.Package{
{
Name: "some-package-" + locationReader.Path(),
},
}, nil, unknown.Newf(locationReader, "unable to read")
}, "**/*")
m := file.NewMockResolverForPaths(
"test-fixtures/a-path.txt",
"test-fixtures/empty.txt",
)
got, _, errs := genericErrorReturning.Catalog(context.TODO(), m)
// require packages and errors
require.NotEmpty(t, got)
unknowns, others := unknown.ExtractCoordinateErrors(errs)
require.NotEmpty(t, unknowns)
require.Empty(t, others)
}

View File

@ -35,7 +35,7 @@ func parsePortageContents(_ context.Context, resolver file.Resolver, _ *generic.
name, version := cpvMatch[1], cpvMatch[2] name, version := cpvMatch[1], cpvMatch[2]
if name == "" || version == "" { if name == "" || version == "" {
log.WithFields("path", reader.Location.RealPath).Warnf("failed to parse portage name and version") log.WithFields("path", reader.Location.RealPath).Warnf("failed to parse portage name and version")
return nil, nil, nil return nil, nil, fmt.Errorf("failed to parse portage name and version")
} }
p := pkg.Package{ p := pkg.Package{

View File

@ -1,6 +1,7 @@
package githubactions package githubactions
import ( import (
"fmt"
"strings" "strings"
"github.com/anchore/packageurl-go" "github.com/anchore/packageurl-go"
@ -9,19 +10,19 @@ import (
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
) )
func newPackageFromUsageStatement(use string, location file.Location) *pkg.Package { func newPackageFromUsageStatement(use string, location file.Location) (*pkg.Package, error) {
name, version := parseStepUsageStatement(use) name, version := parseStepUsageStatement(use)
if name == "" { if name == "" {
log.WithFields("file", location.RealPath, "statement", use).Trace("unable to parse github action usage statement") log.WithFields("file", location.RealPath, "statement", use).Trace("unable to parse github action usage statement")
return nil return nil, fmt.Errorf("unable to parse github action usage statement")
} }
if strings.Contains(name, ".github/workflows/") { if strings.Contains(name, ".github/workflows/") {
return newGithubActionWorkflowPackageUsage(name, version, location) return newGithubActionWorkflowPackageUsage(name, version, location), nil
} }
return newGithubActionPackageUsage(name, version, location) return newGithubActionPackageUsage(name, version, location), nil
} }
func newGithubActionWorkflowPackageUsage(name, version string, workflowLocation file.Location) *pkg.Package { func newGithubActionWorkflowPackageUsage(name, version string, workflowLocation file.Location) *pkg.Package {

View File

@ -7,6 +7,7 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -24,14 +25,14 @@ type compositeActionRunsDef struct {
} }
func parseCompositeActionForActionUsage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { func parseCompositeActionForActionUsage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
contents, err := io.ReadAll(reader) contents, errs := io.ReadAll(reader)
if err != nil { if errs != nil {
return nil, nil, fmt.Errorf("unable to read yaml composite action file: %w", err) return nil, nil, fmt.Errorf("unable to read yaml composite action file: %w", errs)
} }
var ca compositeActionDef var ca compositeActionDef
if err = yaml.Unmarshal(contents, &ca); err != nil { if errs = yaml.Unmarshal(contents, &ca); errs != nil {
return nil, nil, fmt.Errorf("unable to parse yaml composite action file: %w", err) return nil, nil, fmt.Errorf("unable to parse yaml composite action file: %w", errs)
} }
// we use a collection to help with deduplication before raising to higher level processing // we use a collection to help with deduplication before raising to higher level processing
@ -42,11 +43,14 @@ func parseCompositeActionForActionUsage(_ context.Context, _ file.Resolver, _ *g
continue continue
} }
p := newPackageFromUsageStatement(step.Uses, reader.Location) p, err := newPackageFromUsageStatement(step.Uses, reader.Location)
if err != nil {
errs = unknown.Append(errs, reader, err)
}
if p != nil { if p != nil {
pkgs.Add(*p) pkgs.Add(*p)
} }
} }
return pkgs.Sorted(), nil, nil return pkgs.Sorted(), nil, errs
} }

View File

@ -33,3 +33,10 @@ func Test_parseCompositeActionForActionUsage(t *testing.T) {
var expectedRelationships []artifact.Relationship var expectedRelationships []artifact.Relationship
pkgtest.TestFileParser(t, fixture, parseCompositeActionForActionUsage, expected, expectedRelationships) pkgtest.TestFileParser(t, fixture, parseCompositeActionForActionUsage, expected, expectedRelationships)
} }
func Test_corruptCompositeAction(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/corrupt/composite-action.yaml").
WithError().
TestParser(t, parseCompositeActionForActionUsage)
}

View File

@ -7,6 +7,7 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -37,14 +38,14 @@ type stepDef struct {
} }
func parseWorkflowForWorkflowUsage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { func parseWorkflowForWorkflowUsage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
contents, err := io.ReadAll(reader) contents, errs := io.ReadAll(reader)
if err != nil { if errs != nil {
return nil, nil, fmt.Errorf("unable to read yaml workflow file: %w", err) return nil, nil, fmt.Errorf("unable to read yaml workflow file: %w", errs)
} }
var wf workflowDef var wf workflowDef
if err = yaml.Unmarshal(contents, &wf); err != nil { if errs = yaml.Unmarshal(contents, &wf); errs != nil {
return nil, nil, fmt.Errorf("unable to parse yaml workflow file: %w", err) return nil, nil, fmt.Errorf("unable to parse yaml workflow file: %w", errs)
} }
// we use a collection to help with deduplication before raising to higher level processing // we use a collection to help with deduplication before raising to higher level processing
@ -52,25 +53,28 @@ func parseWorkflowForWorkflowUsage(_ context.Context, _ file.Resolver, _ *generi
for _, job := range wf.Jobs { for _, job := range wf.Jobs {
if job.Uses != "" { if job.Uses != "" {
p := newPackageFromUsageStatement(job.Uses, reader.Location) p, err := newPackageFromUsageStatement(job.Uses, reader.Location)
if err != nil {
errs = unknown.Append(errs, reader, err)
}
if p != nil { if p != nil {
pkgs.Add(*p) pkgs.Add(*p)
} }
} }
} }
return pkgs.Sorted(), nil, nil return pkgs.Sorted(), nil, errs
} }
func parseWorkflowForActionUsage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { func parseWorkflowForActionUsage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
contents, err := io.ReadAll(reader) contents, errs := io.ReadAll(reader)
if err != nil { if errs != nil {
return nil, nil, fmt.Errorf("unable to read yaml workflow file: %w", err) return nil, nil, fmt.Errorf("unable to read yaml workflow file: %w", errs)
} }
var wf workflowDef var wf workflowDef
if err = yaml.Unmarshal(contents, &wf); err != nil { if errs = yaml.Unmarshal(contents, &wf); errs != nil {
return nil, nil, fmt.Errorf("unable to parse yaml workflow file: %w", err) return nil, nil, fmt.Errorf("unable to parse yaml workflow file: %w", errs)
} }
// we use a collection to help with deduplication before raising to higher level processing // we use a collection to help with deduplication before raising to higher level processing
@ -81,12 +85,15 @@ func parseWorkflowForActionUsage(_ context.Context, _ file.Resolver, _ *generic.
if step.Uses == "" { if step.Uses == "" {
continue continue
} }
p := newPackageFromUsageStatement(step.Uses, reader.Location) p, err := newPackageFromUsageStatement(step.Uses, reader.Location)
if err != nil {
errs = unknown.Append(errs, reader, err)
}
if p != nil { if p != nil {
pkgs.Add(*p) pkgs.Add(*p)
} }
} }
} }
return pkgs.Sorted(), nil, nil return pkgs.Sorted(), nil, errs
} }

View File

@ -86,3 +86,17 @@ func Test_parseWorkflowForWorkflowUsage(t *testing.T) {
var expectedRelationships []artifact.Relationship var expectedRelationships []artifact.Relationship
pkgtest.TestFileParser(t, fixture, parseWorkflowForWorkflowUsage, expected, expectedRelationships) pkgtest.TestFileParser(t, fixture, parseWorkflowForWorkflowUsage, expected, expectedRelationships)
} }
func Test_corruptActionWorkflow(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/corrupt/workflow-multi-job.yaml").
WithError().
TestParser(t, parseWorkflowForActionUsage)
}
func Test_corruptWorkflowWorkflow(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/corrupt/workflow-multi-job.yaml").
WithError().
TestParser(t, parseWorkflowForWorkflowUsage)
}

View File

@ -0,0 +1,13 @@
name: "Bootstrap"
description: "Bootstrap all tools and dependencies"
ints:
go-version:
descrapt-packages:
description: "Space delimited list of tools to install via apt"
default: "libxml2-utils"
rns:
us all cache fingerprints
shell: bash
run: make fingerprints

View File

@ -0,0 +1,16 @@
name: "Validations"
on:
workflow_dispatch:
pull_request:
push:
branches:
- main
jbs
Statnapshot
key: snapshot-build-${{ github.run_id }}
- name: Run CLI Tests (Linux)
run: make cli

View File

@ -66,9 +66,9 @@ func (c *goBinaryCataloger) parseGoBinary(_ context.Context, resolver file.Resol
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
defer internal.CloseAndLogError(reader.ReadCloser, reader.RealPath)
mods := scanFile(unionReader, reader.RealPath) mods, errs := scanFile(reader.Location, unionReader)
internal.CloseAndLogError(reader.ReadCloser, reader.RealPath)
var rels []artifact.Relationship var rels []artifact.Relationship
for _, mod := range mods { for _, mod := range mods {
@ -81,7 +81,7 @@ func (c *goBinaryCataloger) parseGoBinary(_ context.Context, resolver file.Resol
pkgs = append(pkgs, depPkgs...) pkgs = append(pkgs, depPkgs...)
} }
return pkgs, rels, nil return pkgs, rels, errs
} }
func createModuleRelationships(main pkg.Package, deps []pkg.Package) []artifact.Relationship { func createModuleRelationships(main pkg.Package, deps []pkg.Package) []artifact.Relationship {

View File

@ -157,3 +157,11 @@ func Test_GoSumHashes(t *testing.T) {
}) })
} }
} }
func Test_corruptGoMod(t *testing.T) {
c := NewGoModuleFileCataloger(DefaultCatalogerConfig().WithSearchRemoteLicenses(false))
pkgtest.NewCatalogTester().
FromDirectory(t, "test-fixtures/corrupt").
WithError().
TestCataloger(t, c)
}

View File

@ -9,6 +9,8 @@ import (
"github.com/kastenhq/goversion/version" "github.com/kastenhq/goversion/version"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/unionreader" "github.com/anchore/syft/syft/internal/unionreader"
) )
@ -19,20 +21,21 @@ type extendedBuildInfo struct {
} }
// scanFile scans file to try to report the Go and module versions. // scanFile scans file to try to report the Go and module versions.
func scanFile(reader unionreader.UnionReader, filename string) []*extendedBuildInfo { func scanFile(location file.Location, reader unionreader.UnionReader) ([]*extendedBuildInfo, error) {
// NOTE: multiple readers are returned to cover universal binaries, which are files // NOTE: multiple readers are returned to cover universal binaries, which are files
// with more than one binary // with more than one binary
readers, err := unionreader.GetReaders(reader) readers, errs := unionreader.GetReaders(reader)
if err != nil { if errs != nil {
log.WithFields("error", err).Warnf("failed to open a golang binary") log.WithFields("error", errs).Warnf("failed to open a golang binary")
return nil return nil, fmt.Errorf("failed to open a golang binary: %w", errs)
} }
var builds []*extendedBuildInfo var builds []*extendedBuildInfo
for _, r := range readers { for _, r := range readers {
bi, err := getBuildInfo(r) bi, err := getBuildInfo(r)
if err != nil { if err != nil {
log.WithFields("file", filename, "error", err).Trace("unable to read golang buildinfo") log.WithFields("file", location.RealPath, "error", err).Trace("unable to read golang buildinfo")
continue continue
} }
// it's possible the reader just isn't a go binary, in which case just skip it // it's possible the reader just isn't a go binary, in which case just skip it
@ -42,23 +45,25 @@ func scanFile(reader unionreader.UnionReader, filename string) []*extendedBuildI
v, err := getCryptoInformation(r) v, err := getCryptoInformation(r)
if err != nil { if err != nil {
log.WithFields("file", filename, "error", err).Trace("unable to read golang version info") log.WithFields("file", location.RealPath, "error", err).Trace("unable to read golang version info")
// don't skip this build info. // don't skip this build info.
// we can still catalog packages, even if we can't get the crypto information // we can still catalog packages, even if we can't get the crypto information
errs = unknown.Appendf(errs, location, "unable to read golang version info: %w", err)
} }
arch := getGOARCH(bi.Settings) arch := getGOARCH(bi.Settings)
if arch == "" { if arch == "" {
arch, err = getGOARCHFromBin(r) arch, err = getGOARCHFromBin(r)
if err != nil { if err != nil {
log.WithFields("file", filename, "error", err).Trace("unable to read golang arch info") log.WithFields("file", location.RealPath, "error", err).Trace("unable to read golang arch info")
// don't skip this build info. // don't skip this build info.
// we can still catalog packages, even if we can't get the arch information // we can still catalog packages, even if we can't get the arch information
errs = unknown.Appendf(errs, location, "unable to read golang arch info: %w", err)
} }
} }
builds = append(builds, &extendedBuildInfo{BuildInfo: bi, cryptoSettings: v, arch: arch}) builds = append(builds, &extendedBuildInfo{BuildInfo: bi, cryptoSettings: v, arch: arch})
} }
return builds return builds, errs
} }
func getCryptoInformation(reader io.ReaderAt) ([]string, error) { func getCryptoInformation(reader io.ReaderAt) ([]string, error) {

View File

@ -0,0 +1,11 @@
module github.com/anchore/syft
go 1.18
ruire (
github.com/CycloneDX/cyclonedx-go v0.7.0
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
)
replace github.com/CycloneDX/cyclonedx-go => github.com/CycloneDX/cyclonedx-go v0.6.0

View File

@ -0,0 +1,4 @@
github.com/CycloneDX/cyclonedx-go v0.6.0/go.mod h1:nQCiF4Tvrg5Ieu8qPhYMvzPGMu5I7fANZkrSsJjl5mg=
github.com/Cycpansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github6IF
github.com/stretchr/test4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -9,6 +9,7 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -50,7 +51,7 @@ func parseStackLock(_ context.Context, _ file.Resolver, _ *generic.Environment,
if err := yaml.Unmarshal(bytes, &lockFile); err != nil { if err := yaml.Unmarshal(bytes, &lockFile); err != nil {
log.WithFields("error", err).Tracef("failed to parse stack.yaml.lock file %q", reader.RealPath) log.WithFields("error", err).Tracef("failed to parse stack.yaml.lock file %q", reader.RealPath)
return nil, nil, nil return nil, nil, fmt.Errorf("failed to parse stack.yaml.lock file")
} }
var ( var (
@ -82,7 +83,7 @@ func parseStackLock(_ context.Context, _ file.Resolver, _ *generic.Environment,
) )
} }
return pkgs, nil, nil return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
} }
func parseStackPackageEncoding(pkgEncoding string) (name, version, hash string) { func parseStackPackageEncoding(pkgEncoding string) (name, version, hash string) {

View File

@ -130,3 +130,10 @@ func TestParseStackLock(t *testing.T) {
pkgtest.TestFileParser(t, fixture, parseStackLock, expectedPkgs, expectedRelationships) pkgtest.TestFileParser(t, fixture, parseStackLock, expectedPkgs, expectedRelationships)
} }
func Test_corruptStackLock(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/corrupt/stack.yaml.lock").
WithError().
TestParser(t, parseStackLock)
}

View File

@ -8,6 +8,7 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -31,7 +32,7 @@ func parseStackYaml(_ context.Context, _ file.Resolver, _ *generic.Environment,
if err := yaml.Unmarshal(bytes, &stackFile); err != nil { if err := yaml.Unmarshal(bytes, &stackFile); err != nil {
log.WithFields("error", err).Tracef("failed to parse stack.yaml file %q", reader.RealPath) log.WithFields("error", err).Tracef("failed to parse stack.yaml file %q", reader.RealPath)
return nil, nil, nil return nil, nil, fmt.Errorf("failed to parse stack.yaml file")
} }
var pkgs []pkg.Package var pkgs []pkg.Package
@ -50,5 +51,5 @@ func parseStackYaml(_ context.Context, _ file.Resolver, _ *generic.Environment,
) )
} }
return pkgs, nil, nil return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
} }

View File

@ -110,3 +110,10 @@ func TestParseStackYaml(t *testing.T) {
pkgtest.TestFileParser(t, fixture, parseStackYaml, expectedPkgs, expectedRelationships) pkgtest.TestFileParser(t, fixture, parseStackYaml, expectedPkgs, expectedRelationships)
} }
func Test_corruptStackYaml(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/corrupt/stack.yaml").
WithError().
TestParser(t, parseStackYaml)
}

View File

@ -0,0 +1,6 @@
flag
extra-package-dbs: []
packa@sha256:fbcf49ecfc3d4da53e797fd0275264cba776ffa324ee223e2a3f4ec2d2c9c4a6,2165
- stm-2.5.0.2@sha256:e4dc6473faaa75fbd7eccab4e3ee1d651d75bb0e49946ef0b8b751ccde771a55,2314
ghc-options:
"$everything": -haddock

View File

@ -0,0 +1,8 @@
packages
-comp
al:
commit: a5847301404583e16d55cd4d051b8e605d704fbc
git: https://github.com/runtimeverification/haskell-backend.git
subdir: kore
snapshots
- complete//raw.github

View File

@ -48,7 +48,6 @@ type CatalogTester struct {
func NewCatalogTester() *CatalogTester { func NewCatalogTester() *CatalogTester {
return &CatalogTester{ return &CatalogTester{
wantErr: require.NoError,
locationComparer: cmptest.DefaultLocationComparer, locationComparer: cmptest.DefaultLocationComparer,
licenseComparer: cmptest.DefaultLicenseComparer, licenseComparer: cmptest.DefaultLicenseComparer,
packageStringer: stringPackage, packageStringer: stringPackage,
@ -113,7 +112,6 @@ func (p *CatalogTester) WithEnv(env *generic.Environment) *CatalogTester {
} }
func (p *CatalogTester) WithError() *CatalogTester { func (p *CatalogTester) WithError() *CatalogTester {
p.assertResultExpectations = true
p.wantErr = require.Error p.wantErr = require.Error
return p return p
} }
@ -226,7 +224,10 @@ func (p *CatalogTester) IgnoreUnfulfilledPathResponses(paths ...string) *Catalog
func (p *CatalogTester) TestParser(t *testing.T, parser generic.Parser) { func (p *CatalogTester) TestParser(t *testing.T, parser generic.Parser) {
t.Helper() t.Helper()
pkgs, relationships, err := parser(context.Background(), p.resolver, p.env, p.reader) pkgs, relationships, err := parser(context.Background(), p.resolver, p.env, p.reader)
p.wantErr(t, err) // only test for errors if explicitly requested
if p.wantErr != nil {
p.wantErr(t, err)
}
p.assertPkgs(t, pkgs, relationships) p.assertPkgs(t, pkgs, relationships)
} }
@ -247,8 +248,12 @@ func (p *CatalogTester) TestCataloger(t *testing.T, cataloger pkg.Cataloger) {
assert.ElementsMatchf(t, p.expectedContentQueries, resolver.AllContentQueries(), "unexpected content queries observed: diff %s", cmp.Diff(p.expectedContentQueries, resolver.AllContentQueries())) assert.ElementsMatchf(t, p.expectedContentQueries, resolver.AllContentQueries(), "unexpected content queries observed: diff %s", cmp.Diff(p.expectedContentQueries, resolver.AllContentQueries()))
} }
if p.assertResultExpectations { // only test for errors if explicitly requested
if p.wantErr != nil {
p.wantErr(t, err) p.wantErr(t, err)
}
if p.assertResultExpectations {
p.assertPkgs(t, pkgs, relationships) p.assertPkgs(t, pkgs, relationships)
} }
@ -256,7 +261,7 @@ func (p *CatalogTester) TestCataloger(t *testing.T, cataloger pkg.Cataloger) {
a(t, pkgs, relationships) a(t, pkgs, relationships)
} }
if !p.assertResultExpectations && len(p.customAssertions) == 0 { if !p.assertResultExpectations && len(p.customAssertions) == 0 && p.wantErr == nil {
resolver.PruneUnfulfilledPathResponses(p.ignoreUnfulfilledPathResponses, p.ignoreAnyUnfulfilledPaths...) resolver.PruneUnfulfilledPathResponses(p.ignoreUnfulfilledPathResponses, p.ignoreAnyUnfulfilledPaths...)
// if we aren't testing the results, we should focus on what was searched for (for glob-centric tests) // if we aren't testing the results, we should focus on what was searched for (for glob-centric tests)

View File

@ -16,6 +16,7 @@ import (
intFile "github.com/anchore/syft/internal/file" intFile "github.com/anchore/syft/internal/file"
"github.com/anchore/syft/internal/licenses" "github.com/anchore/syft/internal/licenses"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -133,14 +134,22 @@ func (j *archiveParser) parse(ctx context.Context) ([]pkg.Package, []artifact.Re
} }
pkgs = append(pkgs, auxPkgs...) pkgs = append(pkgs, auxPkgs...)
var errs error
if j.detectNested { if j.detectNested {
// find nested java archive packages // find nested java archive packages
nestedPkgs, nestedRelationships, err := j.discoverPkgsFromNestedArchives(ctx, parentPkg) nestedPkgs, nestedRelationships, err := j.discoverPkgsFromNestedArchives(ctx, parentPkg)
if err != nil { if err != nil {
return nil, nil, err errs = unknown.Append(errs, j.location, err)
} }
pkgs = append(pkgs, nestedPkgs...) pkgs = append(pkgs, nestedPkgs...)
relationships = append(relationships, nestedRelationships...) relationships = append(relationships, nestedRelationships...)
} else {
// .jar and .war files are present in archives, are others? or generally just consider them top-level?
nestedArchives := j.fileManifest.GlobMatch(true, "*.jar", "*.war")
if len(nestedArchives) > 0 {
slices.Sort(nestedArchives)
errs = unknown.Appendf(errs, j.location, "nested archives not cataloged: %v", strings.Join(nestedArchives, ", "))
}
} }
// lastly, add the parent package to the list (assuming the parent exists) // lastly, add the parent package to the list (assuming the parent exists)
@ -166,7 +175,11 @@ func (j *archiveParser) parse(ctx context.Context) ([]pkg.Package, []artifact.Re
p.SetID() p.SetID()
} }
return pkgs, relationships, nil if len(pkgs) == 0 {
errs = unknown.Appendf(errs, j.location, "no package identified in archive")
}
return pkgs, relationships, errs
} }
// discoverMainPackage parses the root Java manifest used as the parent package to all discovered nested packages. // discoverMainPackage parses the root Java manifest used as the parent package to all discovered nested packages.
@ -283,14 +296,14 @@ func (j *archiveParser) findLicenseFromJavaMetadata(ctx context.Context, groupID
if parsedPom != nil { if parsedPom != nil {
pomLicenses, err = j.maven.resolveLicenses(ctx, parsedPom.project) pomLicenses, err = j.maven.resolveLicenses(ctx, parsedPom.project)
if err != nil { if err != nil {
log.WithFields("error", err, "mavenID", j.maven.getMavenID(ctx, parsedPom.project)).Debug("error attempting to resolve pom licenses") log.WithFields("error", err, "mavenID", j.maven.getMavenID(ctx, parsedPom.project)).Trace("error attempting to resolve pom licenses")
} }
} }
if err == nil && len(pomLicenses) == 0 { if err == nil && len(pomLicenses) == 0 {
pomLicenses, err = j.maven.findLicenses(ctx, groupID, artifactID, version) pomLicenses, err = j.maven.findLicenses(ctx, groupID, artifactID, version)
if err != nil { if err != nil {
log.WithFields("error", err, "mavenID", mavenID{groupID, artifactID, version}).Debug("error attempting to find licenses") log.WithFields("error", err, "mavenID", mavenID{groupID, artifactID, version}).Trace("error attempting to find licenses")
} }
} }
@ -300,7 +313,7 @@ func (j *archiveParser) findLicenseFromJavaMetadata(ctx context.Context, groupID
groupID = strings.Join(packages[:len(packages)-1], ".") groupID = strings.Join(packages[:len(packages)-1], ".")
pomLicenses, err = j.maven.findLicenses(ctx, groupID, artifactID, version) pomLicenses, err = j.maven.findLicenses(ctx, groupID, artifactID, version)
if err != nil { if err != nil {
log.WithFields("error", err, "mavenID", mavenID{groupID, artifactID, version}).Debug("error attempting to find sub-group licenses") log.WithFields("error", err, "mavenID", mavenID{groupID, artifactID, version}).Trace("error attempting to find sub-group licenses")
} }
} }
@ -630,7 +643,7 @@ func newPackageFromMavenData(ctx context.Context, r *mavenResolver, pomPropertie
} }
if err != nil { if err != nil {
log.WithFields("error", err, "mavenID", mavenID{pomProperties.GroupID, pomProperties.ArtifactID, pomProperties.Version}).Debug("error attempting to resolve licenses") log.WithFields("error", err, "mavenID", mavenID{pomProperties.GroupID, pomProperties.ArtifactID, pomProperties.Version}).Trace("error attempting to resolve licenses")
} }
licenses := make([]pkg.License, 0) licenses := make([]pkg.License, 0)

View File

@ -91,10 +91,12 @@ func TestParseJar(t *testing.T) {
fixture string fixture string
expected map[string]pkg.Package expected map[string]pkg.Package
ignoreExtras []string ignoreExtras []string
wantErr require.ErrorAssertionFunc
}{ }{
{ {
name: "example-jenkins-plugin", name: "example-jenkins-plugin",
fixture: "test-fixtures/java-builds/packages/example-jenkins-plugin.hpi", fixture: "test-fixtures/java-builds/packages/example-jenkins-plugin.hpi",
wantErr: require.Error, // there are nested jars, which are not scanned and result in unknown errors
ignoreExtras: []string{ ignoreExtras: []string{
"Plugin-Version", // has dynamic date "Plugin-Version", // has dynamic date
"Built-By", // podman returns the real UID "Built-By", // podman returns the real UID
@ -153,6 +155,7 @@ func TestParseJar(t *testing.T) {
{ {
name: "example-java-app-gradle", name: "example-java-app-gradle",
fixture: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar", fixture: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar",
wantErr: require.NoError, // no nested jars
expected: map[string]pkg.Package{ expected: map[string]pkg.Package{
"example-java-app-gradle": { "example-java-app-gradle": {
Name: "example-java-app-gradle", Name: "example-java-app-gradle",
@ -226,6 +229,7 @@ func TestParseJar(t *testing.T) {
{ {
name: "example-java-app-maven", name: "example-java-app-maven",
fixture: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar", fixture: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar",
wantErr: require.NoError, // no nested jars
ignoreExtras: []string{ ignoreExtras: []string{
"Build-Jdk", // can't guarantee the JDK used at build time "Build-Jdk", // can't guarantee the JDK used at build time
"Built-By", // podman returns the real UID "Built-By", // podman returns the real UID
@ -351,13 +355,15 @@ func TestParseJar(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
actual, _, err := parser.parse(context.Background()) actual, _, err := parser.parse(context.Background())
require.NoError(t, err) if test.wantErr != nil {
test.wantErr(t, err)
}
if len(actual) != len(test.expected) { if len(actual) != len(test.expected) {
for _, a := range actual { for _, a := range actual {
t.Log(" ", a) t.Log(" ", a)
} }
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(test.expected)) t.Fatalf("unexpected package count; expected: %d got: %d", len(test.expected), len(actual))
} }
var parent *pkg.Package var parent *pkg.Package
@ -1488,3 +1494,11 @@ func run(t testing.TB, cmd *exec.Cmd) {
func ptr[T any](value T) *T { func ptr[T any](value T) *T {
return &value return &value
} }
func Test_corruptJarArchive(t *testing.T) {
ap := newGenericArchiveParserAdapter(DefaultArchiveCatalogerConfig())
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/corrupt/example.jar").
WithError().
TestParser(t, ap.parseJavaArchive)
}

View File

@ -79,7 +79,7 @@ func (r *mavenResolver) resolvePropertyValue(ctx context.Context, propertyValue
} }
resolved, err := r.resolveExpression(ctx, resolutionContext, *propertyValue, resolvingProperties) resolved, err := r.resolveExpression(ctx, resolutionContext, *propertyValue, resolvingProperties)
if err != nil { if err != nil {
log.WithFields("error", err, "propertyValue", *propertyValue).Debug("error resolving maven property") log.WithFields("error", err, "propertyValue", *propertyValue).Trace("error resolving maven property")
return "" return ""
} }
return resolved return resolved

View File

@ -15,6 +15,7 @@ import (
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -41,11 +42,13 @@ func (p pomXMLCataloger) Catalog(ctx context.Context, fileResolver file.Resolver
r := newMavenResolver(fileResolver, p.cfg) r := newMavenResolver(fileResolver, p.cfg)
var errs error
var poms []*gopom.Project var poms []*gopom.Project
for _, pomLocation := range locations { for _, pomLocation := range locations {
pom, err := readPomFromLocation(fileResolver, pomLocation) pom, err := readPomFromLocation(fileResolver, pomLocation)
if err != nil || pom == nil { if err != nil || pom == nil {
log.WithFields("error", err, "pomLocation", pomLocation).Debug("error while reading pom") log.WithFields("error", err, "pomLocation", pomLocation).Debug("error while reading pom")
errs = unknown.Appendf(errs, pomLocation, "error reading pom.xml: %w", err)
continue continue
} }
@ -60,7 +63,7 @@ func (p pomXMLCataloger) Catalog(ctx context.Context, fileResolver file.Resolver
for _, pom := range poms { for _, pom := range poms {
pkgs = append(pkgs, processPomXML(ctx, r, pom, r.pomLocations[pom])...) pkgs = append(pkgs, processPomXML(ctx, r, pom, r.pomLocations[pom])...)
} }
return pkgs, nil, nil return pkgs, nil, errs
} }
func readPomFromLocation(fileResolver file.Resolver, pomLocation file.Location) (*gopom.Project, error) { func readPomFromLocation(fileResolver file.Resolver, pomLocation file.Location) (*gopom.Project, error) {

View File

@ -705,3 +705,11 @@ func getCommonsTextExpectedPackages() []pkg.Package {
}, },
} }
} }
func Test_corruptPomXml(t *testing.T) {
c := NewPomCataloger(DefaultArchiveCatalogerConfig())
pkgtest.NewCatalogTester().
FromDirectory(t, "test-fixtures/corrupt").
WithError().
TestCataloger(t, c)
}

View File

@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
) )
func Test_parseTarWrappedJavaArchive(t *testing.T) { func Test_parseTarWrappedJavaArchive(t *testing.T) {
@ -57,3 +58,11 @@ func Test_parseTarWrappedJavaArchive(t *testing.T) {
}) })
} }
} }
func Test_corruptTarArchive(t *testing.T) {
ap := newGenericTarWrappedJavaArchiveParser(DefaultArchiveCatalogerConfig())
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/corrupt/example.tar").
WithError().
TestParser(t, ap.parseTarWrappedJavaArchive)
}

View File

@ -0,0 +1 @@
example archive

View File

@ -0,0 +1 @@
example archive

View File

@ -0,0 +1 @@
<this is not a valid pom xml>

View File

@ -11,7 +11,6 @@ import (
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -64,11 +63,8 @@ func parsePackageJSON(_ context.Context, _ file.Resolver, _ *generic.Environment
return nil, nil, fmt.Errorf("failed to parse package.json file: %w", err) return nil, nil, fmt.Errorf("failed to parse package.json file: %w", err)
} }
if !p.hasNameAndVersionValues() { // always create a package, regardless of having a valid name and/or version,
log.Debugf("encountered package.json file without a name and/or version field, ignoring (path=%q)", reader.Path()) // a compliance filter later will remove these packages based on compliance rules
return nil, nil, nil
}
pkgs = append( pkgs = append(
pkgs, pkgs,
newPackageJSONPackage(p, reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), newPackageJSONPackage(p, reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
@ -203,10 +199,6 @@ func licensesFromJSON(b []byte) ([]npmPackageLicense, error) {
return nil, errors.New("unmarshal failed") return nil, errors.New("unmarshal failed")
} }
func (p packageJSON) hasNameAndVersionValues() bool {
return p.Name != "" && p.Version != ""
}
// this supports both windows and unix paths // this supports both windows and unix paths
var filepathSeparator = regexp.MustCompile(`[\\/]`) var filepathSeparator = regexp.MustCompile(`[\\/]`)

View File

@ -210,10 +210,28 @@ func TestParsePackageJSON(t *testing.T) {
} }
} }
func Test_corruptPackageJSON(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/corrupt/package.json").
WithError().
TestParser(t, parsePackageJSON)
}
func TestParsePackageJSON_Partial(t *testing.T) { // see https://github.com/anchore/syft/issues/311 func TestParsePackageJSON_Partial(t *testing.T) { // see https://github.com/anchore/syft/issues/311
const fixtureFile = "test-fixtures/pkg-json/package-partial.json" const fixtureFile = "test-fixtures/pkg-json/package-partial.json"
pkgtest.TestFileParser(t, fixtureFile, parsePackageJSON, nil, nil) // raise package.json files as packages with any information we find, these will be filtered out
// according to compliance rules later
expectedPkgs := []pkg.Package{
{
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
PURL: packageURL("", ""),
Metadata: pkg.NpmPackage{},
Locations: file.NewLocationSet(file.NewLocation(fixtureFile)),
},
}
pkgtest.TestFileParser(t, fixtureFile, parsePackageJSON, expectedPkgs, nil)
} }
func Test_pathContainsNodeModulesDirectory(t *testing.T) { func Test_pathContainsNodeModulesDirectory(t *testing.T) {

View File

@ -9,6 +9,7 @@ import (
"strings" "strings"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -100,7 +101,7 @@ func (a genericPackageLockAdapter) parsePackageLock(_ context.Context, resolver
pkg.Sort(pkgs) pkg.Sort(pkgs)
return pkgs, nil, nil return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
} }
func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) { func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) {

View File

@ -333,3 +333,11 @@ func TestParsePackageLockLicenseWithArray(t *testing.T) {
adapter := newGenericPackageLockAdapter(CatalogerConfig{}) adapter := newGenericPackageLockAdapter(CatalogerConfig{})
pkgtest.TestFileParser(t, fixture, adapter.parsePackageLock, expectedPkgs, expectedRelationships) pkgtest.TestFileParser(t, fixture, adapter.parsePackageLock, expectedPkgs, expectedRelationships)
} }
func Test_corruptPackageLock(t *testing.T) {
gap := newGenericPackageLockAdapter(DefaultCatalogerConfig())
pkgtest.NewCatalogTester().
FromFile(t, "test-fixtures/corrupt/package-lock.json").
WithError().
TestParser(t, gap.parsePackageLock)
}

Some files were not shown because too many files have changed in this diff Show More