2022-11-09 00:13:39 +03:00
|
|
|
// Copyright 2022 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.
|
|
|
|
|
2022-11-19 00:26:39 +03:00
|
|
|
package report
|
2022-11-09 00:13:39 +03:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"os"
|
2022-11-19 00:26:39 +03:00
|
|
|
"path/filepath"
|
2022-11-09 00:13:39 +03:00
|
|
|
"regexp"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"golang.org/x/exp/maps"
|
2022-11-09 00:24:30 +03:00
|
|
|
"golang.org/x/vulndb/internal/derrors"
|
2023-03-28 22:39:34 +03:00
|
|
|
"golang.org/x/vulndb/internal/osv"
|
2022-11-09 00:13:39 +03:00
|
|
|
"golang.org/x/vulndb/internal/stdlib"
|
|
|
|
)
|
|
|
|
|
2022-11-19 00:26:39 +03:00
|
|
|
var (
|
|
|
|
// osvDir is the name of the directory in the vulndb repo that
|
|
|
|
// contains reports.
|
|
|
|
OSVDir = "data/osv"
|
2022-11-21 21:47:08 +03:00
|
|
|
|
2023-05-15 21:27:41 +03:00
|
|
|
// SchemaVersion is used to indicate which version of the OSV schema a
|
2022-11-21 21:47:08 +03:00
|
|
|
// particular vulnerability was exported with.
|
2023-05-15 21:27:41 +03:00
|
|
|
SchemaVersion = "1.3.1"
|
2022-11-19 00:26:39 +03:00
|
|
|
)
|
2022-11-09 00:13:39 +03:00
|
|
|
|
2023-03-30 00:24:32 +03:00
|
|
|
// GenerateOSVEntry create an osv.Entry for a report.
|
|
|
|
// In addition to the report, it takes the ID for the vuln and the time
|
|
|
|
// the vuln was last modified.
|
2022-11-19 00:26:39 +03:00
|
|
|
func (r *Report) GenerateOSVEntry(goID string, lastModified time.Time) osv.Entry {
|
2022-11-09 00:13:39 +03:00
|
|
|
var credits []osv.Credit
|
|
|
|
if r.Credit != "" {
|
|
|
|
credits = append(credits, osv.Credit{
|
|
|
|
Name: r.Credit,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-03-30 00:24:32 +03:00
|
|
|
var withdrawn *osv.Time
|
|
|
|
if r.Withdrawn != nil {
|
|
|
|
withdrawn = &osv.Time{Time: *r.Withdrawn}
|
|
|
|
}
|
|
|
|
|
2022-11-09 00:13:39 +03:00
|
|
|
entry := osv.Entry{
|
2023-03-31 23:44:23 +03:00
|
|
|
ID: goID,
|
|
|
|
Published: osv.Time{Time: r.Published},
|
|
|
|
Modified: osv.Time{Time: lastModified},
|
|
|
|
Withdrawn: withdrawn,
|
|
|
|
Details: trimWhitespace(r.Description),
|
|
|
|
Credits: credits,
|
2023-05-15 21:27:41 +03:00
|
|
|
SchemaVersion: SchemaVersion,
|
2023-03-31 23:44:23 +03:00
|
|
|
DatabaseSpecific: &osv.DatabaseSpecific{URL: GetGoAdvisoryLink(goID)},
|
2022-11-09 00:13:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, m := range r.Modules {
|
2023-03-31 23:44:23 +03:00
|
|
|
entry.Affected = append(entry.Affected, generateAffected(m))
|
2022-11-09 00:13:39 +03:00
|
|
|
}
|
|
|
|
for _, ref := range r.References {
|
|
|
|
entry.References = append(entry.References, osv.Reference{
|
2023-03-29 21:55:39 +03:00
|
|
|
Type: ref.Type,
|
2022-11-09 00:13:39 +03:00
|
|
|
URL: ref.URL,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
entry.Aliases = r.GetAliases()
|
|
|
|
return entry
|
|
|
|
}
|
|
|
|
|
2022-11-19 00:26:39 +03:00
|
|
|
func GetOSVFilename(goID string) string {
|
|
|
|
return filepath.Join(OSVDir, goID+".json")
|
|
|
|
}
|
|
|
|
|
2022-11-09 00:13:39 +03:00
|
|
|
// ReadOSV reads an osv.Entry from a file.
|
2022-11-09 00:24:30 +03:00
|
|
|
func ReadOSV(filename string) (entry osv.Entry, err error) {
|
|
|
|
derrors.Wrap(&err, "ReadOSV(%s)", filename)
|
2022-11-19 00:26:39 +03:00
|
|
|
if err = UnmarshalFromFile(filename, &entry); err != nil {
|
2022-11-09 00:13:39 +03:00
|
|
|
return osv.Entry{}, err
|
|
|
|
}
|
|
|
|
return entry, nil
|
|
|
|
}
|
|
|
|
|
2022-11-19 00:26:39 +03:00
|
|
|
func UnmarshalFromFile(path string, v any) (err error) {
|
2022-11-09 00:24:30 +03:00
|
|
|
content, err := os.ReadFile(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err = json.Unmarshal(content, v); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-11-09 00:13:39 +03:00
|
|
|
// ModulesForEntry returns the list of modules affected by an OSV entry.
|
|
|
|
func ModulesForEntry(entry osv.Entry) []string {
|
|
|
|
mods := map[string]bool{}
|
|
|
|
for _, a := range entry.Affected {
|
2023-03-29 21:55:39 +03:00
|
|
|
mods[a.Module.Path] = true
|
2022-11-09 00:13:39 +03:00
|
|
|
}
|
|
|
|
return maps.Keys(mods)
|
|
|
|
}
|
|
|
|
|
2023-03-29 21:55:39 +03:00
|
|
|
func AffectedRanges(versions []VersionRange) []osv.Range {
|
|
|
|
a := osv.Range{Type: osv.RangeTypeSemver}
|
2022-11-09 00:13:39 +03:00
|
|
|
if len(versions) == 0 || versions[0].Introduced == "" {
|
|
|
|
a.Events = append(a.Events, osv.RangeEvent{Introduced: "0"})
|
|
|
|
}
|
|
|
|
for _, v := range versions {
|
|
|
|
if v.Introduced != "" {
|
|
|
|
a.Events = append(a.Events, osv.RangeEvent{Introduced: v.Introduced.Canonical()})
|
|
|
|
}
|
|
|
|
if v.Fixed != "" {
|
|
|
|
a.Events = append(a.Events, osv.RangeEvent{Fixed: v.Fixed.Canonical()})
|
|
|
|
}
|
|
|
|
}
|
2023-03-29 21:55:39 +03:00
|
|
|
return []osv.Range{a}
|
2022-11-09 00:13:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// trimWhitespace removes unnecessary whitespace from a string, but preserves
|
|
|
|
// paragraph breaks (indicated by two newlines).
|
|
|
|
func trimWhitespace(s string) string {
|
|
|
|
s = strings.TrimSpace(s)
|
|
|
|
// Replace single newlines with spaces.
|
|
|
|
newlines := regexp.MustCompile(`([^\n])\n([^\n])`)
|
|
|
|
s = newlines.ReplaceAllString(s, "$1 $2")
|
|
|
|
// Replace instances of 2 or more newlines with exactly two newlines.
|
|
|
|
paragraphs := regexp.MustCompile(`\s*\n\n\s*`)
|
|
|
|
s = paragraphs.ReplaceAllString(s, "\n\n")
|
|
|
|
// Replace tabs and double spaces with single spaces.
|
|
|
|
spaces := regexp.MustCompile(`[ \t]+`)
|
|
|
|
s = spaces.ReplaceAllString(s, " ")
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2023-03-29 21:55:39 +03:00
|
|
|
func generateImports(m *Module) (imps []osv.Package) {
|
2022-11-09 00:13:39 +03:00
|
|
|
for _, p := range m.Packages {
|
|
|
|
syms := append([]string{}, p.Symbols...)
|
|
|
|
syms = append(syms, p.DerivedSymbols...)
|
|
|
|
sort.Strings(syms)
|
2023-03-29 21:55:39 +03:00
|
|
|
imps = append(imps, osv.Package{
|
2022-11-09 00:13:39 +03:00
|
|
|
Path: p.Package,
|
|
|
|
GOOS: p.GOOS,
|
|
|
|
GOARCH: p.GOARCH,
|
|
|
|
Symbols: syms,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return imps
|
|
|
|
}
|
|
|
|
|
2023-03-31 23:44:23 +03:00
|
|
|
func generateAffected(m *Module) osv.Affected {
|
2022-11-09 00:13:39 +03:00
|
|
|
name := m.Module
|
|
|
|
switch name {
|
|
|
|
case stdlib.ModulePath:
|
2023-03-29 21:55:39 +03:00
|
|
|
name = osv.GoStdModulePath
|
2022-11-09 00:13:39 +03:00
|
|
|
case stdlib.ToolchainModulePath:
|
2023-03-29 21:55:39 +03:00
|
|
|
name = osv.GoCmdModulePath
|
2022-11-09 00:13:39 +03:00
|
|
|
}
|
|
|
|
return osv.Affected{
|
2023-03-29 21:55:39 +03:00
|
|
|
Module: osv.Module{
|
|
|
|
Path: name,
|
2022-11-09 00:13:39 +03:00
|
|
|
Ecosystem: osv.GoEcosystem,
|
|
|
|
},
|
2023-03-31 23:44:23 +03:00
|
|
|
Ranges: AffectedRanges(m.Versions),
|
2023-03-29 21:55:39 +03:00
|
|
|
EcosystemSpecific: &osv.EcosystemSpecific{
|
|
|
|
Packages: generateImports(m),
|
2022-11-09 00:13:39 +03:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|