Skip to content

Commit

Permalink
More specific cyclone dx parsing (google#258)
Browse files Browse the repository at this point in the history
Make sure the file name follows the recognized file names here:
https://cyclonedx.org/specification/overview/#recognized-file-patterns

Resolves google#257 

Also 
- adds tests for sboms,
- makes SBOM scan logging output consistent with lockfile scan logging
output
- Minor refactor of SBOMs

I believe we will also want something similar to parse-as in lockfiles
for SBOMs as well in the future to allow file names that doesn't conform
to the standard to be scanned.
  • Loading branch information
another-rex authored and julieqiu committed May 2, 2023
1 parent a9a5a24 commit 3dc655a
Show file tree
Hide file tree
Showing 7 changed files with 5,785 additions and 15 deletions.
604 changes: 604 additions & 0 deletions cmd/osv-scanner/fixtures/locks-many/alpine.cdx.xml

Large diffs are not rendered by default.

5,110 changes: 5,110 additions & 0 deletions cmd/osv-scanner/fixtures/sbom-insecure/postgres-stretch.cdx.xml

Large diffs are not rendered by default.

23 changes: 22 additions & 1 deletion cmd/osv-scanner/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,27 @@ func TestRun(t *testing.T) {
wantExitCode: 0,
wantStdout: `
Scanning dir ./fixtures/locks-many/composer.lock
Scanned %%/fixtures/locks-many/composer.lock file and found 1 packages
Scanned %%/fixtures/locks-many/composer.lock file and found 1 packages
`,
wantStderr: "",
},
// one specific supported sbom with vulns
{
name: "",
args: []string{"", "./fixtures/sbom-insecure/postgres-stretch.cdx.xml"},
wantExitCode: 1,
wantStdout: `
Scanning dir ./fixtures/sbom-insecure/postgres-stretch.cdx.xml
Scanned %%/fixtures/sbom-insecure/postgres-stretch.cdx.xml as CycloneDX SBOM and found 136 packages
+-------------------------------------+-----------+---------+------------------------------------+-------------------------------------------------+
| OSV URL (ID IN BOLD) | ECOSYSTEM | PACKAGE | VERSION | SOURCE |
+-------------------------------------+-----------+---------+------------------------------------+-------------------------------------------------+
| https://osv.dev/GHSA-v95c-p5hm-xq8f | Go | runc | v1.0.1 | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
| https://osv.dev/GO-2022-0274 | | | | |
| https://osv.dev/GHSA-f3fp-gc8g-vw66 | Go | runc | v1.0.1 | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
| https://osv.dev/GHSA-p782-xgp4-8hr8 | Go | sys | v0.0.0-20210817142637-7d9622a276b7 | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
| https://osv.dev/GO-2022-0493 | | | | |
+-------------------------------------+-----------+---------+------------------------------------+-------------------------------------------------+
`,
wantStderr: "",
},
Expand All @@ -152,6 +172,7 @@ func TestRun(t *testing.T) {
wantStdout: `
Scanning dir ./fixtures/locks-many
Scanned %%/fixtures/locks-many/Gemfile.lock file and found 1 packages
Scanned %%/fixtures/locks-many/alpine.cdx.xml as CycloneDX SBOM and found 15 packages
Scanned %%/fixtures/locks-many/composer.lock file and found 1 packages
Scanned %%/fixtures/locks-many/yarn.lock file and found 1 packages
`,
Expand Down
25 changes: 25 additions & 0 deletions internal/sbom/cyclonedx.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sbom
import (
"fmt"
"io"
"path/filepath"
"strings"

"github.com/CycloneDX/cyclonedx-go"
Expand All @@ -21,6 +22,30 @@ func (c *CycloneDX) Name() string {
return "CycloneDX"
}

func (c *CycloneDX) MatchesRecognizedFileNames(path string) bool {
// See https://cyclonedx.org/specification/overview/#recognized-file-patterns
expectedGlobs := []string{
"bom.xml",
"bom.json",
"*.cdx.json",
"*.cdx.xml",
}
filename := filepath.Base(path)
for _, v := range expectedGlobs {
matched, err := filepath.Match(v, filename)
if err != nil {
// Just panic since the only error is invalid glob pattern
panic("Glob pattern is invalid: " + err.Error())
}

if matched {
return true
}
}

return false
}

func (c *CycloneDX) enumerateComponents(components []cyclonedx.Component, callback func(Identifier) error) error {
for _, component := range components {
if component.PackageURL != "" {
Expand Down
2 changes: 2 additions & 0 deletions internal/sbom/sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type Identifier struct {
// SBOMReader is an interface for all SBOM providers.
type SBOMReader interface {
Name() string
// Checks if the file path is a standard recognized file name
MatchesRecognizedFileNames(string) bool
GetPackages(io.ReadSeeker, func(Identifier) error) error
}

Expand Down
10 changes: 9 additions & 1 deletion internal/sbom/spdx.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ package sbom
import (
"fmt"
"io"
"path/filepath"
"strings"

"github.com/spdx/tools-golang/json"
spdx_json "github.com/spdx/tools-golang/json"
"github.com/spdx/tools-golang/rdfloader"
"github.com/spdx/tools-golang/spdx/v2_3"
"github.com/spdx/tools-golang/tvloader"
Expand All @@ -26,6 +28,12 @@ func (s *SPDX) Name() string {
return "SPDX"
}

func (s *SPDX) MatchesRecognizedFileNames(path string) bool {
// All spdx files should have the .spdx in the filename, even if
// it's not the extension: https://spdx.github.io/spdx-spec/v2.3/conformance/
return strings.Contains(strings.ToLower(filepath.Base(path)), ".spdx")
}

func (s *SPDX) enumeratePackages(doc *v2_3.Document, callback func(Identifier) error) error {
for _, p := range doc.Packages {
for _, r := range p.PackageExternalReferences {
Expand Down
26 changes: 13 additions & 13 deletions pkg/osvscanner/osvscanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,22 +222,22 @@ func scanLockfile(r *output.Reporter, query *osv.BatchedQuery, path string, pars
// scanSBOMFile will load, identify, and parse the SBOM path passed in, and add the dependencies specified
// within to `query`
func scanSBOMFile(r *output.Reporter, query *osv.BatchedQuery, path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()

for _, provider := range sbom.Providers {
if provider.Name() == "SPDX" &&
!strings.Contains(strings.ToLower(filepath.Base(path)), ".spdx") {
// All spdx files should have the .spdx in the filename, even if
// it's not the extension: https://spdx.github.io/spdx-spec/v2.3/conformance/
// Skip if this isn't the case to avoid panics
if !provider.MatchesRecognizedFileNames(path) {
// Skip if filename is not usually a sbom file of this format
continue
}

// Opening file inside loop is OK, since providers is not very long,
// and it is unlikely that multiple providers accept the same file name
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()

count := 0
err := provider.GetPackages(file, func(id sbom.Identifier) error {
err = provider.GetPackages(file, func(id sbom.Identifier) error {
purlQuery := osv.MakePURLRequest(id.PURL)
purlQuery.Source = models.SourceInfo{
Path: path,
Expand All @@ -250,7 +250,7 @@ func scanSBOMFile(r *output.Reporter, query *osv.BatchedQuery, path string) erro
})
if err == nil {
// Found the right format.
r.PrintText(fmt.Sprintf("Scanned %s SBOM and found %d packages\n", provider.Name(), count))
r.PrintText(fmt.Sprintf("Scanned %s as %s SBOM and found %d packages\n", path, provider.Name(), count))
return nil
}

Expand Down

0 comments on commit 3dc655a

Please sign in to comment.