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

fix: ensure that affected entries are in order before comparing #198

Merged
merged 2 commits into from
Aug 19, 2023
Merged
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
32 changes: 32 additions & 0 deletions pkg/database/osv.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/g-rath/osv-detector/pkg/semantic"
"os"
"regexp"
"sort"
"strings"
"time"
"unicode"
Expand Down Expand Up @@ -58,6 +59,22 @@ type AffectsRange struct {
Events []RangeEvent `json:"events"`
}

func (e RangeEvent) version() string {
if e.Introduced != "" {
return e.Introduced
}

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

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

return ""
}

func (ar AffectsRange) containsVersion(pkg internal.PackageDetails) bool {
if ar.Type != TypeEcosystem && ar.Type != TypeSemver {
return false
Expand All @@ -69,6 +86,21 @@ func (ar AffectsRange) containsVersion(pkg internal.PackageDetails) bool {

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(a.version(), pkg.CompareAs).CompareStr(b.version()) < 0
})

var affected bool
for _, e := range ar.Events {
if affected {
Expand Down
229 changes: 229 additions & 0 deletions pkg/database/osv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ func expectIsAffected(t *testing.T, osv database.OSV, version string, expectAffe
}

if osv.IsAffected(pkg) != expectAffected {
if version == "" {
version = "<empty>"
}

if expectAffected {
t.Errorf("Expected OSV to affect package version %s but it did not", version)
} else {
Expand Down Expand Up @@ -280,6 +284,40 @@ func TestOSV_IsAffected_AffectsWithEcosystem_SingleAffected(t *testing.T) {
// an empty version should always be treated as affected
expectIsAffected(t, osv, "", true)

// multiple fixes and introduced, shuffled
osv = buildOSVWithAffected(
database.Affected{
Package: database.Package{Ecosystem: lockfile.NpmEcosystem, Name: "my-package"},
Ranges: []database.AffectsRange{
buildEcosystemAffectsRange(
database.RangeEvent{Introduced: "0"},
database.RangeEvent{Introduced: "2.1.0"},
database.RangeEvent{Fixed: "3.2.0"},
database.RangeEvent{Fixed: "1"},
),
},
},
)

for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
expectIsAffected(t, osv, v, true)
}

for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
expectIsAffected(t, osv, v, false)
}

for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
expectIsAffected(t, osv, v, true)
}

for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
expectIsAffected(t, osv, v, false)
}

// an empty version should always be treated as affected
expectIsAffected(t, osv, "", true)

// "LastAffected: 1" means all versions after this are not vulnerable
osv = buildOSVWithAffected(
database.Affected{
Expand Down Expand Up @@ -337,6 +375,40 @@ func TestOSV_IsAffected_AffectsWithEcosystem_SingleAffected(t *testing.T) {

// an empty version should always be treated as affected
expectIsAffected(t, osv, "", true)

// mix of fixes, last_known_affected, and introduced, shuffled
osv = buildOSVWithAffected(
database.Affected{
Package: database.Package{Ecosystem: lockfile.NpmEcosystem, Name: "my-package"},
Ranges: []database.AffectsRange{
buildEcosystemAffectsRange(
database.RangeEvent{Introduced: "0"},
database.RangeEvent{Introduced: "2.1.0"},
database.RangeEvent{Fixed: "1"},
database.RangeEvent{LastAffected: "3.1.9"},
),
},
},
)

for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
expectIsAffected(t, osv, v, true)
}

for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
expectIsAffected(t, osv, v, false)
}

for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc", "3.1.9"} {
expectIsAffected(t, osv, v, true)
}

for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
expectIsAffected(t, osv, v, false)
}

// an empty version should always be treated as affected
expectIsAffected(t, osv, "", true)
}

func TestOSV_IsAffected_AffectsWithEcosystem_MultipleAffected(t *testing.T) {
Expand Down Expand Up @@ -394,6 +466,95 @@ func TestOSV_IsAffected_AffectsWithEcosystem_MultipleAffected(t *testing.T) {

// an empty version should always be treated as affected
expectIsAffected(t, osv, "", true)

// shuffled
osv = buildOSVWithAffected(
database.Affected{
Package: database.Package{Ecosystem: lockfile.NpmEcosystem, Name: "my-package"},
Ranges: []database.AffectsRange{
buildEcosystemAffectsRange(
database.RangeEvent{Fixed: "1"},
database.RangeEvent{Introduced: "0"},
),
},
},
database.Affected{
Package: database.Package{Ecosystem: lockfile.NpmEcosystem, Name: "my-package"},
Ranges: []database.AffectsRange{
buildEcosystemAffectsRange(
database.RangeEvent{Fixed: "3.2.0"},
database.RangeEvent{Introduced: "2.1.0"},
),
},
},
database.Affected{
Package: database.Package{Ecosystem: lockfile.NpmEcosystem, Name: "my-package"},
Ranges: []database.AffectsRange{
buildEcosystemAffectsRange(
database.RangeEvent{LastAffected: "3.5.0"},
database.RangeEvent{Introduced: "3.3.0"},
),
},
},
)

for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
expectIsAffected(t, osv, v, true)
}

for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
expectIsAffected(t, osv, v, false)
}

