blob: 1513b4774b36fcc5ed22bebeb653ca0c5c4cfb6d [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 report
import (
"errors"
"regexp"
"strings"
"encoding/json"
"golang.org/x/vulndb/internal/cveschema"
"golang.org/x/vulndb/internal/derrors"
"golang.org/x/vulndb/internal/stdlib"
)
// ToCVE creates a CVE from a reports/GO-YYYY-NNNN.yaml file.
func ToCVE(reportPath string) (_ *cveschema.CVE, err error) {
defer derrors.Wrap(&err, "report.ToCVE(%q)", reportPath)
r, err := ReadAndLint(reportPath)
if err != nil {
return nil, err
}
if r.CVEMetadata == nil {
return nil, errors.New("cve_metadata must be present to convert a report to a CVE")
}
description := r.CVEMetadata.Description
if description == "" {
description = r.Description
}
c := &cveschema.CVE{
DataType: "CVE",
DataFormat: "MITRE",
DataVersion: "4.0",
Metadata: cveschema.Metadata{
ID: r.CVEMetadata.ID,
Assigner: "security@golang.org",
State: cveschema.StatePublic,
},
Description: cveschema.Description{
Data: []cveschema.LangString{
{
Lang: "eng",
Value: removeNewlines(description),
},
},
},
ProblemType: cveschema.ProblemType{
Data: []cveschema.ProblemTypeDataItem{
{
Description: []cveschema.LangString{
{
Lang: "eng",
Value: r.CVEMetadata.CWE,
},
},
},
},
},
}
for _, m := range r.Modules {
var pkgData []cveschema.ProductDataItem
for _, p := range m.Packages {
pkgData = append(pkgData,
cveschema.ProductDataItem{
ProductName: p.Package,
Version: versionToVersion(m.Versions),
})
}
c.Affects.Vendor.Data = append(c.Affects.Vendor.Data, cveschema.VendorDataItem{
VendorName: getVendor(m.Module),
Product: cveschema.Product{
Data: pkgData,
},
})
}
for _, ref := range r.References {
c.References.Data = append(c.References.Data, cveschema.Reference{URL: ref.URL})
}
goAdvisory := GetGoAdvisoryLink(GetGoIDFromFilename(reportPath))
c.References.Data = append(c.References.Data, cveschema.Reference{URL: goAdvisory})
c.RawCredit, err = json.Marshal([]cveschema.LangString{{
Lang: "eng",
Value: removeNewlines(r.Credit),
}})
if err != nil {
return nil, err
}
return c, nil
}
func getVendor(modulePath string) string {
switch modulePath {
case stdlib.ModulePath:
return "Go standard library"
case stdlib.ToolchainModulePath:
return "Go toolchain"
default:
return modulePath
}
}
// removeNewlines removes leading and trailing space characters and
// replaces inner newlines with spaces.
func removeNewlines(s string) string {
newlines := regexp.MustCompile(`\n+`)
return newlines.ReplaceAllString(strings.TrimSpace(s), " ")
}
func versionToVersion(versions []VersionRange) cveschema.VersionData {
vd := cveschema.VersionData{}
for _, vr := range versions {
if vr.Introduced != "" {
vd.Data = append(vd.Data, cveschema.VersionDataItem{
VersionValue: string(vr.Introduced),
VersionAffected: ">=",
})
}
if vr.Fixed != "" {
vd.Data = append(vd.Data, cveschema.VersionDataItem{
VersionValue: string(vr.Fixed),
VersionAffected: "<",
})
}
}
return vd
}
// CVEToReport creates a Report struct from a given CVE and modulePath.
func CVEToReport(c *cveschema.CVE, modulePath string) *Report {
var description string
for _, d := range c.Description.Data {
description += d.Value + "\n"
}
var refs []*Reference
for _, r := range c.References.Data {
typ := ReferenceTypeWeb
switch {
case strings.Contains(r.URL, "go-review.googlesource.com"):
typ = ReferenceTypeFix
case strings.Contains(r.URL, "commit"):
typ = ReferenceTypeFix
case strings.Contains(r.URL, "pull"):
typ = ReferenceTypeFix
case strings.Contains(r.URL, "pr"):
typ = ReferenceTypeFix
case strings.Contains(r.URL, "/issue/"):
typ = ReferenceTypeReport
}
refs = append(refs, &Reference{
Type: typ,
URL: r.URL,
})
}
var credits []string
for _, v := range c.Credit.Data.Description.Data {
credits = append(credits, v.Value)
}
credit := strings.Join(credits, "\t")
var pkgPath string
if data := c.Affects.Vendor.Data; len(data) > 0 {
if data2 := data[0].Product.Data; len(data2) > 0 {
pkgPath = data2[0].ProductName
}
}
if modulePath == "" {
modulePath = "TODO"
}
if pkgPath == "" {
pkgPath = modulePath
}
r := &Report{
Modules: []*Module{{
Module: modulePath,
Packages: []*Package{{
Package: pkgPath,
}},
}},
Description: description,
CVEs: []string{c.Metadata.ID},
Credit: credit,
References: refs,
}
if !strings.Contains(modulePath, ".") {
r.Modules[0].Module = stdlib.ModulePath
r.Modules[0].Packages[0].Package = modulePath
}
if stdlib.Contains(r.Modules[0].Module) && r.Modules[0].Packages[0].Package == "" {
r.Modules[0].Packages[0].Package = modulePath
}
r.Fix()
return r
}