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
21 changes: 20 additions & 1 deletion grant/license.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package grant

import (
"fmt"
"strings"

"github.com/github/go-spdx/v2/spdxexp"
Expand Down Expand Up @@ -75,8 +76,26 @@ func ConvertSyftLicenses(set syftPkg.LicenseSet) (licenses []License) {
return licenses
}

// safeExtractLicenses wraps spdxexp.ExtractLicenses so a malformed SPDX
// expression cannot take down the whole scan. The upstream parser
// (github.com/github/go-spdx) panics on some inputs, for example an expression
// with a dangling opening parenthesis like "MIT AND (". SBOM license fields
// flow through here unvalidated, so a single malformed value in an SBOM would
// otherwise crash grant. syft guards its own call to this parser for
// the same reason (see anchore/syft#1837); grant does the same here and lets the
// existing error path fall back to treating the value as a non-SPDX license.
func safeExtractLicenses(expression string) (extracted []string, err error) {
defer func() {
if r := recover(); r != nil {
extracted = nil
err = fmt.Errorf("recovered from panic parsing SPDX expression %q: %v", expression, r)
}
}()
return spdxexp.ExtractLicenses(expression)
}

func handleSPDXLicense(license syftPkg.License, licenses []License, licenseLocations []string, checked map[string]bool) []License {
extractedLicenses, err := spdxexp.ExtractLicenses(license.SPDXExpression)
extractedLicenses, err := safeExtractLicenses(license.SPDXExpression)
if err != nil {
// log.Errorf("unable to extract licenses from SPDX expression: %s", license.SPDXExpression)
return addNonSPDXLicense(licenses, license, licenseLocations)
Expand Down
44 changes: 44 additions & 0 deletions grant/license_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package grant

import (
"testing"

syftPkg "github.com/anchore/syft/syft/pkg"
)

// TestConvertSyftLicenses_MalformedSPDXExpressionDoesNotPanic feeds a license
// whose SPDXExpression is a malformed expression with a dangling opening
// parenthesis. This is the shape that reaches handleSPDXLicense when grant
// decodes an SBOM whose license field is malformed. The upstream SPDX parser
// panics on this input, so without the guard in handleSPDXLicense the whole scan
// would crash. The expression should instead fall back to a non-SPDX license.
func TestConvertSyftLicenses_MalformedSPDXExpressionDoesNotPanic(t *testing.T) {
malformed := []string{
"MIT AND (",
"(",
"(((((",
"MIT OR (",
}

for _, expr := range malformed {
expr := expr
t.Run(expr, func(t *testing.T) {
set := syftPkg.NewLicenseSet(syftPkg.License{Value: expr, SPDXExpression: expr})

// must not panic
got := ConvertSyftLicenses(set)

if len(got) != 1 {
t.Fatalf("expected 1 license, got %d", len(got))
}
// the malformed expression should be carried as a plain name, not
// treated as a valid SPDX expression
if got[0].IsSPDX() {
t.Fatalf("expected malformed expression %q to fall back to a non-SPDX license, got SPDX license %q", expr, got[0].SPDXExpression)
}
if got[0].Name != expr {
t.Fatalf("expected fallback license name %q, got %q", expr, got[0].Name)
}
})
}
}