Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add osv output lockfile + refactor #505

Merged
merged 14 commits into from
Aug 30, 2023
2 changes: 2 additions & 0 deletions cmd/osv-scanner/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ type cliTestCase struct {
func testCli(t *testing.T, tc cliTestCase) {
t.Helper()

t.Logf("Running test: [%s]", tc.name)
another-rex marked this conversation as resolved.
Show resolved Hide resolved

stdoutBuffer := &bytes.Buffer{}
stderrBuffer := &bytes.Buffer{}

Expand Down
3 changes: 2 additions & 1 deletion internal/local/zip.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"path"
"strings"

"github.com/google/osv-scanner/internal/modelsutil"
"github.com/google/osv-scanner/pkg/lockfile"
"github.com/google/osv-scanner/pkg/models"
"github.com/google/osv-scanner/pkg/osv"
Expand Down Expand Up @@ -235,7 +236,7 @@ func (db *ZipDB) VulnerabilitiesAffectingPackage(pkg lockfile.PackageDetails) mo
var vulnerabilities models.Vulnerabilities

for _, vulnerability := range db.Vulnerabilities(false) {
if vulnerability.IsAffected(pkg) && !vulnerabilities.Includes(vulnerability) {
if modelsutil.IsAffected(vulnerability, pkg) && !modelsutil.VulnsInclude(vulnerabilities, vulnerability) {
vulnerabilities = append(vulnerabilities, vulnerability)
}
}
Expand Down
20 changes: 20 additions & 0 deletions internal/modelsutil/vulnerabilities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package modelsutil
oliverchang marked this conversation as resolved.
Show resolved Hide resolved

import "github.com/google/osv-scanner/pkg/models"

func VulnsInclude(vs models.Vulnerabilities, vulnerability models.Vulnerability) bool {
for _, vuln := range vs {
if vuln.ID == vulnerability.ID {
return true
}

if vulnIsAliasOf(vuln, vulnerability) {
return true
}
if vulnIsAliasOf(vulnerability, vuln) {
return true
}
}

return false
}
129 changes: 129 additions & 0 deletions internal/modelsutil/vulnerabilities_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package modelsutil_test

import (
"testing"

"github.com/google/osv-scanner/internal/modelsutil"
"github.com/google/osv-scanner/pkg/models"
)

func TestVulnerabilities_Includes(t *testing.T) {
t.Parallel()

type args struct {
osv models.Vulnerability
}
tests := []struct {
name string
vs models.Vulnerabilities
args args
want bool
}{
{
name: "",
vs: models.Vulnerabilities{
models.Vulnerability{
ID: "GHSA-1",
Aliases: []string{},
},
},
args: args{
osv: models.Vulnerability{
ID: "GHSA-2",
Aliases: []string{},
},
},
want: false,
},
{
name: "",
vs: models.Vulnerabilities{
models.Vulnerability{
ID: "GHSA-1",
Aliases: []string{},
},
},
args: args{
osv: models.Vulnerability{
ID: "GHSA-1",
Aliases: []string{},
},
},
want: true,
},
{
name: "",
vs: models.Vulnerabilities{
models.Vulnerability{
ID: "GHSA-1",
Aliases: []string{"GHSA-2"},
},
},
args: args{
osv: models.Vulnerability{
ID: "GHSA-2",
Aliases: []string{},
},
},
want: true,
},
{
name: "",
vs: models.Vulnerabilities{
models.Vulnerability{
ID: "GHSA-1",
Aliases: []string{},
},
},
args: args{
osv: models.Vulnerability{
ID: "GHSA-2",
Aliases: []string{"GHSA-1"},
},
},
want: true,
},
{
name: "",
vs: models.Vulnerabilities{
models.Vulnerability{
ID: "GHSA-1",
Aliases: []string{"CVE-1"},
},
},
args: args{
osv: models.Vulnerability{
ID: "GHSA-2",
Aliases: []string{"CVE-1"},
},
},
want: true,
},
{
name: "",
vs: models.Vulnerabilities{
models.Vulnerability{
ID: "GHSA-1",
Aliases: []string{"CVE-2"},
},
},
args: args{
osv: models.Vulnerability{
ID: "GHSA-2",
Aliases: []string{"CVE-2"},
},
},
want: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

if got := modelsutil.VulnsInclude(tt.vs, tt.args.osv); got != tt.want {
t.Errorf("Includes() = %v, want %v", got, tt.want)
}
})
}
}
152 changes: 152 additions & 0 deletions internal/modelsutil/vulnerability.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package modelsutil

import (
"fmt"
"os"
"sort"

"github.com/google/osv-scanner/internal/semantic"
"github.com/google/osv-scanner/pkg/lockfile"
"github.com/google/osv-scanner/pkg/models"
"golang.org/x/exp/slices"
)

func eventVersion(e models.Event) string {
if e.Introduced != "" {
return e.Introduced
}

if e.Fixed != "" {
return e.Fixed
}

if e.Limit != "" {
return e.Limit
}

if e.LastAffected != "" {
return e.LastAffected
}

return ""
}

func rangeContainsVersion(ar models.Range, pkg lockfile.PackageDetails) bool {
if ar.Type != models.RangeEcosystem && ar.Type != models.RangeSemVer {
return false
}
// todo: we should probably warn here
if len(ar.Events) == 0 {
return false
}

vp := semantic.MustParse(pkg.Version, pkg.CompareAs)

sort.Slice(ar.Events, func(i, j int) bool {
a := ar.Events[i]
b := ar.Events[j]

if a.Introduced == "0" {
return true
}

if b.Introduced == "0" {
return false
}

return semantic.MustParse(eventVersion(a), pkg.CompareAs).CompareStr(eventVersion(b)) < 0
})

var affected bool
for _, e := range ar.Events {
if affected {
if e.Fixed != "" {
affected = vp.CompareStr(e.Fixed) < 0
} else if e.LastAffected != "" {
affected = e.LastAffected == pkg.Version || vp.CompareStr(e.LastAffected) <= 0
}
} else if e.Introduced != "" {
affected = e.Introduced == "0" || vp.CompareStr(e.Introduced) >= 0
}
}

return affected
}

// rangeAffectsVersion checks if the given version is within the range
// specified by the events of any "Ecosystem" or "Semver" type ranges
func rangeAffectsVersion(a []models.Range, pkg lockfile.PackageDetails) bool {
for _, r := range a {
if r.Type != models.RangeEcosystem && r.Type != models.RangeSemVer {
return false
}
if rangeContainsVersion(r, pkg) {
return true
}
}

return false
}

func vulnIsAliasOfID(v models.Vulnerability, id string) bool {
for _, alias := range v.Aliases {
if alias == id {
return true
}
}

return false
}

func vulnIsAliasOf(v models.Vulnerability, vulnerability models.Vulnerability) bool {
for _, alias := range vulnerability.Aliases {
if v.ID == alias || vulnIsAliasOfID(v, alias) {
return true
}
}

return false
}

func AffectsEcosystem(v models.Vulnerability, ecosystem lockfile.Ecosystem) bool {
for _, affected := range v.Affected {
if string(affected.Package.Ecosystem) == string(ecosystem) {
return true
}
}

return false
}

func IsAffected(v models.Vulnerability, pkg lockfile.PackageDetails) bool {
for _, affected := range v.Affected {
if string(affected.Package.Ecosystem) == string(pkg.Ecosystem) &&
affected.Package.Name == pkg.Name {
if len(affected.Ranges) == 0 && len(affected.Versions) == 0 {
_, _ = fmt.Fprintf(
os.Stderr,
"%s does not have any ranges or versions - this is probably a mistake!\n",
v.ID,
)

continue
}

if slices.Contains(affected.Versions, pkg.Version) {
return true
}

if rangeAffectsVersion(affected.Ranges, pkg) {
return true
}

// if a package does not have a version, assume it is vulnerable
// as false positives are better than false negatives here
if pkg.Version == "" {
return true
}
}
}

return false
}
Loading