blob: 62a2f83a35c759010b2d80e66800decdf113c559 [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"
"fmt"
"io/ioutil"
"sort"
"strings"
"golang.org/x/vulndb/internal/cveschema"
"golang.org/x/vulndb/internal/derrors"
"golang.org/x/vulndb/internal/stdlib"
"gopkg.in/yaml.v2"
)
// 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)
b, err := ioutil.ReadFile(reportPath)
if err != nil {
return nil, fmt.Errorf("ioutil.ReadFile(%q): %v", reportPath, err)
}
var r Report
if err = yaml.UnmarshalStrict(b, &r); err != nil {
return nil, fmt.Errorf("yaml.Unmarshal:: %v", err)
}
if len(r.CVEs) > 0 {
return nil, errors.New("report has CVE ID is wrong section (should be in cve_metadata for self-issued CVEs)")
}
if r.CVEMetadata == nil {
return nil, errors.New("report missing cve_metadata section")
}
if r.CVEMetadata.ID == "" {
return nil, errors.New("report missing CVE ID")
}
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: strings.TrimSuffix(r.CVEMetadata.Description, "\n"),
},
},
},
ProblemType: cveschema.ProblemType{
Data: []cveschema.ProblemTypeDataItem{
{
Description: []cveschema.LangString{
{
Lang: "eng",
Value: r.CVEMetadata.CWE,
},
},
},
},
},
Affects: cveschema.Affects{
Vendor: cveschema.Vendor{
Data: []cveschema.VendorDataItem{
{
VendorName: "n/a", // ???
Product: cveschema.Product{
Data: []cveschema.ProductDataItem{
{
ProductName: r.Package,
Version: versionToVersion(r.Versions),
},
},
},
},
},
},
},
}
for _, additional := range r.AdditionalPackages {
c.Affects.Vendor.Data = append(c.Affects.Vendor.Data, cveschema.VendorDataItem{
VendorName: "n/a",
Product: cveschema.Product{
Data: []cveschema.ProductDataItem{
{
ProductName: additional.Package,
Version: versionToVersion(additional.Versions),
},
},
},
})
}
if r.Links.PR != "" {
c.References.Data = append(c.References.Data, cveschema.Reference{URL: r.Links.PR})
}
if r.Links.Commit != "" {
c.References.Data = append(c.References.Data, cveschema.Reference{URL: r.Links.Commit})
}
for _, url := range r.Links.Context {
c.References.Data = append(c.References.Data, cveschema.Reference{URL: url})
}
return c, nil
}
func versionToVersion(versions []VersionRange) cveschema.VersionData {
vd := cveschema.VersionData{}
for _, vr := range versions {
if vr.Introduced != "" {
vd.Data = append(vd.Data, cveschema.VersionDataItem{
VersionValue: vr.Introduced,
VersionAffected: ">=",
})
}
if vr.Fixed != "" {
vd.Data = append(vd.Data, cveschema.VersionDataItem{
VersionValue: 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 (
pr, commit string
context []string
)
for _, r := range c.References.Data {
if strings.Contains(r.URL, "go-review.googlesource.com") {
pr = r.URL
} else if strings.Contains(r.URL, "commit") {
commit = r.URL
} else if strings.Contains(r.URL, "pull") {
pr = r.URL
} else {
context = append(context, r.URL)
}
}
sort.Strings(context)
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
}
}
r := &Report{
Module: modulePath,
Package: pkgPath,
Description: description,
CVEs: []string{c.Metadata.ID},
Credit: credit,
Links: Links{
Commit: commit,
PR: pr,
Context: context,
},
}
if !strings.Contains(modulePath, ".") {
r.Module = stdlib.ModulePath
r.Package = modulePath
}
if stdlib.Contains(r.Module) && r.Package == "" {
r.Package = modulePath
}
r.Fix()
return r
}