mirror of
https://github.com/anchore/syft.git
synced 2026-02-12 02:26:42 +01:00
Detect embedded deps.json in .NET binaries (#4375)
* syft detect embedded deps.json,dotnet , fixed #4344 Signed-off-by: Rez Moss <hi@rezmoss.com> * [wip] have pe utils process embedded dep.json Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * [wip] add PoC bundler processing Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * [wip] search for bundle marker within pe sections Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * put bundle parsing for multiple .net versions under test Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Rez Moss <hi@rezmoss.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:
parent
2c97ff1b24
commit
c79a57b6a1
@ -489,6 +489,40 @@ func TestCataloger(t *testing.T) {
|
|||||||
"netstandard @ 8.0.1425.11118 (/app/netstandard.dll)",
|
"netstandard @ 8.0.1425.11118 (/app/netstandard.dll)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assertAllDepEntriesInEmbeddedExecutable := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
|
||||||
|
t.Helper()
|
||||||
|
for _, p := range pkgs {
|
||||||
|
// assert that all packages DO NOT have an executable associated with it
|
||||||
|
m, ok := p.Metadata.(pkg.DotnetDepsEntry)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected metadata to be of type DotnetDepsEntry")
|
||||||
|
}
|
||||||
|
if len(m.Executables) != 0 {
|
||||||
|
t.Errorf("expected no executables for package %s, found %d", p.Name, len(m.Executables))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := extractMatchingPackage(t, "Newtonsoft.Json", pkgs)
|
||||||
|
expected := pkg.Package{
|
||||||
|
Name: "Newtonsoft.Json",
|
||||||
|
Version: "13.0.3",
|
||||||
|
Locations: file.NewLocationSet(file.NewLocation("/app/dotnetapp.exe")), // important! not this is an exe
|
||||||
|
Language: pkg.Dotnet,
|
||||||
|
Type: pkg.DotnetPkg,
|
||||||
|
PURL: "pkg:nuget/Newtonsoft.Json@13.0.3",
|
||||||
|
Metadata: pkg.DotnetDepsEntry{
|
||||||
|
Name: "Newtonsoft.Json",
|
||||||
|
Version: "13.0.3",
|
||||||
|
Path: "newtonsoft.json/13.0.3",
|
||||||
|
Sha512: "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==",
|
||||||
|
HashPath: "newtonsoft.json.13.0.3.nupkg.sha512",
|
||||||
|
Executables: nil, // important!
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgtest.AssertPackagesEqualIgnoreLayers(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
assertAllDepEntriesWithoutExecutables := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
|
assertAllDepEntriesWithoutExecutables := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for _, p := range pkgs {
|
for _, p := range pkgs {
|
||||||
@ -895,12 +929,163 @@ func TestCataloger(t *testing.T) {
|
|||||||
name: "combined cataloger (single file)",
|
name: "combined cataloger (single file)",
|
||||||
fixture: "image-net8-app-single-file",
|
fixture: "image-net8-app-single-file",
|
||||||
cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()),
|
cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()),
|
||||||
|
expectedPkgs: []string{"Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
// important: no relationships should be found
|
// extracted libraries from the embedded deps.json
|
||||||
expectedPkgs: []string{
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
"dotnetapp @ 1.0.0.0 (/app/dotnetapp.exe)",
|
"Humanizer.Core.af @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.ar @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.az @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.bg @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.bn-BD @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.cs @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.da @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.de @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.el @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.es @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.fa @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.fi-FI @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.fr @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.fr-BE @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.he @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.hr @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.hu @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.hy @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.id @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.is @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.it @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.ja @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.ko-KR @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.ku @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.lv @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.ms-MY @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.mt @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.nb @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.nb-NO @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.nl @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.pl @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.pt @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.ro @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.ru @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.sk @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.sl @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.sr @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.sr-Latn @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.sv @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.th-TH @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.tr @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.uk @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.uz-Cyrl-UZ @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.uz-Latn-UZ @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.vi @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.zh-CN @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.zh-Hans @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.zh-Hant @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Newtonsoft.Json @ 13.0.3 (/app/dotnetapp.exe)",
|
||||||
|
"dotnetapp @ 1.0.0 (/app/dotnetapp.exe)",
|
||||||
|
"runtimepack.Microsoft.NETCore.App.Runtime.win-x64 @ 8.0.14 (/app/dotnetapp.exe)",
|
||||||
},
|
},
|
||||||
assertion: assertSingleFileDeployment,
|
expectedRels: []string{
|
||||||
|
"Humanizer @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.af @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.ar @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.az @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.bg @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.bn-BD @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.cs @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.da @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.de @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.el @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.es @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.fa @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.fi-FI @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.fr @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.fr-BE @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.he @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.hr @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.hu @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.hy @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.id @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.is @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.it @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.ja @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.ko-KR @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.ku @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.lv @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.ms-MY @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.mt @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.nb @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.nb-NO @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.nl @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.pl @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.pt @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.ro @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.ru @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.sk @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.sl @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.sr @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.sr-Latn @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.sv @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.th-TH @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.tr @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.uk @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.uz-Cyrl-UZ @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.uz-Latn-UZ @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.vi @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.zh-CN @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.zh-Hans @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer.Core.zh-Hant @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.af @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.ar @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.az @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.bg @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.bn-BD @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.cs @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.da @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.de @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.el @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.es @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.fa @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.fi-FI @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.fr @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.fr-BE @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.he @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.hr @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.hu @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.hy @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.id @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.is @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.it @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.ja @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.ko-KR @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.ku @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.lv @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.ms-MY @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.mt @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.nb @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.nb-NO @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.nl @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.pl @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.pt @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.ro @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.ru @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.sk @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.sl @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.sr @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.sr-Latn @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.sv @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.th-TH @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.tr @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.uk @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.uz-Cyrl-UZ @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.uz-Latn-UZ @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.vi @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.zh-CN @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.zh-Hans @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Humanizer.Core.zh-Hant @ 2.14.1 (/app/dotnetapp.exe) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.exe)",
|
||||||
|
"Newtonsoft.Json @ 13.0.3 (/app/dotnetapp.exe) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.exe)",
|
||||||
|
"runtimepack.Microsoft.NETCore.App.Runtime.win-x64 @ 8.0.14 (/app/dotnetapp.exe) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.exe)",
|
||||||
|
},
|
||||||
|
assertion: assertAllDepEntriesInEmbeddedExecutable,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "pe cataloger (single file)",
|
name: "pe cataloger (single file)",
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package dotnet
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
@ -147,6 +148,18 @@ func isRuntimePackageLocation(loc file.Location) (string, bool) {
|
|||||||
|
|
||||||
// partitionPEs pairs PE files with the deps.json based on directory containment.
|
// partitionPEs pairs PE files with the deps.json based on directory containment.
|
||||||
func partitionPEs(depJsons []logicalDepsJSON, peFiles []logicalPE) ([]logicalDepsJSON, []logicalPE, []logicalDepsJSON) {
|
func partitionPEs(depJsons []logicalDepsJSON, peFiles []logicalPE) ([]logicalDepsJSON, []logicalPE, []logicalDepsJSON) {
|
||||||
|
// if there are any embedded deps.json files in PE files, extract them and add them to the list of deps.json files to process.
|
||||||
|
consideredPEs := file.NewCoordinateSet()
|
||||||
|
for _, pe := range peFiles {
|
||||||
|
if pe.EmbeddedDepsJSON != "" {
|
||||||
|
dep := extractEmbeddedDeps(pe)
|
||||||
|
if dep != nil {
|
||||||
|
depJsons = append(depJsons, *dep)
|
||||||
|
consideredPEs.Add(pe.Location.Coordinates) // mark this PE as already considered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// sort deps.json paths from longest to shortest. This is so we are processing the most specific match first.
|
// sort deps.json paths from longest to shortest. This is so we are processing the most specific match first.
|
||||||
sort.Slice(depJsons, func(i, j int) bool {
|
sort.Slice(depJsons, func(i, j int) bool {
|
||||||
return depJsons[i].Location.RealPath > depJsons[j].Location.RealPath
|
return depJsons[i].Location.RealPath > depJsons[j].Location.RealPath
|
||||||
@ -170,7 +183,9 @@ func partitionPEs(depJsons []logicalDepsJSON, peFiles []logicalPE) ([]logicalDep
|
|||||||
// across multiple deps.json files.
|
// across multiple deps.json files.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
// if we did not find a deps.json to associate this PE with, keep track of it for later processing.
|
||||||
|
// also, if we have already considered this PE because it had an embedded deps.json, skip it.
|
||||||
|
if !found && !consideredPEs.Contains(pe.Location.Coordinates) {
|
||||||
remainingPeFiles = append(remainingPeFiles, pe)
|
remainingPeFiles = append(remainingPeFiles, pe)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -489,3 +504,14 @@ func readPEFile(resolver file.Resolver, loc file.Location) (*logicalPE, error) {
|
|||||||
|
|
||||||
return ldpe, nil
|
return ldpe, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractEmbeddedDeps(pe logicalPE) *logicalDepsJSON {
|
||||||
|
doc, err := newDepsJSON(file.NewLocationReadCloser(pe.Location, io.NopCloser(strings.NewReader(pe.EmbeddedDepsJSON))))
|
||||||
|
if err != nil || doc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.Location = pe.Location
|
||||||
|
lDoc := getLogicalDepsJSON(*doc, nil)
|
||||||
|
return &lDoc
|
||||||
|
}
|
||||||
|
|||||||
259
syft/pkg/cataloger/internal/pe/bundle.go
Normal file
259
syft/pkg/cataloger/internal/pe/bundle.go
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
package pe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"debug/pe"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dotNetBundleSignature is the SHA-256 hash of ".net core bundle" used to identify single-file bundles.
|
||||||
|
var dotNetBundleSignature = []byte{
|
||||||
|
0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38,
|
||||||
|
0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32,
|
||||||
|
0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18,
|
||||||
|
0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae,
|
||||||
|
}
|
||||||
|
|
||||||
|
// dotNetBundleHeader represents the fixed portion of the bundle header (version 1+)
|
||||||
|
type dotNetBundleHeader struct {
|
||||||
|
MajorVersion uint32
|
||||||
|
MinorVersion uint32
|
||||||
|
NumEmbeddedFiles int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// dotNetBundleHeaderV2 represents additional fields in V2+ bundles (.NET 5+)
|
||||||
|
type dotNetBundleHeaderV2 struct {
|
||||||
|
DepsJSONOffset int64
|
||||||
|
DepsJSONSize int64
|
||||||
|
RuntimeConfigJSONOffset int64
|
||||||
|
RuntimeConfigJSONSize int64
|
||||||
|
Flags uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// dotNetFileType represents the type of bundled file in the manifest
|
||||||
|
type dotNetFileType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
dotNetFileTypeUnknown dotNetFileType = iota
|
||||||
|
dotNetFileTypeAssembly
|
||||||
|
dotNetFileTypeNativeBinary
|
||||||
|
dotNetFileTypeDepsJSON
|
||||||
|
dotNetFileTypeRuntimeConfigJSON
|
||||||
|
dotNetFileTypeSymbols
|
||||||
|
)
|
||||||
|
|
||||||
|
// extractDepsJSONFromBundle searches for an embedded deps.json file in a .NET single-file bundle.
|
||||||
|
// When built with PublishSingleFile=true, .NET embeds the application and all dependencies into
|
||||||
|
// the AppHost executable. The bundle marker (8-byte header offset + 32-byte signature) is placed
|
||||||
|
// in a placeholder location within the PE structure, pointing to the bundle header which contains
|
||||||
|
// file entry metadata. For V2+ bundles (.NET 5+), the header includes direct offsets to deps.json;
|
||||||
|
// for V1 bundles (.NET Core 3.x), we parse the manifest to locate it.
|
||||||
|
//
|
||||||
|
// ┌──────────────────────────────────┐
|
||||||
|
// │ PE AppHost Binary │ Standard PE structure
|
||||||
|
// │ ... │
|
||||||
|
// │ [8B offset][32B signature] │ Bundle marker (in placeholder within PE)
|
||||||
|
// │ ... │
|
||||||
|
// ├──────────────────────────────────┤
|
||||||
|
// │ Bundled Files │ Raw file contents (assemblies, deps.json, etc.)
|
||||||
|
// ├──────────────────────────────────┤
|
||||||
|
// │ Bundle Header │ Version info, file count, deps.json offset (V2+)
|
||||||
|
// │ File Manifest │ Per-file: offset, size, type, path
|
||||||
|
// └──────────────────────────────────┘
|
||||||
|
//
|
||||||
|
// Parsing strategy:
|
||||||
|
// 1. Search only the PE portion (using section headers) for the bundle signature
|
||||||
|
// 2. Read 8 bytes before signature to get header offset
|
||||||
|
// 3. Parse header to get deps.json location (V2+) or scan manifest entries (V1)
|
||||||
|
//
|
||||||
|
// See related documentation for more information:
|
||||||
|
// - https://github.com/dotnet/designs/blob/main/accepted/2020/single-file/design.md
|
||||||
|
// - https://github.com/dotnet/designs/blob/main/accepted/2020/single-file/bundler.md
|
||||||
|
// - https://github.com/dotnet/runtime/blob/main/src/installer/managed/Microsoft.NET.HostModel/Bundle/Manifest.cs
|
||||||
|
// - https://github.com/dotnet/runtime/blob/main/src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs
|
||||||
|
// - https://github.com/dotnet/runtime/blob/main/src/native/corehost/bundle/header.h
|
||||||
|
// - https://github.com/dotnet/runtime/blob/main/src/native/corehost/bundle/file_entry.h
|
||||||
|
// - https://github.com/dotnet/runtime/blob/main/src/native/corehost/bundle/file_type.h
|
||||||
|
func extractDepsJSONFromBundle(r io.ReadSeeker, sections []pe.SectionHeader32) (string, error) {
|
||||||
|
headerOffset, err := findBundleHeaderOffset(r, sections)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if headerOffset == 0 {
|
||||||
|
return "", nil // not a .NET single-file bundle
|
||||||
|
}
|
||||||
|
|
||||||
|
return readDepsJSONFromBundleHeader(r, headerOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// findBundleHeaderOffset locates the bundle marker within the PE structure and returns the header offset.
|
||||||
|
// Returns 0 if no bundle marker is found (not a single-file bundle).
|
||||||
|
func findBundleHeaderOffset(r io.ReadSeeker, sections []pe.SectionHeader32) (int64, error) {
|
||||||
|
peEndOffset := calculatePEEndOffset(sections)
|
||||||
|
|
||||||
|
if _, err := r.Seek(0, io.SeekStart); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
peData := make([]byte, peEndOffset)
|
||||||
|
n, err := io.ReadFull(r, peData)
|
||||||
|
if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
peData = peData[:n]
|
||||||
|
|
||||||
|
idx := bytes.Index(peData, dotNetBundleSignature)
|
||||||
|
if idx == -1 || idx < 8 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// the header offset is stored in the 8 bytes immediately before the signature
|
||||||
|
headerOffset := int64(binary.LittleEndian.Uint64(peData[idx-8 : idx]))
|
||||||
|
return headerOffset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculatePEEndOffset determines where the PE structure ends based on section headers,
|
||||||
|
// adding padding for alignment. This bounds our search for the bundle marker.
|
||||||
|
func calculatePEEndOffset(sections []pe.SectionHeader32) int64 {
|
||||||
|
var peEndOffset int64
|
||||||
|
for _, sec := range sections {
|
||||||
|
endOfSection := int64(sec.PointerToRawData) + int64(sec.SizeOfRawData)
|
||||||
|
if endOfSection > peEndOffset {
|
||||||
|
peEndOffset = endOfSection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add buffer for alignment padding after sections
|
||||||
|
return peEndOffset + 4096
|
||||||
|
}
|
||||||
|
|
||||||
|
// readDepsJSONFromBundleHeader parses the bundle header at the given offset and extracts deps.json content.
|
||||||
|
func readDepsJSONFromBundleHeader(r io.ReadSeeker, headerOffset int64) (string, error) {
|
||||||
|
if _, err := r.Seek(headerOffset, io.SeekStart); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var header dotNetBundleHeader
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip bundle ID (7-bit length-prefixed string)
|
||||||
|
if err := skipDotNetString(r); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// for V2+ bundles (.NET 5+), read deps.json location directly from header
|
||||||
|
if header.MajorVersion >= 2 {
|
||||||
|
var headerV2 dotNetBundleHeaderV2
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &headerV2); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if headerV2.DepsJSONSize > 0 && headerV2.DepsJSONOffset > 0 {
|
||||||
|
return readDepsJSONAtOffset(r, headerV2.DepsJSONOffset, headerV2.DepsJSONSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for V1 bundles (.NET Core 3.x) or if V2 header doesn't have deps.json, parse manifest
|
||||||
|
return findDepsJSONInManifest(r, header.NumEmbeddedFiles, header.MajorVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// skipDotNetString skips a 7-bit length-prefixed string (.NET BinaryWriter format)
|
||||||
|
func skipDotNetString(r io.ReadSeeker) error {
|
||||||
|
length, err := read7BitEncodedInt(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = r.Seek(int64(length), io.SeekCurrent)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// read7BitEncodedInt reads a .NET 7-bit encoded integer (variable-length encoding used by BinaryWriter)
|
||||||
|
func read7BitEncodedInt(r io.Reader) (int, error) {
|
||||||
|
result := 0
|
||||||
|
shift := 0
|
||||||
|
for {
|
||||||
|
var b [1]byte
|
||||||
|
if _, err := r.Read(b[:]); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
result |= int(b[0]&0x7F) << shift
|
||||||
|
if b[0]&0x80 == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
shift += 7
|
||||||
|
if shift >= 35 { // prevent overflow
|
||||||
|
return 0, errors.New("invalid 7-bit encoded int")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readDepsJSONAtOffset reads deps.json content at a specific offset using seeks (avoiding loading entire file)
|
||||||
|
func readDepsJSONAtOffset(r io.ReadSeeker, offset, size int64) (string, error) {
|
||||||
|
if _, err := r.Seek(offset, io.SeekStart); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to seek to deps.json at offset %d: %w", offset, err)
|
||||||
|
}
|
||||||
|
data := make([]byte, size)
|
||||||
|
if _, err := io.ReadFull(r, data); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read deps.json (%d bytes): %w", size, err)
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findDepsJSONInManifest parses manifest entries to find deps.json (for V1 bundles or fallback)
|
||||||
|
func findDepsJSONInManifest(r io.ReadSeeker, numFiles int32, majorVersion uint32) (string, error) {
|
||||||
|
for i := int32(0); i < numFiles; i++ {
|
||||||
|
var offset, size int64
|
||||||
|
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &offset); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &size); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// V6+ bundles (.NET 6+) have compressed size field
|
||||||
|
if majorVersion >= 6 {
|
||||||
|
var compressedSize int64
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &compressedSize); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileType dotNetFileType
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &fileType); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip relativePath string
|
||||||
|
if err := skipDotNetString(r); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileType == dotNetFileTypeDepsJSON && size > 0 {
|
||||||
|
// save current position to resume manifest parsing if needed
|
||||||
|
currentPos, err := r.Seek(0, io.SeekCurrent)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// read deps.json content
|
||||||
|
content, err := readDepsJSONAtOffset(r, offset, size)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore position (in case caller needs to continue)
|
||||||
|
if _, err := r.Seek(currentPos, io.SeekStart); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
58
syft/pkg/cataloger/internal/pe/bundle_test.go
Normal file
58
syft/pkg/cataloger/internal/pe/bundle_test.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package pe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_extractDepsJSONFromBundle_Versions(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fixture string
|
||||||
|
path string
|
||||||
|
wantDepsJSON bool // true if deps.json should be found
|
||||||
|
wantJSONContain string // string that should be in the JSON (varies by .NET version)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "V1 bundle (.NET Core 3.1)",
|
||||||
|
fixture: "image-dotnet31-single-file",
|
||||||
|
path: "/app/hello.exe",
|
||||||
|
wantDepsJSON: true,
|
||||||
|
wantJSONContain: "runtimeOptions", // .NET Core 3.1 uses runtimeOptions
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "V2 bundle (.NET 5)",
|
||||||
|
fixture: "image-dotnet5-single-file",
|
||||||
|
path: "/app/hello.exe",
|
||||||
|
wantDepsJSON: true,
|
||||||
|
wantJSONContain: "runtimeTarget", // .NET 5+ uses runtimeTarget
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "V6 bundle (.NET 6)",
|
||||||
|
fixture: "image-dotnet6-single-file",
|
||||||
|
path: "/app/hello.exe",
|
||||||
|
wantDepsJSON: true,
|
||||||
|
wantJSONContain: "runtimeTarget", // .NET 6+ uses runtimeTarget
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
reader := fixtureFile(t, tt.fixture, tt.path)
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
got, err := Read(reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if tt.wantDepsJSON {
|
||||||
|
assert.NotEmpty(t, got.EmbeddedDepsJSON, "expected deps.json to be extracted from bundle")
|
||||||
|
// verify it looks like valid JSON for this .NET version
|
||||||
|
assert.Contains(t, got.EmbeddedDepsJSON, tt.wantJSONContain, "deps.json should contain expected field")
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, got.EmbeddedDepsJSON, "expected no deps.json in non-bundle file")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -34,6 +34,10 @@ type File struct {
|
|||||||
// understand if this executable is even a .NET application.
|
// understand if this executable is even a .NET application.
|
||||||
CLR *CLREvidence
|
CLR *CLREvidence
|
||||||
|
|
||||||
|
// EmbeddedDepsJSON is the contents of an embedded deps.json file found within the PE file, if any.
|
||||||
|
// This is typical when using the PublishSingleFile build option.
|
||||||
|
EmbeddedDepsJSON string
|
||||||
|
|
||||||
// VersionResources is a map of version resource keys to their values found in the VERSIONINFO resource directory.
|
// VersionResources is a map of version resource keys to their values found in the VERSIONINFO resource directory.
|
||||||
VersionResources map[string]string
|
VersionResources map[string]string
|
||||||
}
|
}
|
||||||
@ -153,7 +157,7 @@ func Read(f file.LocationReadCloser) (*File, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sections, _, err := parsePEFile(r)
|
sections, sectionHeaders, err := parsePEFile(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to parse PE sections: %w", err)
|
return nil, fmt.Errorf("unable to parse PE sections: %w", err)
|
||||||
}
|
}
|
||||||
@ -171,9 +175,15 @@ func Read(f file.LocationReadCloser) (*File, error) {
|
|||||||
return nil, fmt.Errorf("unable to parse PE CLR directory: %w", err)
|
return nil, fmt.Errorf("unable to parse PE CLR directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
embeddedDepsJSON, err := extractDepsJSONFromBundle(r, sectionHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to extract embedded deps.json: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return &File{
|
return &File{
|
||||||
Location: f.Location,
|
Location: f.Location,
|
||||||
CLR: c,
|
CLR: c,
|
||||||
|
EmbeddedDepsJSON: embeddedDepsJSON,
|
||||||
VersionResources: versionResources,
|
VersionResources: versionResources,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package pe
|
package pe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
@ -14,13 +16,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Test_Read_DotNetDetection(t *testing.T) {
|
func Test_Read_DotNetDetection(t *testing.T) {
|
||||||
|
singleFileDepsJSON, err := os.ReadFile("test-fixtures/net8-app-single-file.deps.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fixture string
|
fixture string
|
||||||
path string
|
path string
|
||||||
wantVR map[string]string
|
wantVR map[string]string
|
||||||
wantCLR bool
|
wantCLR bool
|
||||||
wantErr require.ErrorAssertionFunc
|
wantDepsJSON string
|
||||||
|
wantErr require.ErrorAssertionFunc
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "newtonsoft",
|
name: "newtonsoft",
|
||||||
@ -114,7 +120,8 @@ func Test_Read_DotNetDetection(t *testing.T) {
|
|||||||
"ProductVersion": "1.0.0",
|
"ProductVersion": "1.0.0",
|
||||||
"Assembly Version": "1.0.0.0",
|
"Assembly Version": "1.0.0.0",
|
||||||
},
|
},
|
||||||
wantErr: require.NoError,
|
wantDepsJSON: string(singleFileDepsJSON),
|
||||||
|
wantErr: require.NoError,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,6 +144,11 @@ func Test_Read_DotNetDetection(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, tt.wantCLR, got.CLR.HasEvidenceOfCLR())
|
assert.Equal(t, tt.wantCLR, got.CLR.HasEvidenceOfCLR())
|
||||||
|
|
||||||
|
if d := cmp.Diff(tt.wantDepsJSON, got.EmbeddedDepsJSON); d != "" {
|
||||||
|
fmt.Printf("got embedded deps.json: %s\n", got.EmbeddedDepsJSON)
|
||||||
|
t.Errorf("unexpected deps.json location (-want +got): %s", d)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
syft/pkg/cataloger/internal/pe/test-fixtures/Makefile
Normal file
19
syft/pkg/cataloger/internal/pe/test-fixtures/Makefile
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
FINGERPRINT_FILE=cache.fingerprint
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := fixtures
|
||||||
|
|
||||||
|
# requirement 1: 'fixtures' goal to generate any and all test fixtures
|
||||||
|
fixtures:
|
||||||
|
@echo "nothing to do"
|
||||||
|
|
||||||
|
# requirement 2: 'fingerprint' goal to determine if cache should be busted
|
||||||
|
fingerprint: $(FINGERPRINT_FILE)
|
||||||
|
|
||||||
|
# requirement 3: always recalculate fingerprint based on source
|
||||||
|
.PHONY: $(FINGERPRINT_FILE)
|
||||||
|
$(FINGERPRINT_FILE):
|
||||||
|
@find Makefile **/Dockerfile **/src/** -type f -exec sha256sum {} \; | sort -k2 > $(FINGERPRINT_FILE)
|
||||||
|
|
||||||
|
# requirement 4: 'clean' goal to remove generated test fixtures
|
||||||
|
clean:
|
||||||
|
rm -f $(FINGERPRINT_FILE)
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY src/ .
|
||||||
|
RUN dotnet publish -c Release -r win-x64 \
|
||||||
|
-p:PublishSingleFile=true \
|
||||||
|
-p:SelfContained=true \
|
||||||
|
-o /app
|
||||||
|
|
||||||
|
FROM busybox
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app/hello.exe .
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Hello
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Hello");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:5.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY src/ .
|
||||||
|
RUN dotnet publish -c Release -r win-x64 \
|
||||||
|
-p:PublishSingleFile=true \
|
||||||
|
-p:SelfContained=true \
|
||||||
|
-o /app
|
||||||
|
|
||||||
|
FROM busybox
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app/hello.exe .
|
||||||
@ -0,0 +1 @@
|
|||||||
|
System.Console.WriteLine("Hello");
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY src/ .
|
||||||
|
RUN dotnet publish -c Release -r win-x64 \
|
||||||
|
-p:PublishSingleFile=true \
|
||||||
|
-p:SelfContained=true \
|
||||||
|
-o /app
|
||||||
|
|
||||||
|
FROM busybox
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app/hello.exe .
|
||||||
@ -0,0 +1 @@
|
|||||||
|
System.Console.WriteLine("Hello");
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user