| // 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 |
| } |