blob: d5ea472e143e39be946656bbc39761e2677a3b35 [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package osv
import (
"time"
"golang.org/x/mod/semver"
"golang.org/x/vulndb/report"
)
// DBIndex contains a mapping of vulnerable packages to the
// last time a new vulnerability was added to the database.
// TODO: this is probably not the correct place to put this
// type, since it's not really an OSV/CVF thing, but rather
// vulndb implementatiion detail.
type DBIndex map[string]time.Time
type AffectsRangeType int
const (
TypeUnspecified AffectsRangeType = iota
TypeGit
TypeSemver
)
type Ecosystem string
const GoEcosystem Ecosystem = "go"
type Package struct {
Name string
Ecosystem Ecosystem
}
type AffectsRange struct {
Type AffectsRangeType
Introduced string
Fixed string
}
func (ar AffectsRange) containsSemver(v string) bool {
if ar.Type != TypeSemver {
return false
}
return (ar.Introduced == "" || semver.Compare(v, ar.Introduced) >= 0) &&
(ar.Fixed == "" || semver.Compare(v, ar.Fixed) < 0)
}
type Affects struct {
Ranges []AffectsRange
}
func generateAffects(versions []report.VersionRange) Affects {
a := Affects{}
for _, v := range versions {
a.Ranges = append(a.Ranges, AffectsRange{
Type: TypeSemver,
Introduced: v.Introduced,
Fixed: v.Fixed,
})
}
return a
}
func (a Affects) AffectsSemver(v string) bool {
if len(a.Ranges) == 0 {
// No ranges implies all versions are affected
return true
}
var semverRangePresent bool
for _, r := range a.Ranges {
if r.Type != TypeSemver {
continue
}
semverRangePresent = true
if r.containsSemver(v) {
return true
}
}
// If there were no semver ranges present we
// assume that all semvers are affected, similarly
// to how to we assume all semvers are affected
// if there are no ranges at all.
return !semverRangePresent
}
type GoSpecific struct {
Symbols []string `json:",omitempty"`
GOOS []string `json:",omitempty"`
GOARCH []string `json:",omitempty"`
URL string
}
type Reference struct {
Type string
URL string
}
// Entry represents a OSV style JSON vulnerability database
// entry
type Entry struct {
ID string
Published time.Time
Modified time.Time
Withdrawn *time.Time
Aliases []string `json:",omitempty"`
Package Package
Details string
Affects Affects
References []Reference `json:",omitempty"`
Extra struct {
Go GoSpecific
}
}
func Generate(id string, url string, r report.Report) []Entry {
importPath := r.Module
if r.Package != "" {
importPath = r.Package
}
lastModified := r.Published
if r.LastModified != nil {
lastModified = *r.LastModified
}
entry := Entry{
ID: id,
Published: r.Published,
Modified: lastModified,
Withdrawn: r.Withdrawn,
Package: Package{
Name: importPath,
Ecosystem: GoEcosystem,
},
Details: r.Description,
Affects: generateAffects(r.Versions),
Extra: struct{ Go GoSpecific }{
Go: GoSpecific{
Symbols: r.Symbols,
GOOS: r.OS,
GOARCH: r.Arch,
URL: url,
},
},
}
if r.Links.PR != "" {
entry.References = append(entry.References, Reference{Type: "code review", URL: r.Links.PR})
}
if r.Links.Commit != "" {
entry.References = append(entry.References, Reference{Type: "fix", URL: r.Links.Commit})
}
for _, link := range r.Links.Context {
entry.References = append(entry.References, Reference{Type: "misc", URL: link})
}
if r.CVE != "" {
entry.Aliases = []string{r.CVE}
}
entries := []Entry{entry}
// It would be better if this was just a recursive thing maybe?
for _, additional := range r.AdditionalPackages {
entryCopy := entry
additionalImportPath := additional.Module
if additional.Package != "" {
additionalImportPath = additional.Package
}
entryCopy.Package.Name = additionalImportPath
entryCopy.Extra.Go.Symbols = additional.Symbols
entryCopy.Affects = generateAffects(additional.Versions)
entries = append(entries, entryCopy)
}
return entries
}