for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
expectIsAffected(t, osv, v, true)
}

for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
expectIsAffected(t, osv, v, false)
}

for _, v := range []string{"3.3.1", "3.4.5"} {
expectIsAffected(t, osv, v, true)
}

// an empty version should always be treated as affected
expectIsAffected(t, osv, "", true)

// zeros with build strings
osv = buildOSVWithAffected(
database.Affected{
// golang.org/x/sys
Package: database.Package{Ecosystem: lockfile.NpmEcosystem, Name: "my-package"},
Ranges: []database.AffectsRange{
buildEcosystemAffectsRange(
database.RangeEvent{Fixed: "0.0.0-20220412211240-33da011f77ad"},
database.RangeEvent{Introduced: "0"},
),
},
},
database.Affected{
// golang.org/x/net
Package: database.Package{Ecosystem: lockfile.NpmEcosystem, Name: "my-package"},
Ranges: []database.AffectsRange{
buildEcosystemAffectsRange(
database.RangeEvent{Introduced: "0.0.0-20180925071336-cf3bd585ca2a"},
database.RangeEvent{Fixed: "0"},
),
},
},
)

for _, v := range []string{"0.0.0", "0.14.0"} {
expectIsAffected(t, osv, v, false)
}

for _, v := range []string{"0.0.0-20180925071336-cf3bd585ca2a"} {
expectIsAffected(t, osv, v, true)
}

// an empty version should always be treated as affected
expectIsAffected(t, osv, "", true)
}

func TestOSV_IsAffected_AffectsWithEcosystem_PipNamesAreNormalised(t *testing.T) {
Expand Down Expand Up @@ -540,6 +701,40 @@ func TestOSV_IsAffected_AffectsWithSemver_SingleAffected(t *testing.T) {
// an empty version should always be treated as affected
expectIsAffected(t, osv, "", true)

// multiple fixes and introduced, shuffled
osv = buildOSVWithAffected(
database.Affected{
Package: database.Package{Ecosystem: lockfile.NpmEcosystem, Name: "my-package"},
Ranges: []database.AffectsRange{
buildSemverAffectsRange(
database.RangeEvent{Fixed: "1"},
database.RangeEvent{Fixed: "3.2.0"},
database.RangeEvent{Introduced: "0"},
database.RangeEvent{Introduced: "2.1.0"},
),
},
},
)

for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
expectIsAffected(t, osv, v, true)
}

for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
expectIsAffected(t, osv, v, false)
}

for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
expectIsAffected(t, osv, v, true)
}

for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
expectIsAffected(t, osv, v, false)
}

// an empty version should always be treated as affected
expectIsAffected(t, osv, "", true)

// "LastAffected: 1" means all versions after this are not vulnerable
osv = buildOSVWithAffected(
database.Affected{
Expand Down Expand Up @@ -594,6 +789,40 @@ func TestOSV_IsAffected_AffectsWithSemver_SingleAffected(t *testing.T) {

// an empty version should always be treated as affected
expectIsAffected(t, osv, "", true)

// mix of fixes, last_known_affected, and introduced, shuffled
osv = buildOSVWithAffected(
database.Affected{
Package: database.Package{Ecosystem: lockfile.NpmEcosystem, Name: "my-package"},
Ranges: []database.AffectsRange{
buildSemverAffectsRange(
database.RangeEvent{Introduced: "2.1.0"},
database.RangeEvent{Introduced: "0"},
database.RangeEvent{LastAffected: "3.1.9"},
database.RangeEvent{Fixed: "1"},
),
},
},
)

for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
expectIsAffected(t, osv, v, true)
}

for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
expectIsAffected(t, osv, v, false)
}

for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
expectIsAffected(t, osv, v, true)
}

for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
expectIsAffected(t, osv, v, false)
}

// an empty version should always be treated as affected
expectIsAffected(t, osv, "", true)
}

func TestOSV_IsAffected_AffectsWithSemver_MultipleAffected(t *testing.T) {
Expand Down