Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 37 additions & 19 deletions syft/pkg/cataloger/internal/cpegenerate/candidate_for_pe.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ import (
"github.com/anchore/syft/syft/pkg"
)

// candidateVendorsForPE returns vendor candidates for PE (BinaryPkg) packages based on common metadata hints.
// Specifically, normalize Ghostscript binaries to vendor "artifex" when detected.
func candidateVendorsForPE(p pkg.Package) fieldCandidateSet {
candidates := newFieldCandidateSet()
// isMicrosoftEdgePE returns true if the PE metadata resembles a Microsoft Edge binary (e.g. msedge.exe).
func isMicrosoftEdgePE(product, fileDesc string) bool {
return product == "microsoft edge" || fileDesc == "microsoft edge"
}

// candidateVendorsForPE adds vendor candidates for PE (BinaryPkg) packages based on common metadata hints,
// mutating the given set in place. Specifically, normalize Ghostscript binaries to vendor "artifex" when detected.
// Unlike sibling candidateXForY helpers in this package, this mutates the caller's set directly (rather than
// returning a new one to union in) because the Microsoft Edge case below needs to clear() the caller's existing
// (incorrect) candidates, not just add to them.
func candidateVendorsForPE(p pkg.Package, vendors fieldCandidateSet) {
meta, ok := p.Metadata.(pkg.PEBinary)
if !ok {
return candidates
return
}

var company, product, fileDesc string
Expand All @@ -29,25 +35,30 @@ func candidateVendorsForPE(p pkg.Package) fieldCandidateSet {
}

if strings.Contains(product, "ghostscript") || strings.Contains(fileDesc, "ghostscript") || strings.Contains(company, "artifex") {
candidates.addValue("artifex")
vendors.addValue("artifex")
}

if product == "git" || fileDesc == "git setup" || company == "the git development community" {
candidates.addValue("git_for_windows_project")
candidates.addValue("gitforwindows")
vendors.addValue("git_for_windows_project")
vendors.addValue("gitforwindows")
}

return candidates
if isMicrosoftEdgePE(product, fileDesc) {
// the raised package name ("Microsoft Edge") is otherwise used verbatim as the vendor guess, which
// does not match the NVD CPE dictionary entry (vendor: microsoft); replace it outright so the
// mismatched candidate doesn't win on specificity and get selected as the primary CPE. Delimiter
// variations are disallowed so this exact, known-correct value isn't out-ranked by a hyphenated form.
vendors.clear()
vendors.add(fieldCandidate{value: "microsoft", disallowSubSelections: true, disallowDelimiterVariations: true})
}
}

// candidateProductsForPE returns product candidates for PE (BinaryPkg) packages based on common metadata hints.
// Specifically, normalize Ghostscript binaries to product "ghostscript" when detected.
func candidateProductsForPE(p pkg.Package) fieldCandidateSet {
candidates := newFieldCandidateSet()

// candidateProductsForPE adds product candidates for PE (BinaryPkg) packages based on common metadata hints,
// mutating the given set in place. Specifically, normalize Ghostscript binaries to product "ghostscript" when detected.
func candidateProductsForPE(p pkg.Package, products fieldCandidateSet) {
meta, ok := p.Metadata.(pkg.PEBinary)
if !ok {
return candidates
return
}

var product, fileDesc string
Expand All @@ -61,13 +72,20 @@ func candidateProductsForPE(p pkg.Package) fieldCandidateSet {
}

if strings.Contains(product, "ghostscript") || strings.Contains(fileDesc, "ghostscript") {
candidates.addValue("ghostscript")
products.addValue("ghostscript")
}

if product == "git" || fileDesc == "git setup" {
candidates.addValue("git_for_windows")
candidates.addValue("git")
products.addValue("git_for_windows")
products.addValue("git")
}

return candidates
if isMicrosoftEdgePE(product, fileDesc) {
// the raised package name ("Microsoft Edge") is otherwise used verbatim as the product guess, which
// does not match the NVD CPE dictionary entry (product: edge_chromium); replace it outright so the
// mismatched candidate doesn't win on specificity and get selected as the primary CPE. Delimiter
// variations are disallowed so this exact, known-correct value isn't out-ranked by a hyphenated form.
products.clear()
products.add(fieldCandidate{value: "edge_chromium", disallowSubSelections: true, disallowDelimiterVariations: true})
}
}
4 changes: 2 additions & 2 deletions syft/pkg/cataloger/internal/cpegenerate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ func candidateVendorsByType(p pkg.Package, vendors fieldCandidateSet) fieldCandi
vendors.union(candidateVendorsForJavascript(p))
case pkg.PEBinary:
// Add PE-specific vendor hints (e.g. ghostscript -> artifex)
vendors.union(candidateVendorsForPE(p))
candidateVendorsForPE(p, vendors)
case pkg.WordpressPluginEntry:
vendors.clear()
vendors.union(candidateVendorsForWordpressPlugin(p))
Expand Down Expand Up @@ -328,7 +328,7 @@ func candidateProductSet(p pkg.Package) fieldCandidateSet {
products.union(candidateProductsForAPK(p))
case pkg.PEBinary:
// Add PE-specific product hints (e.g. ghostscript)
products.union(candidateProductsForPE(p))
candidateProductsForPE(p, products)
case pkg.WordpressPluginEntry:
products.clear()
products.union(candidateProductsForWordpressPlugin(p))
Expand Down
34 changes: 34 additions & 0 deletions syft/pkg/cataloger/internal/cpegenerate/pe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,37 @@ func TestGhostscriptPEGeneratesArtifexCPE(t *testing.T) {
t.Fatalf("expected to find CPE with vendor 'artifex' and product 'ghostscript' for Ghostscript PE binary; got: %+v", cpes)
}
}

func TestMicrosoftEdgePEGeneratesCorrectCPE(t *testing.T) {
// construct a BinaryPkg with PE metadata resembling Microsoft Edge (e.g. msedge.exe)
p := pkg.Package{
Name: "Microsoft Edge",
Version: "122.0.2365.106",
Type: pkg.BinaryPkg,
Metadata: pkg.PEBinary{
VersionResources: pkg.KeyValues{
{Key: "CompanyName", Value: "Microsoft Corporation"},
{Key: "ProductName", Value: "Microsoft Edge"},
{Key: "FileDescription", Value: "Microsoft Edge"},
},
},
}

cpes := FromPackageAttributes(p)
if len(cpes) == 0 {
t.Fatalf("expected at least one CPE, got none")
}

// the first CPE is the one selected as the package's primary CPE in output formats (e.g. CycloneDX),
// so it must be the correct vendor/product pairing and not the raw "Microsoft_Edge:Microsoft_Edge" guess.
got := cpes[0].Attributes
if got.Vendor != "microsoft" || got.Product != "edge_chromium" || got.Version != p.Version {
t.Fatalf("expected primary CPE vendor 'microsoft' and product 'edge_chromium' for Microsoft Edge PE binary; got: %+v", cpes)
}

for _, c := range cpes {
if c.Attributes.Vendor == "microsoft_edge" || c.Attributes.Product == "microsoft_edge" {
t.Fatalf("did not expect a CPE derived verbatim from the raised package name; got: %+v", cpes)
}
}
}