internal/report: add quick fix for module merging issue
Previously, FixModules would attempt to merge versions even if it
resulted in an invalid version list (e.g., with overlapping ranges).
As a quick fix, bail out from the merge attempt if any of the
results wouldn't make sense, and add a note to the report.
This can be improved in the future by continuing to attempt to merge
modules that can be merged instead of bailing out early.
Change-Id: I5074cd3d04251423da9afa1a52933f0fbeab4b5f
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/580435
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/internal/genericosv/testdata/yaml/GHSA-pg5p-wwp8-97g8.yaml b/internal/genericosv/testdata/yaml/GHSA-pg5p-wwp8-97g8.yaml
index 3e0017a..7f2007e 100644
--- a/internal/genericosv/testdata/yaml/GHSA-pg5p-wwp8-97g8.yaml
+++ b/internal/genericosv/testdata/yaml/GHSA-pg5p-wwp8-97g8.yaml
@@ -3,15 +3,23 @@
- module: github.com/cilium/cilium
versions:
- introduced: 1.7.0
- - introduced: 1.11.0
- fixed: 1.11.16
- - introduced: 1.12.0
- fixed: 1.12.9
- - introduced: 1.13.0
- fixed: 1.13.2
unsupported_versions:
- version: 1.10.0
type: last_affected
+ - module: github.com/cilium/cilium
+ versions:
+ - introduced: 1.11.0
+ fixed: 1.11.16
+ vulnerable_at: 1.11.15
+ - module: github.com/cilium/cilium
+ versions:
+ - introduced: 1.12.0
+ fixed: 1.12.9
+ vulnerable_at: 1.12.8
+ - module: github.com/cilium/cilium
+ versions:
+ - introduced: 1.13.0
+ fixed: 1.13.2
vulnerable_at: 1.13.1
summary: Debug mode leaks confidential data in Cilium in github.com/cilium/cilium
description: |-
@@ -60,9 +68,9 @@
references:
- advisory: https://github.com/cilium/cilium/security/advisories/GHSA-pg5p-wwp8-97g8
notes:
+ - fix: 'module merge error: could not merge versions of module github.com/cilium/cilium: introduced and fixed versions must alternate'
- lint: 'description: possible markdown formatting (found ### )'
- lint: 'description: possible markdown formatting (found [Slack](https://docs.cilium.io/en/latest/community/community/#slack))'
- lint: 'modules[0] "github.com/cilium/cilium": unsupported_versions: found 1 (want none)'
- - lint: 'modules[0] "github.com/cilium/cilium": versions: introduced and fixed versions must alternate'
source:
id: GHSA-pg5p-wwp8-97g8
diff --git a/internal/report/fix.go b/internal/report/fix.go
index fb36aa0..a2dec4e 100644
--- a/internal/report/fix.go
+++ b/internal/report/fix.go
@@ -17,6 +17,7 @@
"golang.org/x/vulndb/internal/cveschema5"
"golang.org/x/vulndb/internal/ghsa"
"golang.org/x/vulndb/internal/osv"
+ "golang.org/x/vulndb/internal/osvutils"
"golang.org/x/vulndb/internal/proxy"
"golang.org/x/vulndb/internal/version"
)
@@ -86,9 +87,15 @@
}
m.VulnerableAt = fixVersion(m.VulnerableAt)
- sort.SliceStable(m.Versions, func(i, j int) bool {
- intro, fixed := m.Versions[i].Introduced, m.Versions[i].Fixed
- intro2, fixed2 := m.Versions[j].Introduced, m.Versions[j].Fixed
+ m.Versions = fixVersionRanges(m.Versions)
+
+ m.fixVulnerableAt(pc)
+}
+
+func fixVersionRanges(vrs []VersionRange) []VersionRange {
+ sort.SliceStable(vrs, func(i, j int) bool {
+ intro, fixed := vrs[i].Introduced, vrs[i].Fixed
+ intro2, fixed2 := vrs[j].Introduced, vrs[j].Fixed
switch {
case intro != "" && intro2 != "":
return version.Before(intro, intro2)
@@ -104,25 +111,25 @@
})
// Remove duplicate version ranges.
- m.Versions = slices.Compact(m.Versions)
+ vrs = slices.Compact(vrs)
// Collect together version ranges that don't need to be separate,
// e.g:
// [ {Introduced: 1.1.0}, {Fixed: 1.2.0} ] becomes
// [ {Introduced: 1.1.0, Fixed: 1.2.0} ].
- for i := 0; i < len(m.Versions); i++ {
+ for i := 0; i < len(vrs); i++ {
if i != 0 {
- current, prev := m.Versions[i], m.Versions[i-1]
+ current, prev := vrs[i], vrs[i-1]
if (prev.Introduced != "" && prev.Fixed == "") &&
(current.Introduced == "" && current.Fixed != "") {
- m.Versions[i-1].Fixed = current.Fixed
- m.Versions = append(m.Versions[:i], m.Versions[i+1:]...)
+ vrs[i-1].Fixed = current.Fixed
+ vrs = append(vrs[:i], vrs[i+1:]...)
i--
}
}
}
- m.fixVulnerableAt(pc)
+ return vrs
}
func (m *Module) fixVulnerableAt(pc *proxy.Client) {
@@ -265,7 +272,12 @@
canonicalize(m, pc)
}
- r.Modules = merge(r.Modules)
+ merged, err := merge(r.Modules)
+ if err != nil {
+ r.AddNote(NoteTypeFix, "module merge error: %s", err)
+ } else {
+ r.Modules = merged
+ }
// Fix the versions *after* the modules have been merged.
for _, m := range r.Modules {
@@ -487,7 +499,7 @@
// merge merges all modules with the same module & package info
// (but possibly different versions) into one.
-func merge(ms []*Module) []*Module {
+func merge(ms []*Module) ([]*Module, error) {
type compMod struct {
path string
packages string // sorted, comma separated list of package names
@@ -504,15 +516,19 @@
}
}
- merge := func(m1, m2 *Module) *Module {
- // only run if m1 and m2 are same except versions
- // deletes vulnerable_at if set
+ // only run if m1 and m2 are same except versions
+ // deletes vulnerable_at if set
+ merge := func(m1, m2 *Module) (*Module, error) {
+ versions, err := mergeVersionRanges(m1.Versions, m2.Versions)
+ if err != nil {
+ return nil, fmt.Errorf("could not merge versions of module %s: %w", m1.Module, err)
+ }
return &Module{
Module: m1.Module,
- Versions: append(m1.Versions, m2.Versions...),
+ Versions: versions,
UnsupportedVersions: append(m1.UnsupportedVersions, m2.UnsupportedVersions...),
Packages: m1.Packages,
- }
+ }, nil
}
modules := make(map[compMod]*Module)
@@ -522,11 +538,27 @@
if !ok {
modules[c] = m
} else {
- modules[c] = merge(mod, m)
+ merged, err := merge(mod, m)
+ if err != nil {
+ // For now, bail out if any module can't be merged.
+ // This could be improved by continuing to try even if
+ // some merges fail.
+ return nil, err
+ }
+ modules[c] = merged
}
}
- return maps.Values(modules)
+ return maps.Values(modules), nil
+}
+
+func mergeVersionRanges(v1 []VersionRange, v2 []VersionRange) ([]VersionRange, error) {
+ v := append(v1, v2...)
+ v = fixVersionRanges(v)
+ if err := osvutils.ValidateRanges(AffectedRanges(v)); err != nil {
+ return nil, err
+ }
+ return v, nil
}
func first(vrs []VersionRange) string {
diff --git a/internal/report/testdata/cve/TestCVE5ToReport/CVE-2023-45283.txtar b/internal/report/testdata/cve/TestCVE5ToReport/CVE-2023-45283.txtar
index b87d895..1b3fa7a 100644
--- a/internal/report/testdata/cve/TestCVE5ToReport/CVE-2023-45283.txtar
+++ b/internal/report/testdata/cve/TestCVE5ToReport/CVE-2023-45283.txtar
@@ -22,12 +22,8 @@
- module: std
versions:
- fixed: 1.20.11
- - introduced: 1.20.11
- fixed: 1.20.12
- introduced: 1.21.0-0
fixed: 1.21.4
- - introduced: 1.21.4
- fixed: 1.21.5
packages:
- package: path/filepath
goos:
@@ -48,6 +44,31 @@
- VolumeName
- Walk
- WalkDir
+ - module: std
+ versions:
+ - introduced: 1.20.11
+ fixed: 1.20.12
+ - introduced: 1.21.4
+ fixed: 1.21.5
+ packages:
+ - package: path/filepath
+ goos:
+ - windows
+ symbols:
+ - volumeNameLen
+ - Abs
+ - Base
+ - Clean
+ - Dir
+ - EvalSymlinks
+ - Glob
+ - IsLocal
+ - Join
+ - Rel
+ - Split
+ - VolumeName
+ - Walk
+ - WalkDir
summary: Insecure parsing of Windows paths with a \??\ prefix in path/filepath
description: |-
The filepath package does not recognize paths with a \??\ prefix as special. On
@@ -75,5 +96,7 @@
cve_metadata:
id: CVE-2023-45283
cwe: 'CWE-41: Improper Resolution of Path Equivalence'
+notes:
+ - fix: 'module merge error: could not merge versions of module std: range events must be in strictly ascending order (found 1.20.11>=1.20.11)'
source:
id: CVE-2023-45283