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
co-authored by Alex Goodman
parent 4d7ed9f749
commit ccbee94b87
145 changed files with 4420 additions and 233 deletions
@@ -6,8 +6,10 @@ package binary
import (
"context"
"encoding/json"
"fmt"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"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) {
var packages []pkg.Package
var relationships []artifact.Relationship
var errs error
for _, cls := range c.classifiers {
log.WithFields("classifier", cls.Class).Trace("cataloging binaries")
newPkgs, err := catalog(resolver, cls)
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
}
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
@@ -98,6 +102,7 @@ func mergePackages(target *pkg.Package, extra *pkg.Package) {
}
func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, err error) {
var errs error
locations, err := resolver.FilesByGlob(cls.FileGlob)
if err != nil {
return nil, err
@@ -105,11 +110,12 @@ func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, er
for _, location := range locations {
pkgs, err := cls.EvidenceMatcher(cls, matcherContext{resolver: resolver, location: location})
if err != nil {
return nil, err
errs = unknown.Append(errs, location, err)
continue
}
packages = append(packages, pkgs...)
}
return packages, nil
return packages, errs
}
// packagesMatch returns true if the binary packages "match" based on basic criteria
@@ -1668,7 +1668,7 @@ func Test_Cataloger_ResilientToErrors(t *testing.T) {
resolver := &panicyResolver{}
_, _, err := c.Catalog(context.Background(), resolver)
assert.NoError(t, err)
assert.Error(t, err)
assert.True(t, resolver.searchCalled)
}
@@ -11,6 +11,7 @@ import (
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/mimetype"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"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) {
var errs error
locations, err := resolver.FilesByMIMEType(mimetype.ExecutableMIMETypeSet.List()...)
if err != nil {
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 {
notes, key, err := parseElfPackageNotes(resolver, location, c)
if err != nil {
return nil, nil, err
errs = unknown.Append(errs, location, err)
continue
}
if notes == nil {
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
// 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.
return pkgs, nil, nil
return pkgs, nil, errs
}
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 {
log.WithFields("file", location.Path(), "error", err).Trace("unable to parse ELF notes")
return nil, elfPackageKey{}, nil
return nil, elfPackageKey{}, err
}
if notes == nil {
@@ -173,7 +176,7 @@ func getELFNotes(r file.LocationReadCloser) (*elfBinaryPackageNotes, error) {
if len(notes) > headerSize {
var metadata elfBinaryPackageNotes
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
}
log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to unmarshal ELF package notes as JSON")
@@ -3,6 +3,8 @@ package binary
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
@@ -14,6 +16,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
name string
fixture string
expected []pkg.Package
wantErr require.ErrorAssertionFunc
}{
{
name: "go case",
@@ -63,6 +66,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
},
},
},
wantErr: require.Error,
},
{
name: "fedora 64 bit binaries",
@@ -116,6 +120,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
WithImageResolver(t, v.fixture).
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
Expects(v.expected, nil).
WithErrorAssertion(v.wantErr).
TestCataloger(t, NewELFPackageCataloger())
})
}
@@ -5,9 +5,11 @@ RUN dnf update -y; \
dnf clean all
RUN mkdir -p /usr/local/bin/elftests/elfbinwithnestedlib
RUN mkdir -p /usr/local/bin/elftests/elfbinwithsisterlib
RUN mkdir -p /usr/local/bin/elftests/elfbinwithcorrupt
COPY ./elfbinwithnestedlib /usr/local/bin/elftests/elfbinwithnestedlib
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
@@ -16,6 +18,8 @@ RUN make
WORKDIR /usr/local/bin/elftests/elfbinwithsisterlib
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
FROM busybox:1.36.1-musl
@@ -0,0 +1,6 @@
#include <iostream>
#include "hello_world.h"
void print_hello_world() {
std::cout << "Hello, World!" << std::endl;
}
@@ -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
@@ -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
@@ -0,0 +1,8 @@
#include "hello_world.h"
int main() {
// Call the function from the shared library
print_hello_world();
return 0;
}
@@ -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