Add support for parsing .NET assemblies (#1943)

* Add support for parsing .NET assemblies

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

Former-commit-id: 69c33fe4d77357d843c11590f3b07825bc6249ac

* Add dll and exe files

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

Former-commit-id: b9d204efa6d2ef385b5fbb7a59a3474ecabea641

* Add PE cataloger to directory catalogers

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

Former-commit-id: 9711c00d9da92e2887e0c1f92edd740ea5345849

* Don't set language to dotnet for PEs

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

Former-commit-id: 368313fddac9160d8a06a01ebe8c5ac7990232f5

* Fix spelling of cataloger in constructor

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

Former-commit-id: e42fd77b2f8b6d42e076a84f6cce386861260941

* Adjust which cases in PE parsing return errors

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

Former-commit-id: 95b25f8fc3a7d4e18fe30e489b09851f316795ff

* remove build binary from branch

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

Former-commit-id: fa54c0d0aef0998d5520e9f44cae51f5f9cd38a2

* Fix failing CLI tests

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

---------

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Dan Luhring
2023-07-19 15:34:07 -04:00
committed by GitHub
co-authored by Alex Goodman
parent 0327fdc88a
commit 8478e0bef7
17 changed files with 2167 additions and 80 deletions
+3 -1
View File
@@ -45,7 +45,7 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger {
apkdb.NewApkdbCataloger(),
binary.NewCataloger(),
deb.NewDpkgdbCataloger(),
dotnet.NewDotnetDepsCataloger(),
dotnet.NewDotnetPortableExecutableCataloger(),
golang.NewGoModuleBinaryCataloger(cfg.Go()),
java.NewJavaCataloger(cfg.Java()),
java.NewNativeImageCataloger(),
@@ -71,6 +71,7 @@ func DirectoryCatalogers(cfg Config) []pkg.Cataloger {
dart.NewPubspecLockCataloger(),
deb.NewDpkgdbCataloger(),
dotnet.NewDotnetDepsCataloger(),
dotnet.NewDotnetPortableExecutableCataloger(),
elixir.NewMixLockCataloger(),
erlang.NewRebarLockCataloger(),
golang.NewGoModFileCataloger(cfg.Go()),
@@ -105,6 +106,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
dart.NewPubspecLockCataloger(),
deb.NewDpkgdbCataloger(),
dotnet.NewDotnetDepsCataloger(),
dotnet.NewDotnetPortableExecutableCataloger(),
elixir.NewMixLockCataloger(),
erlang.NewRebarLockCataloger(),
golang.NewGoModFileCataloger(cfg.Go()),
+6 -3
View File
@@ -4,10 +4,13 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
const catalogerName = "dotnet-deps-cataloger"
// NewDotnetDepsCataloger returns a new Dotnet cataloger object base on deps json files.
func NewDotnetDepsCataloger() *generic.Cataloger {
return generic.NewCataloger(catalogerName).
return generic.NewCataloger("dotnet-deps-cataloger").
WithParserByGlobs(parseDotnetDeps, "**/*.deps.json")
}
func NewDotnetPortableExecutableCataloger() *generic.Cataloger {
return generic.NewCataloger("dotnet-portable-executable-cataloger").
WithParserByGlobs(parseDotnetPortableExecutable, "**/*.dll", "**/*.exe")
}
+18 -6
View File
@@ -3,22 +3,34 @@ package dotnet
import (
"testing"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func TestCataloger_Globs(t *testing.T) {
tests := []struct {
name string
fixture string
expected []string
name string
fixture string
cataloger *generic.Cataloger
expected []string
}{
{
name: "obtain deps.json files",
fixture: "test-fixtures/glob-paths",
name: "obtain deps.json files",
fixture: "test-fixtures/glob-paths",
cataloger: NewDotnetDepsCataloger(),
expected: []string{
"src/something.deps.json",
},
},
{
name: "obtain portable executable files",
fixture: "test-fixtures/glob-paths",
cataloger: NewDotnetPortableExecutableCataloger(),
expected: []string{
"src/something.dll",
"src/something.exe",
},
},
}
for _, test := range tests {
@@ -26,7 +38,7 @@ func TestCataloger_Globs(t *testing.T) {
pkgtest.NewCatalogTester().
FromDirectory(t, test.fixture).
ExpectsResolverContentQueries(test.expected).
TestCataloger(t, NewDotnetDepsCataloger())
TestCataloger(t, test.cataloger)
})
}
}
@@ -0,0 +1,87 @@
package dotnet
import (
"fmt"
"io"
"github.com/saferwall/pe"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
var _ generic.Parser = parseDotnetPortableExecutable
func parseDotnetPortableExecutable(_ file.Resolver, _ *generic.Environment, f file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
by, err := io.ReadAll(f)
if err != nil {
return nil, nil, fmt.Errorf("unable to read file: %w", err)
}
peFile, err := pe.NewBytes(by, &pe.Options{})
if err != nil {
return nil, nil, fmt.Errorf("unable to create PE file instance: %w", err)
}
err = peFile.Parse()
if err != nil {
return nil, nil, fmt.Errorf("unable to parse PE file: %w", err)
}
versionResources, err := peFile.ParseVersionResources()
if err != nil {
// this is not a fatal error, just log and continue
// TODO: consider this case for "known unknowns" (same goes for cases below)
log.Tracef("unable to parse version resources in PE file: %s", f.RealPath)
return nil, nil, nil
}
name := versionResources["FileDescription"]
if name == "" {
log.Tracef("unable to find FileDescription in PE file: %s", f.RealPath)
return nil, nil, nil
}
version := versionResources["FileVersion"]
if version == "" {
log.Tracef("unable to find FileVersion in PE file: %s", f.RealPath)
return nil, nil, nil
}
purl := packageurl.NewPackageURL(
packageurl.TypeNuget, // See explanation in syft/pkg/cataloger/dotnet/package.go as to why this was chosen.
"",
name,
version,
nil,
"",
).ToString()
metadata := pkg.DotnetPortableExecutableMetadata{
AssemblyVersion: versionResources["Assembly Version"],
LegalCopyright: versionResources["LegalCopyright"],
Comments: versionResources["Comments"],
InternalName: versionResources["InternalName"],
CompanyName: versionResources["CompanyName"],
ProductName: versionResources["ProductName"],
ProductVersion: versionResources["ProductVersion"],
}
p := pkg.Package{
Name: name,
Version: version,
Locations: file.NewLocationSet(f.Location),
Type: pkg.DotnetPkg,
PURL: purl,
MetadataType: pkg.DotnetPortableExecutableMetadataType,
Metadata: metadata,
}
p.SetID()
return []pkg.Package{p}, nil, nil
}
@@ -0,0 +1,38 @@
package dotnet
import (
"testing"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func TestParseDotnetPortableExecutable(t *testing.T) {
fixture := "test-fixtures/System.Buffers.dll"
fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture))
expected := []pkg.Package{
{
Name: "System.Buffers",
Version: "7.0.923.36201",
Locations: fixtureLocationSet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/System.Buffers@7.0.923.36201",
MetadataType: pkg.DotnetPortableExecutableMetadataType,
Metadata: pkg.DotnetPortableExecutableMetadata{
AssemblyVersion: "7.0.0.0",
LegalCopyright: "© Microsoft Corporation. All rights reserved.",
Comments: "System.Buffers",
InternalName: "System.Buffers.dll",
CompanyName: "Microsoft Corporation",
ProductName: "Microsoft® .NET",
ProductVersion: "7.0.9+8e9a17b2216f51a5788f8b1c467a4cf3b769e7d7",
},
},
}
var expectedRelationships []artifact.Relationship
pkgtest.TestFileParser(t, fixture, parseDotnetPortableExecutable, expected, expectedRelationships)
}
@@ -0,0 +1,2 @@
!*.dll
!*.exe
@@ -0,0 +1 @@
bogus .dll (portable executable)
@@ -0,0 +1 @@
bogus .exe portable executable)