mirror of
https://github.com/anchore/syft.git
synced 2026-07-05 02:28:25 +02:00
fixed dotnet cataloger can't find packages from deps.json in linux el… (#4517)
* fixed dotnet cataloger can't find packages from deps.json in linux elf, fixed #4514 Signed-off-by: Rez Moss <hi@rezmoss.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * split bundle and PE concerns Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * limit resource usage of readall call Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * removed duplicat Signed-off-by: Rez Moss <hi@rezmoss.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * make sure the first 4 bytes in elf arent lostt Signed-off-by: Rez Moss <hi@rezmoss.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * revert readelfbundle func, check size of readdeps json Signed-off-by: Rez Moss <hi@rezmoss.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * revert readelfbundle func, check size of readdeps json, fixed #4514 Co-authored-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> * move dotnet net8 linux fixture to testdata convention Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * address malformed elf size claims + add tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * dont key off of cataloger name in testing 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> Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
This commit is contained in:
parent
a34f86fba1
commit
e7f1a803e7
@ -176,7 +176,9 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
|
|||||||
WithDepPackagesMustHaveDLL(cfg.Dotnet.DepPackagesMustHaveDLL).
|
WithDepPackagesMustHaveDLL(cfg.Dotnet.DepPackagesMustHaveDLL).
|
||||||
WithDepPackagesMustClaimDLL(cfg.Dotnet.DepPackagesMustClaimDLL).
|
WithDepPackagesMustClaimDLL(cfg.Dotnet.DepPackagesMustClaimDLL).
|
||||||
WithPropagateDLLClaimsToParents(cfg.Dotnet.PropagateDLLClaimsToParents).
|
WithPropagateDLLClaimsToParents(cfg.Dotnet.PropagateDLLClaimsToParents).
|
||||||
WithRelaxDLLClaimsWhenBundlingDetected(cfg.Dotnet.RelaxDLLClaimsWhenBundlingDetected),
|
WithRelaxDLLClaimsWhenBundlingDetected(cfg.Dotnet.RelaxDLLClaimsWhenBundlingDetected).
|
||||||
|
WithExcludeProjectReferences(cfg.Dotnet.ExcludeProjectReferences),
|
||||||
|
|
||||||
Golang: golang.DefaultCatalogerConfig().
|
Golang: golang.DefaultCatalogerConfig().
|
||||||
WithSearchLocalModCacheLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchLocalModCacheLicenses)).
|
WithSearchLocalModCacheLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchLocalModCacheLicenses)).
|
||||||
WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
|
WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import (
|
|||||||
"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/generic"
|
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pe"
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/dotnet/pe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewPEPackageCataloger returns a cataloger that interprets packages from DLL, EXE, and BPL files.
|
// NewPEPackageCataloger returns a cataloger that interprets packages from DLL, EXE, and BPL files.
|
||||||
|
|||||||
@ -14,10 +14,11 @@ import (
|
|||||||
|
|
||||||
func TestCataloger_Globs(t *testing.T) {
|
func TestCataloger_Globs(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fixture string
|
fixture string
|
||||||
cataloger pkg.Cataloger
|
cataloger pkg.Cataloger
|
||||||
expected []string
|
expected []string
|
||||||
|
ignoreUnfulfilled []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "obtain deps.json files",
|
name: "obtain deps.json files",
|
||||||
@ -47,15 +48,21 @@ func TestCataloger_Globs(t *testing.T) {
|
|||||||
"src/something.dll",
|
"src/something.dll",
|
||||||
"src/something.exe",
|
"src/something.exe",
|
||||||
},
|
},
|
||||||
|
// the binary cataloger probes executables by MIME type to find embedded bundles,
|
||||||
|
// but the glob fixtures aren't real binaries so those queries go unfulfilled
|
||||||
|
ignoreUnfulfilled: []string{"application/x-executable", "application/x-sharedlib"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
pkgtest.NewCatalogTester().
|
tester := pkgtest.NewCatalogTester().
|
||||||
FromDirectory(t, test.fixture).
|
FromDirectory(t, test.fixture).
|
||||||
ExpectsResolverContentQueries(test.expected).
|
ExpectsResolverContentQueries(test.expected)
|
||||||
TestCataloger(t, test.cataloger)
|
if len(test.ignoreUnfulfilled) > 0 {
|
||||||
|
tester = tester.IgnoreUnfulfilledPathResponses(test.ignoreUnfulfilled...)
|
||||||
|
}
|
||||||
|
tester.TestCataloger(t, test.cataloger)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1105,6 +1112,167 @@ func TestCataloger(t *testing.T) {
|
|||||||
},
|
},
|
||||||
assertion: assertSingleFileDeployment,
|
assertion: assertSingleFileDeployment,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "combined cataloger (single file linux)",
|
||||||
|
fixture: "image-net8-app-single-file-linux",
|
||||||
|
cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()),
|
||||||
|
expectedPkgs: []string{
|
||||||
|
"Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.af @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.ar @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.az @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.bg @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.bn-BD @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.cs @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.da @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.de @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.el @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.es @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.fa @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.fi-FI @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.fr @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.fr-BE @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.he @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.hr @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.hu @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.hy @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.id @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.is @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.it @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.ja @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.ko-KR @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.ku @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.lv @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.ms-MY @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.mt @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.nb @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.nb-NO @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.nl @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.pl @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.pt @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.ro @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.ru @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.sk @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.sl @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.sr @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.sr-Latn @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.sv @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.th-TH @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.tr @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.uk @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.uz-Cyrl-UZ @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.uz-Latn-UZ @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.vi @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.zh-CN @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.zh-Hans @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.zh-Hant @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Newtonsoft.Json @ 13.0.3 (/app/dotnetapp)",
|
||||||
|
"dotnetapp @ 1.0.0 (/app/dotnetapp)",
|
||||||
|
"runtimepack.Microsoft.NETCore.App.Runtime.linux-musl-x64 @ 8.0.14 (/app/dotnetapp)",
|
||||||
|
},
|
||||||
|
expectedRels: []string{
|
||||||
|
"Humanizer @ 2.14.1 (/app/dotnetapp) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.af @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.ar @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.az @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.bg @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.bn-BD @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.cs @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.da @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.de @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.el @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.es @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.fa @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.fi-FI @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.fr @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.fr-BE @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.he @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.hr @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.hu @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.hy @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.id @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.is @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.it @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.ja @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.ko-KR @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.ku @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.lv @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.ms-MY @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.mt @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.nb @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.nb-NO @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.nl @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.pl @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.pt @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.ro @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.ru @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.sk @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.sl @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.sr @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.sr-Latn @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.sv @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.th-TH @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.tr @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.uk @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.uz-Cyrl-UZ @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.uz-Latn-UZ @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.vi @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.zh-CN @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.zh-Hans @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer.Core.zh-Hant @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.af @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.ar @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.az @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.bg @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.bn-BD @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.cs @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.da @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.de @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.el @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.es @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.fa @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.fi-FI @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.fr @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.fr-BE @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.he @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.hr @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.hu @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.hy @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.id @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.is @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.it @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.ja @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.ko-KR @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.ku @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.lv @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.ms-MY @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.mt @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.nb @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.nb-NO @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.nl @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.pl @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.pt @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.ro @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.ru @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.sk @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.sl @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.sr @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.sr-Latn @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.sv @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.th-TH @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.tr @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.uk @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.uz-Cyrl-UZ @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.uz-Latn-UZ @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.vi @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.zh-CN @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.zh-Hans @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Humanizer.Core.zh-Hant @ 2.14.1 (/app/dotnetapp) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp)",
|
||||||
|
"Newtonsoft.Json @ 13.0.3 (/app/dotnetapp) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp)",
|
||||||
|
"runtimepack.Microsoft.NETCore.App.Runtime.linux-musl-x64 @ 8.0.14 (/app/dotnetapp) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp)",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "deps cataloger (self-contained)",
|
name: "deps cataloger (self-contained)",
|
||||||
fixture: "image-net8-app-self-contained",
|
fixture: "image-net8-app-self-contained",
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package dotnet
|
package dotnet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -16,7 +17,9 @@ import (
|
|||||||
"github.com/anchore/syft/internal/unknown"
|
"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/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/dotnet/bundle"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -26,6 +29,8 @@ const (
|
|||||||
bplGlob = "**/*.bpl"
|
bplGlob = "**/*.bpl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var elfMagic = []byte{0x7f, 'E', 'L', 'F'}
|
||||||
|
|
||||||
// depsBinaryCataloger will search for both deps.json evidence and PE file evidence to create packages. All packages
|
// depsBinaryCataloger will search for both deps.json evidence and PE file evidence to create packages. All packages
|
||||||
// from both sources are raised up, but with one merge operation applied; If a deps.json package reference can be
|
// from both sources are raised up, but with one merge operation applied; If a deps.json package reference can be
|
||||||
// correlated with a PE file, the PE file is attached to the package as supporting evidence.
|
// correlated with a PE file, the PE file is attached to the package as supporting evidence.
|
||||||
@ -38,11 +43,16 @@ func (c depsBinaryCataloger) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c depsBinaryCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { //nolint:funlen
|
func (c depsBinaryCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { //nolint:funlen
|
||||||
|
elfDepsJSONs, elfUnknowns := findELFBundledDepsJSON(resolver)
|
||||||
|
|
||||||
depJSONDocs, unknowns, err := findDepsJSON(resolver)
|
depJSONDocs, unknowns, err := findDepsJSON(resolver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
depJSONDocs = append(depJSONDocs, elfDepsJSONs...)
|
||||||
|
unknowns = unknown.Join(unknowns, elfUnknowns)
|
||||||
|
|
||||||
peFiles, ldpeUnknownErr, err := findPEFiles(resolver)
|
peFiles, ldpeUnknownErr, err := findPEFiles(resolver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@ -520,6 +530,68 @@ func readPEFile(resolver file.Resolver, loc file.Location) (*logicalPE, error) {
|
|||||||
return ldpe, nil
|
return ldpe, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findELFBundledDepsJSON(resolver file.Resolver) ([]logicalDepsJSON, error) {
|
||||||
|
locs, err := resolver.FilesByMIMEType("application/x-executable", "application/x-sharedlib")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var depsJSONs []logicalDepsJSON
|
||||||
|
var unknownErr error
|
||||||
|
for _, loc := range locs {
|
||||||
|
doc, err := readELFBundledDepsJSON(resolver, loc)
|
||||||
|
if err != nil {
|
||||||
|
unknownErr = unknown.Append(unknownErr, loc, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if doc != nil {
|
||||||
|
depsJSONs = append(depsJSONs, *doc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return depsJSONs, unknownErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func readELFBundledDepsJSON(resolver file.Resolver, loc file.Location) (*logicalDepsJSON, error) {
|
||||||
|
reader, err := resolver.FileContentsByLocation(loc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||||
|
|
||||||
|
header := make([]byte, 4)
|
||||||
|
if _, err := io.ReadFull(reader, header); err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if !bytes.Equal(header, elfMagic) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uReader, err := unionreader.GetUnionReader(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
depsJSON, err := bundle.ExtractDepsJSONFromELFBundle(uReader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if depsJSON == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, err := newDepsJSON(file.NewLocationReadCloser(loc, io.NopCloser(strings.NewReader(depsJSON))))
|
||||||
|
if err != nil || doc == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.Location = loc
|
||||||
|
lDoc := getLogicalDepsJSON(*doc, nil)
|
||||||
|
|
||||||
|
return &lDoc, nil
|
||||||
|
}
|
||||||
|
|
||||||
func extractEmbeddedDeps(pe logicalPE) *logicalDepsJSON {
|
func extractEmbeddedDeps(pe logicalPE) *logicalDepsJSON {
|
||||||
doc, err := newDepsJSON(file.NewLocationReadCloser(pe.Location, io.NopCloser(strings.NewReader(pe.EmbeddedDepsJSON))))
|
doc, err := newDepsJSON(file.NewLocationReadCloser(pe.Location, io.NopCloser(strings.NewReader(pe.EmbeddedDepsJSON))))
|
||||||
if err != nil || doc == nil {
|
if err != nil || doc == nil {
|
||||||
|
|||||||
@ -20,6 +20,7 @@ var (
|
|||||||
spaceRegex = regexp.MustCompile(`[\s\xa0]+`)
|
spaceRegex = regexp.MustCompile(`[\s\xa0]+`)
|
||||||
numberRegex = regexp.MustCompile(`\d`)
|
numberRegex = regexp.MustCompile(`\d`)
|
||||||
versionPunctuationRegex = regexp.MustCompile(`[.,]+`)
|
versionPunctuationRegex = regexp.MustCompile(`[.,]+`)
|
||||||
|
nonPrintableRegex = regexp.MustCompile(`[\x00-\x1f]`)
|
||||||
)
|
)
|
||||||
|
|
||||||
type runtimeFamily string
|
type runtimeFamily string
|
||||||
@ -204,18 +205,29 @@ func cleanVersionResourceField(values ...string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
depsJSONPathRegex = regexp.MustCompile(`([^\\\/]+)\.deps\.json$`)
|
||||||
|
exePathRegex = regexp.MustCompile(`([^\\\/]+)\.exe$`)
|
||||||
|
singleFileRegex = regexp.MustCompile(`([^\\\/]+)$`)
|
||||||
|
)
|
||||||
|
|
||||||
func getDepsJSONFilePrefix(p string) string {
|
func getDepsJSONFilePrefix(p string) string {
|
||||||
r := regexp.MustCompile(`([^\\\/]+)\.deps\.json$`)
|
match := depsJSONPathRegex.FindStringSubmatch(p)
|
||||||
match := r.FindStringSubmatch(p)
|
|
||||||
if len(match) > 1 {
|
if len(match) > 1 {
|
||||||
return match[1]
|
return match[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
r = regexp.MustCompile(`([^\\\/]+)\.exe$`)
|
match = exePathRegex.FindStringSubmatch(p)
|
||||||
match = r.FindStringSubmatch(p)
|
|
||||||
if len(match) > 1 {
|
if len(match) > 1 {
|
||||||
return match[1]
|
return match[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle ELF
|
||||||
|
match = singleFileRegex.FindStringSubmatch(p)
|
||||||
|
if len(match) > 1 && !strings.Contains(match[1], ".") {
|
||||||
|
return match[1]
|
||||||
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +348,7 @@ func spaceNormalize(value string) string {
|
|||||||
// Consolidate all whitespace.
|
// Consolidate all whitespace.
|
||||||
value = spaceRegex.ReplaceAllString(value, " ")
|
value = spaceRegex.ReplaceAllString(value, " ")
|
||||||
// Remove non-printable characters.
|
// Remove non-printable characters.
|
||||||
value = regexp.MustCompile(`[\x00-\x1f]`).ReplaceAllString(value, "")
|
value = nonPrintableRegex.ReplaceAllString(value, "")
|
||||||
// Consolidate again and trim.
|
// Consolidate again and trim.
|
||||||
value = spaceRegex.ReplaceAllString(value, " ")
|
value = spaceRegex.ReplaceAllString(value, " ")
|
||||||
value = strings.TrimSpace(value)
|
value = strings.TrimSpace(value)
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package dotnet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pe"
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/dotnet/pe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// logicalPE represents a PE file within the context of a .NET project (considering the deps.json file).
|
// logicalPE represents a PE file within the context of a .NET project (considering the deps.json file).
|
||||||
|
|||||||
2
syft/pkg/cataloger/dotnet/testdata/image-net8-app-single-file-linux/.gitignore
vendored
Normal file
2
syft/pkg/cataloger/dotnet/testdata/image-net8-app-single-file-linux/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/app
|
||||||
|
/extract.sh
|
||||||
16
syft/pkg/cataloger/dotnet/testdata/image-net8-app-single-file-linux/Dockerfile
vendored
Normal file
16
syft/pkg/cataloger/dotnet/testdata/image-net8-app-single-file-linux/Dockerfile
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# This is the same as the net8-app image, however, the entire .NET runtime is compiled into a single binary, residing with the application.
|
||||||
|
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:8.0-alpine@sha256:7d3a75ca5c8ac4679908ef7a2591b9bc257c62bd530167de32bba105148bb7be AS build
|
||||||
|
ARG RUNTIME=linux-musl-x64
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
COPY src/*.csproj .
|
||||||
|
COPY src/packages.lock.json .
|
||||||
|
RUN dotnet restore -r $RUNTIME --verbosity normal --locked-mode --force-evaluate
|
||||||
|
|
||||||
|
COPY src/ .
|
||||||
|
RUN dotnet publish -r $RUNTIME -p:PublishSingleFile=true -p:RestorePackagesWithLockFile=true -p:ErrorOnDuplicatePublishOutputFiles=false --self-contained true -p:EnableCompressionInSingleFile=true -p:DebugSymbols=false -p:DebugType=None -o /app
|
||||||
|
|
||||||
|
|
||||||
|
FROM busybox
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app .
|
||||||
48
syft/pkg/cataloger/dotnet/testdata/image-net8-app-single-file-linux/src/Program.cs
vendored
Normal file
48
syft/pkg/cataloger/dotnet/testdata/image-net8-app-single-file-linux/src/Program.cs
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using Humanizer;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
|
||||||
|
namespace IndirectDependencyExample
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
string runtimeInfo = "hello world!\n";
|
||||||
|
Console.WriteLine(runtimeInfo);
|
||||||
|
|
||||||
|
|
||||||
|
Console.WriteLine($"\"this_is_a_test\" to title case: {"this_is_a_test".Humanize(LetterCasing.Title)}");
|
||||||
|
|
||||||
|
const string jsonString = @"
|
||||||
|
{
|
||||||
|
""message"": ""Hello from JSON!"",
|
||||||
|
""details"": {
|
||||||
|
""timestamp"": ""2025-03-26T12:00:00Z"",
|
||||||
|
""version"": ""1.0.0"",
|
||||||
|
""metadata"": {
|
||||||
|
""author"": ""Claude"",
|
||||||
|
""environment"": ""Development""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
""items"": [
|
||||||
|
{
|
||||||
|
""id"": 1,
|
||||||
|
""name"": ""Item One""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
""id"": 2,
|
||||||
|
""name"": ""Item Two""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}";
|
||||||
|
|
||||||
|
JObject jsonObject = JObject.Parse(jsonString);
|
||||||
|
|
||||||
|
string message = (string)jsonObject["message"];
|
||||||
|
Console.WriteLine($"Message: {message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
syft/pkg/cataloger/dotnet/testdata/image-net8-app-single-file-linux/src/dotnetapp.csproj
vendored
Normal file
15
syft/pkg/cataloger/dotnet/testdata/image-net8-app-single-file-linux/src/dotnetapp.csproj
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="Humanizer" Version="2.14.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
459
syft/pkg/cataloger/dotnet/testdata/image-net8-app-single-file-linux/src/packages.lock.json
vendored
Normal file
459
syft/pkg/cataloger/dotnet/testdata/image-net8-app-single-file-linux/src/packages.lock.json
vendored
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"dependencies": {
|
||||||
|
"net8.0": {
|
||||||
|
"Humanizer": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[2.14.1, )",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "/FUTD3cEceAAmJSCPN9+J+VhGwmL/C12jvwlyM1DFXShEMsBzvLzLqSrJ2rb+k/W2znKw7JyflZgZpyE+tI7lA==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core.af": "2.14.1",
|
||||||
|
"Humanizer.Core.ar": "2.14.1",
|
||||||
|
"Humanizer.Core.az": "2.14.1",
|
||||||
|
"Humanizer.Core.bg": "2.14.1",
|
||||||
|
"Humanizer.Core.bn-BD": "2.14.1",
|
||||||
|
"Humanizer.Core.cs": "2.14.1",
|
||||||
|
"Humanizer.Core.da": "2.14.1",
|
||||||
|
"Humanizer.Core.de": "2.14.1",
|
||||||
|
"Humanizer.Core.el": "2.14.1",
|
||||||
|
"Humanizer.Core.es": "2.14.1",
|
||||||
|
"Humanizer.Core.fa": "2.14.1",
|
||||||
|
"Humanizer.Core.fi-FI": "2.14.1",
|
||||||
|
"Humanizer.Core.fr": "2.14.1",
|
||||||
|
"Humanizer.Core.fr-BE": "2.14.1",
|
||||||
|
"Humanizer.Core.he": "2.14.1",
|
||||||
|
"Humanizer.Core.hr": "2.14.1",
|
||||||
|
"Humanizer.Core.hu": "2.14.1",
|
||||||
|
"Humanizer.Core.hy": "2.14.1",
|
||||||
|
"Humanizer.Core.id": "2.14.1",
|
||||||
|
"Humanizer.Core.is": "2.14.1",
|
||||||
|
"Humanizer.Core.it": "2.14.1",
|
||||||
|
"Humanizer.Core.ja": "2.14.1",
|
||||||
|
"Humanizer.Core.ko-KR": "2.14.1",
|
||||||
|
"Humanizer.Core.ku": "2.14.1",
|
||||||
|
"Humanizer.Core.lv": "2.14.1",
|
||||||
|
"Humanizer.Core.ms-MY": "2.14.1",
|
||||||
|
"Humanizer.Core.mt": "2.14.1",
|
||||||
|
"Humanizer.Core.nb": "2.14.1",
|
||||||
|
"Humanizer.Core.nb-NO": "2.14.1",
|
||||||
|
"Humanizer.Core.nl": "2.14.1",
|
||||||
|
"Humanizer.Core.pl": "2.14.1",
|
||||||
|
"Humanizer.Core.pt": "2.14.1",
|
||||||
|
"Humanizer.Core.ro": "2.14.1",
|
||||||
|
"Humanizer.Core.ru": "2.14.1",
|
||||||
|
"Humanizer.Core.sk": "2.14.1",
|
||||||
|
"Humanizer.Core.sl": "2.14.1",
|
||||||
|
"Humanizer.Core.sr": "2.14.1",
|
||||||
|
"Humanizer.Core.sr-Latn": "2.14.1",
|
||||||
|
"Humanizer.Core.sv": "2.14.1",
|
||||||
|
"Humanizer.Core.th-TH": "2.14.1",
|
||||||
|
"Humanizer.Core.tr": "2.14.1",
|
||||||
|
"Humanizer.Core.uk": "2.14.1",
|
||||||
|
"Humanizer.Core.uz-Cyrl-UZ": "2.14.1",
|
||||||
|
"Humanizer.Core.uz-Latn-UZ": "2.14.1",
|
||||||
|
"Humanizer.Core.vi": "2.14.1",
|
||||||
|
"Humanizer.Core.zh-CN": "2.14.1",
|
||||||
|
"Humanizer.Core.zh-Hans": "2.14.1",
|
||||||
|
"Humanizer.Core.zh-Hant": "2.14.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Newtonsoft.Json": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[13.0.3, )",
|
||||||
|
"resolved": "13.0.3",
|
||||||
|
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
|
||||||
|
},
|
||||||
|
"Humanizer.Core": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
|
||||||
|
},
|
||||||
|
"Humanizer.Core.af": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "BoQHyu5le+xxKOw+/AUM7CLXneM/Bh3++0qh1u0+D95n6f9eGt9kNc8LcAHLIOwId7Sd5hiAaaav0Nimj3peNw==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.ar": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "3d1V10LDtmqg5bZjWkA/EkmGFeSfNBcyCH+TiHcHP+HGQQmRq3eBaLcLnOJbVQVn3Z6Ak8GOte4RX4kVCxQlFA==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.az": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "8Z/tp9PdHr/K2Stve2Qs/7uqWPWLUK9D8sOZDNzyv42e20bSoJkHFn7SFoxhmaoVLJwku2jp6P7HuwrfkrP18Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.bg": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "S+hIEHicrOcbV2TBtyoPp1AVIGsBzlarOGThhQYCnP6QzEYo/5imtok6LMmhZeTnBFoKhM8yJqRfvJ5yqVQKSQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.bn-BD": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "U3bfj90tnUDRKlL1ZFlzhCHoVgpTcqUlTQxjvGCaFKb+734TTu3nkHUWVZltA1E/swTvimo/aXLtkxnLFrc0EQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.cs": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "jWrQkiCTy3L2u1T86cFkgijX6k7hoB0pdcFMWYaSZnm6rvG/XJE40tfhYyKhYYgIc1x9P2GO5AC7xXvFnFdqMQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.da": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "5o0rJyE/2wWUUphC79rgYDnif/21MKTTx9LIzRVz9cjCIVFrJ2bDyR2gapvI9D6fjoyvD1NAfkN18SHBsO8S9g==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.de": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "9JD/p+rqjb8f5RdZ3aEJqbjMYkbk4VFii2QDnnOdNo6ywEfg/A5YeOQ55CaBJmy7KvV4tOK4+qHJnX/tg3Z54A==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.el": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "Xmv6sTL5mqjOWGGpqY7bvbfK5RngaUHSa8fYDGSLyxY9mGdNbDcasnRnMOvi0SxJS9gAqBCn21Xi90n2SHZbFA==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.es": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "e//OIAeMB7pjBV1HqqI4pM2Bcw3Jwgpyz9G5Fi4c+RJvhqFwztoWxW57PzTnNJE2lbhGGLQZihFZjsbTUsbczA==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.fa": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "nzDOj1x0NgjXMjsQxrET21t1FbdoRYujzbmZoR8u8ou5CBWY1UNca0j6n/PEJR/iUbt4IxstpszRy41wL/BrpA==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.fi-FI": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "Vnxxx4LUhp3AzowYi6lZLAA9Lh8UqkdwRh4IE2qDXiVpbo08rSbokATaEzFS+o+/jCNZBmoyyyph3vgmcSzhhQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.fr": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "2p4g0BYNzFS3u9SOIDByp2VClYKO0K1ecDV4BkB9EYdEPWfFODYnF+8CH8LpUrpxL2TuWo2fiFx/4Jcmrnkbpg==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.fr-BE": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "o6R3SerxCRn5Ij8nCihDNMGXlaJ/1AqefteAssgmU2qXYlSAGdhxmnrQAXZUDlE4YWt/XQ6VkNLtH7oMqsSPFQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.he": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "FPsAhy7Iw6hb+ZitLgYC26xNcgGAHXb0V823yFAzcyoL5ozM+DCJtYfDPYiOpsJhEZmKFTM9No0jUn1M89WGvg==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.hr": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "chnaD89yOlST142AMkAKLuzRcV5df3yyhDyRU5rypDiqrq2HN8y1UR3h1IicEAEtXLoOEQyjSAkAQ6QuXkn7aw==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.hu": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "hAfnaoF9LTGU/CmFdbnvugN4tIs8ppevVMe3e5bD24+tuKsggMc5hYta9aiydI8JH9JnuVmxvNI4DJee1tK05A==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.hy": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "sVIKxOiSBUb4gStRHo9XwwAg9w7TNvAXbjy176gyTtaTiZkcjr9aCPziUlYAF07oNz6SdwdC2mwJBGgvZ0Sl2g==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.id": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "4Zl3GTvk3a49Ia/WDNQ97eCupjjQRs2iCIZEQdmkiqyaLWttfb+cYXDMGthP42nufUL0SRsvBctN67oSpnXtsg==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.is": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "R67A9j/nNgcWzU7gZy1AJ07ABSLvogRbqOWvfRDn4q6hNdbg/mjGjZBp4qCTPnB2mHQQTCKo3oeCUayBCNIBCw==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.it": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "jYxGeN4XIKHVND02FZ+Woir3CUTyBhLsqxu9iqR/9BISArkMf1Px6i5pRZnvq4fc5Zn1qw71GKKoCaHDJBsLFw==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.ja": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "TM3ablFNoYx4cYJybmRgpDioHpiKSD7q0QtMrmpsqwtiiEsdW5zz/q4PolwAczFnvrKpN6nBXdjnPPKVet93ng==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.ko-KR": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "CtvwvK941k/U0r8PGdEuBEMdW6jv/rBiA9tUhakC7Zd2rA/HCnDcbr1DiNZ+/tRshnhzxy/qwmpY8h4qcAYCtQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.ku": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "vHmzXcVMe+LNrF9txpdHzpG7XJX65SiN9GQd/Zkt6gsGIIEeECHrkwCN5Jnlkddw2M/b0HS4SNxdR1GrSn7uCA==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.lv": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "E1/KUVnYBS1bdOTMNDD7LV/jdoZv/fbWTLPtvwdMtSdqLyRTllv6PGM9xVQoFDYlpvVGtEl/09glCojPHw8ffA==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.ms-MY": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "vX8oq9HnYmAF7bek4aGgGFJficHDRTLgp/EOiPv9mBZq0i4SA96qVMYSjJ2YTaxs7Eljqit7pfpE2nmBhY5Fnw==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.mt": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "pEgTBzUI9hzemF7xrIZigl44LidTUhNu4x/P6M9sAwZjkUF0mMkbpxKkaasOql7lLafKrnszs0xFfaxQyzeuZQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.nb": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "mbs3m6JJq53ssLqVPxNfqSdTxAcZN3njlG8yhJVx83XVedpTe1ECK9aCa8FKVOXv93Gl+yRHF82Hw9T9LWv2hw==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.nb-NO": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "AsJxrrVYmIMbKDGe8W6Z6//wKv9dhWH7RsTcEHSr4tQt/80pcNvLi0hgD3fqfTtg0tWKtgch2cLf4prorEV+5A==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.nl": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "24b0OUdzJxfoqiHPCtYnR5Y4l/s4Oh7KW7uDp+qX25NMAHLCGog2eRfA7p2kRJp8LvnynwwQxm2p534V9m55wQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.pl": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "17mJNYaBssENVZyQHduiq+bvdXS0nhZJGEXtPKoMhKv3GD//WO0mEfd9wjEBsWCSmWI7bjRqhCidxzN+YtJmsg==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.pt": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "8HB8qavcVp2la1GJX6t+G9nDYtylPKzyhxr9LAooIei9MnQvNsjEiIE4QvHoeDZ4weuQ9CsPg1c211XUMVEZ4A==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.ro": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "psXNOcA6R8fSHoQYhpBTtTTYiOk8OBoN3PKCEDgsJKIyeY5xuK81IBdGi77qGZMu/OwBRQjQCBMtPJb0f4O1+A==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.ru": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "zm245xUWrajSN2t9H7BTf84/2APbUkKlUJpcdgsvTdAysr1ag9fi1APu6JEok39RRBXDfNRVZHawQ/U8X0pSvQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.sk": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "Ncw24Vf3ioRnbU4MsMFHafkyYi8JOnTqvK741GftlQvAbULBoTz2+e7JByOaasqeSi0KfTXeegJO+5Wk1c0Mbw==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.sl": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "l8sUy4ciAIbVThWNL0atzTS2HWtv8qJrsGWNlqrEKmPwA4SdKolSqnTes9V89fyZTc2Q43jK8fgzVE2C7t009A==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.sr": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "rnNvhpkOrWEymy7R/MiFv7uef8YO5HuXDyvojZ7JpijHWA5dXuVXooCOiA/3E93fYa3pxDuG2OQe4M/olXbQ7w==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.sr-Latn": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "nuy/ykpk974F8ItoQMS00kJPr2dFNjOSjgzCwfysbu7+gjqHmbLcYs7G4kshLwdA4AsVncxp99LYeJgoh1JF5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.sv": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "E53+tpAG0RCp+cSSI7TfBPC+NnsEqUuoSV0sU+rWRXWr9MbRWx1+Zj02XMojqjGzHjjOrBFBBio6m74seFl0AA==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.th-TH": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "eSevlJtvs1r4vQarNPfZ2kKDp/xMhuD00tVVzRXkSh1IAZbBJI/x2ydxUOwfK9bEwEp+YjvL1Djx2+kw7ziu7g==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.tr": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "rQ8N+o7yFcFqdbtu1mmbrXFi8TQ+uy+fVH9OPI0CI3Cu1om5hUU/GOMC3hXsTCI6d79y4XX+0HbnD7FT5khegA==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.uk": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "2uEfujwXKNm6bdpukaLtEJD+04uUtQD65nSGCetA1fYNizItEaIBUboNfr3GzJxSMQotNwGVM3+nSn8jTd0VSg==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.uz-Cyrl-UZ": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "TD3ME2sprAvFqk9tkWrvSKx5XxEMlAn1sjk+cYClSWZlIMhQQ2Bp/w0VjX1Kc5oeKjxRAnR7vFcLUFLiZIDk9Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.uz-Latn-UZ": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "/kHAoF4g0GahnugZiEMpaHlxb+W6jCEbWIdsq9/I1k48ULOsl/J0pxZj93lXC3omGzVF1BTVIeAtv5fW06Phsg==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.vi": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "rsQNh9rmHMBtnsUUlJbShMsIMGflZtPmrMM6JNDw20nhsvqfrdcoDD8cMnLAbuSovtc3dP+swRmLQzKmXDTVPA==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.zh-CN": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "uH2dWhrgugkCjDmduLdAFO9w1Mo0q07EuvM0QiIZCVm6FMCu/lGv2fpMu4GX+4HLZ6h5T2Pg9FIdDLCPN2a67w==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.zh-Hans": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "WH6IhJ8V1UBG7rZXQk3dZUoP2gsi8a0WkL8xL0sN6WGiv695s8nVcmab9tWz20ySQbuzp0UkSxUQFi5jJHIpOQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core.zh-Hant": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.14.1",
|
||||||
|
"contentHash": "VIXB7HCUC34OoaGnO3HJVtSv2/wljPhjV7eKH4+TFPgQdJj2lvHNKY41Dtg0Bphu7X5UaXFR4zrYYyo+GNOjbA==",
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "[2.14.1]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"net8.0/win-x64": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,6 @@
|
|||||||
package pe
|
package bundle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"debug/pe"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -45,92 +43,8 @@ const (
|
|||||||
dotNetFileTypeSymbols
|
dotNetFileTypeSymbols
|
||||||
)
|
)
|
||||||
|
|
||||||
// extractDepsJSONFromBundle searches for an embedded deps.json file in a .NET single-file bundle.
|
// ReadDepsJSONFromBundleHeader parses the bundle header at the given offset and extracts deps.json content.
|
||||||
// When built with PublishSingleFile=true, .NET embeds the application and all dependencies into
|
func ReadDepsJSONFromBundleHeader(r io.ReadSeeker, headerOffset int64) (string, error) {
|
||||||
// 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 {
|
if _, err := r.Seek(headerOffset, io.SeekStart); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -194,6 +108,9 @@ func read7BitEncodedInt(r io.Reader) (int, error) {
|
|||||||
|
|
||||||
// readDepsJSONAtOffset reads deps.json content at a specific offset using seeks (avoiding loading entire file)
|
// 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) {
|
func readDepsJSONAtOffset(r io.ReadSeeker, offset, size int64) (string, error) {
|
||||||
|
if size <= 0 || size > 3*1024*1024 { // 3MB max deps.json size in dotnet bundle
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
if _, err := r.Seek(offset, io.SeekStart); err != nil {
|
if _, err := r.Seek(offset, io.SeekStart); err != nil {
|
||||||
return "", fmt.Errorf("failed to seek to deps.json at offset %d: %w", offset, err)
|
return "", fmt.Errorf("failed to seek to deps.json at offset %d: %w", offset, err)
|
||||||
}
|
}
|
||||||
@ -206,7 +123,7 @@ func readDepsJSONAtOffset(r io.ReadSeeker, offset, size int64) (string, error) {
|
|||||||
|
|
||||||
// findDepsJSONInManifest parses manifest entries to find deps.json (for V1 bundles or fallback)
|
// findDepsJSONInManifest parses manifest entries to find deps.json (for V1 bundles or fallback)
|
||||||
func findDepsJSONInManifest(r io.ReadSeeker, numFiles int32, majorVersion uint32) (string, error) {
|
func findDepsJSONInManifest(r io.ReadSeeker, numFiles int32, majorVersion uint32) (string, error) {
|
||||||
for range numFiles {
|
for i := int32(0); i < numFiles; i++ {
|
||||||
var offset, size int64
|
var offset, size int64
|
||||||
|
|
||||||
if err := binary.Read(r, binary.LittleEndian, &offset); err != nil {
|
if err := binary.Read(r, binary.LittleEndian, &offset); err != nil {
|
||||||
83
syft/pkg/cataloger/internal/dotnet/bundle/bundle_elf.go
Normal file
83
syft/pkg/cataloger/internal/dotnet/bundle/bundle_elf.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package bundle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"debug/elf"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/internal/unionreader"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExtractDepsJSONFromELFBundle extracts the deps.json content from a .net singlefile
|
||||||
|
// bundle contained within an ELF bin
|
||||||
|
func ExtractDepsJSONFromELFBundle(r unionreader.UnionReader) (string, error) {
|
||||||
|
headerOffset, err := findBundleHeaderOffsetInELF(r)
|
||||||
|
if err != nil || headerOffset == 0 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return ReadDepsJSONFromBundleHeader(r, headerOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findBundleHeaderOffsetInELF(r unionreader.UnionReader) (int64, error) {
|
||||||
|
elfFile, err := elf.NewFile(r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
elfEndOffset := calculateELFEndOffset(elfFile)
|
||||||
|
if elfEndOffset == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// clamp to the actual file size so a malformed ELF (with bogus segment/section
|
||||||
|
// offsets+sizes) can't drive an arbitrarily large allocation below.
|
||||||
|
fileSize, err := r.Seek(0, io.SeekEnd)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if elfEndOffset > fileSize {
|
||||||
|
elfEndOffset = fileSize
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := r.Seek(0, io.SeekStart); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
searchData := make([]byte, elfEndOffset)
|
||||||
|
n, err := io.ReadFull(r, searchData)
|
||||||
|
if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
searchData = searchData[:n]
|
||||||
|
|
||||||
|
idx := bytes.Index(searchData, dotNetBundleSignature)
|
||||||
|
if idx == -1 || idx < 8 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(binary.LittleEndian.Uint64(searchData[idx-8 : idx])), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateELFEndOffset(f *elf.File) int64 {
|
||||||
|
var endOffset int64
|
||||||
|
|
||||||
|
for _, prog := range f.Progs {
|
||||||
|
end := int64(prog.Off) + int64(prog.Filesz)
|
||||||
|
if end > endOffset {
|
||||||
|
endOffset = end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sec := range f.Sections {
|
||||||
|
if sec.Type == elf.SHT_NOBITS {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
end := int64(sec.Offset) + int64(sec.Size)
|
||||||
|
if end > endOffset {
|
||||||
|
endOffset = end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return endOffset + 4096
|
||||||
|
}
|
||||||
63
syft/pkg/cataloger/internal/dotnet/bundle/bundle_elf_test.go
Normal file
63
syft/pkg/cataloger/internal/dotnet/bundle/bundle_elf_test.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package bundle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// readSeekCloser adapts a *bytes.Reader to the unionreader.UnionReader interface (adds Close).
|
||||||
|
type readSeekCloser struct {
|
||||||
|
*bytes.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (readSeekCloser) Close() error { return nil }
|
||||||
|
|
||||||
|
// buildELFWithHugeFilesz returns a minimal, parseable ELF64 whose single program header
|
||||||
|
// declares an absurd p_filesz. calculateELFEndOffset will compute a huge end offset; the
|
||||||
|
// clamp in findBundleHeaderOffsetInELF must keep the allocation bounded to the real file.
|
||||||
|
func buildELFWithHugeFilesz() []byte {
|
||||||
|
const (
|
||||||
|
ehSize = 64
|
||||||
|
phSize = 56
|
||||||
|
phOff = ehSize
|
||||||
|
phCount = 1
|
||||||
|
)
|
||||||
|
buf := make([]byte, ehSize+phSize)
|
||||||
|
|
||||||
|
// e_ident
|
||||||
|
copy(buf[0:4], []byte{0x7f, 'E', 'L', 'F'})
|
||||||
|
buf[4] = 2 // ELFCLASS64
|
||||||
|
buf[5] = 1 // ELFDATA2LSB
|
||||||
|
buf[6] = 1 // EV_CURRENT
|
||||||
|
|
||||||
|
le := binary.LittleEndian
|
||||||
|
le.PutUint16(buf[16:], 2) // e_type = ET_EXEC
|
||||||
|
le.PutUint16(buf[18:], 0x3e) // e_machine = x86-64
|
||||||
|
le.PutUint32(buf[20:], 1) // e_version
|
||||||
|
le.PutUint64(buf[32:], phOff) // e_phoff
|
||||||
|
le.PutUint16(buf[52:], ehSize) // e_ehsize
|
||||||
|
le.PutUint16(buf[54:], phSize) // e_phentsize
|
||||||
|
le.PutUint16(buf[56:], phCount) // e_phnum
|
||||||
|
// e_shoff/e_shnum left zero so no sections are parsed
|
||||||
|
|
||||||
|
ph := buf[phOff:]
|
||||||
|
le.PutUint32(ph[0:], 1) // p_type = PT_LOAD
|
||||||
|
le.PutUint64(ph[16:], 0) // p_offset
|
||||||
|
le.PutUint64(ph[32:], 1<<60) // p_filesz (bogus, attacker-controlled)
|
||||||
|
le.PutUint64(ph[40:], 1<<60) // p_memsz
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractDepsJSONFromELFBundle_MalformedFileszDoesNotOverAllocate(t *testing.T) {
|
||||||
|
data := buildELFWithHugeFilesz()
|
||||||
|
r := readSeekCloser{bytes.NewReader(data)}
|
||||||
|
|
||||||
|
// must not OOM/panic on the bogus p_filesz, and find no bundle signature
|
||||||
|
content, err := ExtractDepsJSONFromELFBundle(r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, content)
|
||||||
|
}
|
||||||
103
syft/pkg/cataloger/internal/dotnet/pe/bundle.go
Normal file
103
syft/pkg/cataloger/internal/dotnet/pe/bundle.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package pe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"debug/pe"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/dotnet/bundle"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 bundle.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
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
|
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY src/ .
|
COPY src .
|
||||||
RUN dotnet publish -c Release -r win-x64 \
|
RUN dotnet publish -c Release -r win-x64 \
|
||||||
-p:PublishSingleFile=true \
|
-p:PublishSingleFile=true \
|
||||||
-p:SelfContained=true \
|
-p:SelfContained=true \
|
||||||
@ -1,6 +1,6 @@
|
|||||||
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:5.0 AS build
|
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:5.0 AS build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY src/ .
|
COPY src .
|
||||||
RUN dotnet publish -c Release -r win-x64 \
|
RUN dotnet publish -c Release -r win-x64 \
|
||||||
-p:PublishSingleFile=true \
|
-p:PublishSingleFile=true \
|
||||||
-p:SelfContained=true \
|
-p:SelfContained=true \
|
||||||
@ -1,6 +1,6 @@
|
|||||||
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY src/ .
|
COPY src .
|
||||||
RUN dotnet publish -c Release -r win-x64 \
|
RUN dotnet publish -c Release -r win-x64 \
|
||||||
-p:PublishSingleFile=true \
|
-p:PublishSingleFile=true \
|
||||||
-p:SelfContained=true \
|
-p:SelfContained=true \
|
||||||
1
syft/pkg/cataloger/internal/dotnet/pe/testdata/image-net8-app
vendored
Symbolic link
1
syft/pkg/cataloger/internal/dotnet/pe/testdata/image-net8-app
vendored
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../../dotnet/testdata/image-net8-app
|
||||||
1
syft/pkg/cataloger/internal/dotnet/pe/testdata/image-net8-app-single-file
vendored
Symbolic link
1
syft/pkg/cataloger/internal/dotnet/pe/testdata/image-net8-app-single-file
vendored
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../../dotnet/testdata/image-net8-app-single-file
|
||||||
@ -1 +0,0 @@
|
|||||||
../../../dotnet/testdata/image-net8-app
|
|
||||||
@ -1 +0,0 @@
|
|||||||
../../../dotnet/testdata/image-net8-app-single-file
|
|
||||||
Loading…
x
Reference in New Issue
Block a user