-
Notifications
You must be signed in to change notification settings - Fork 363
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Pulled refactor out to a separate PR as suggested here: #505 In this PR: - Refactored models package to no longer depend on lockfile - Moved functions that required lockfile types into separate internal package under `utility/vulns` - Copied over OSV url from `osv` package to avoid dependency on `osv` from `output`/`reporter` I went through and graphed out the dependencies, - `models` no longer depends on any internal dependencies - `lockfile` doesn't depend on any internal dependencies apart from `cacheexp` - `osv` brings in both `lockfiles` and `models`. This is probably the most problematic one. Ideally `osv` should only be referencing `models`, but it'll be a breaking change to change this API. This is one of the main things we should change for V2 (related to #442) (TODO: Make separate issue for this - `reporter` depends on the internal `output`, which depends on `osv`. This dependency causes `lockfile` to be pulled into everywhere that needs to print out anything. This can be decoupled without too much trouble by copying in the OSV url. I did so in this PR. - `config` package references `reporter`, with the above change it also no longer depends on `lockfile`.
- Loading branch information
1 parent
e1bb554
commit 5a316f5
Showing
10 changed files
with
1,015 additions
and
986 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package vulns | ||
|
||
import "github.com/google/osv-scanner/pkg/models" | ||
|
||
func Include(vs models.Vulnerabilities, vulnerability models.Vulnerability) bool { | ||
for _, vuln := range vs { | ||
if vuln.ID == vulnerability.ID { | ||
return true | ||
} | ||
|
||
if isAliasOf(vuln, vulnerability) { | ||
return true | ||
} | ||
if isAliasOf(vulnerability, vuln) { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package vulns_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/google/osv-scanner/internal/utility/vulns" | ||
"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 := vulns.Include(tt.vs, tt.args.osv); got != tt.want { | ||
t.Errorf("Includes() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package vulns | ||
|
||
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 isAliasOfID(v models.Vulnerability, id string) bool { | ||
for _, alias := range v.Aliases { | ||
if alias == id { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
func isAliasOf(v models.Vulnerability, vulnerability models.Vulnerability) bool { | ||
for _, alias := range vulnerability.Aliases { | ||
if v.ID == alias || isAliasOfID(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 | ||
} |
Oops, something went wrong.