mirror of https://github.com/golang/go.git
gopls/internal/vulncheck/vulntest: add test helpers
This package hosts helper utilities for vulncheck features. This package requires go1.18+. Most of the code were adopted from golang.org/x/vulndb/internal. The first batch is NewDatabase reads YAML-format vulnerability information files (https://github.com/golang/vulndb/blob/master/doc/format.md) packaged in txtar, and creates a filesystem-based vuln database that can be used as a data source of golang.org/x/vuln/client APIs. See db_test.go for example. * Source of the code db.go: golang.org/x/vulndb/internal/database#Generate report.go: golang.org/x/vulndb/internal/report#Report stdlib.go: golang.org/x/vulndb/internal/stdlib This change adds a new dependency on "gopkg.in/yaml.v3" for parsing YAMLs in testing Change-Id: Ica5da4284c38a8a9531b1f943deb4288a2058c9b Reviewed-on: https://go-review.googlesource.com/c/tools/+/435358 Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com> TryBot-Result: Gopher Robot <gobot@golang.org> gopls-CI: kokoro <noreply+kokoro@google.com> Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
parent
2f57270232
commit
4ef38dc8f2
|
|
@ -13,6 +13,7 @@ require (
|
|||
golang.org/x/text v0.3.7
|
||||
golang.org/x/tools v0.1.13-0.20220928184430-f80e98464e27
|
||||
golang.org/x/vuln v0.0.0-20221004232641-2aa0553d353b
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
honnef.co/go/tools v0.3.3
|
||||
mvdan.cc/gofumpt v0.3.1
|
||||
mvdan.cc/xurls/v2 v2.4.0
|
||||
|
|
|
|||
|
|
@ -76,11 +76,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
|
||||
honnef.co/go/tools v0.3.3 h1:oDx7VAwstgpYpb3wv0oxiZlxY+foCpRAwY7Vk6XpAgA=
|
||||
honnef.co/go/tools v0.3.3/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,303 @@
|
|||
// 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.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
// Package vulntest provides helpers for vulncheck functionality testing.
|
||||
package vulntest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/internal/span"
|
||||
"golang.org/x/tools/txtar"
|
||||
"golang.org/x/vuln/client"
|
||||
"golang.org/x/vuln/osv"
|
||||
)
|
||||
|
||||
// NewDatabase returns a read-only DB containing the provided
|
||||
// txtar-format collection of vulnerability reports.
|
||||
// Each vulnerability report is a YAML file whose format
|
||||
// is defined in golang.org/x/vulndb/doc/format.md.
|
||||
// A report file name must have the id as its base name,
|
||||
// and have .yaml as its extension.
|
||||
//
|
||||
// db, err := NewDatabase(ctx, reports)
|
||||
// ...
|
||||
// defer db.Clean()
|
||||
// client, err := NewClient(db)
|
||||
// ...
|
||||
//
|
||||
// The returned DB's Clean method must be called to clean up the
|
||||
// generated database.
|
||||
func NewDatabase(ctx context.Context, txtarReports []byte) (*DB, error) {
|
||||
disk, err := ioutil.TempDir("", "vulndb-test")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := generateDB(ctx, txtarReports, disk, false); err != nil {
|
||||
os.RemoveAll(disk)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DB{disk: disk}, nil
|
||||
}
|
||||
|
||||
// DB is a read-only vulnerability database on disk.
|
||||
// Users can use this database with golang.org/x/vuln APIs
|
||||
// by setting the `VULNDB“ environment variable.
|
||||
type DB struct {
|
||||
disk string
|
||||
}
|
||||
|
||||
// URI returns the file URI that can be used for VULNDB environment
|
||||
// variable.
|
||||
func (db *DB) URI() string {
|
||||
u := span.URIFromPath(db.disk)
|
||||
return string(u)
|
||||
}
|
||||
|
||||
// Clean deletes the database.
|
||||
func (db *DB) Clean() error {
|
||||
return os.RemoveAll(db.disk)
|
||||
}
|
||||
|
||||
// NewClient returns a vuln DB client that works with the given DB.
|
||||
func NewClient(db *DB) (client.Client, error) {
|
||||
return client.NewClient([]string{db.URI()}, client.Options{})
|
||||
}
|
||||
|
||||
//
|
||||
// The following was selectively copied from golang.org/x/vulndb/internal/database
|
||||
//
|
||||
|
||||
const (
|
||||
dbURL = "https://pkg.go.dev/vuln/"
|
||||
|
||||
// idDirectory is the name of the directory that contains entries
|
||||
// listed by their IDs.
|
||||
idDirectory = "ID"
|
||||
|
||||
// stdFileName is the name of the .json file in the vulndb repo
|
||||
// that will contain info on standard library vulnerabilities.
|
||||
stdFileName = "stdlib"
|
||||
|
||||
// toolchainFileName is the name of the .json file in the vulndb repo
|
||||
// that will contain info on toolchain (cmd/...) vulnerabilities.
|
||||
toolchainFileName = "toolchain"
|
||||
|
||||
// cmdModule is the name of the module containing Go toolchain
|
||||
// binaries.
|
||||
cmdModule = "cmd"
|
||||
|
||||
// stdModule is the name of the module containing Go std packages.
|
||||
stdModule = "std"
|
||||
)
|
||||
|
||||
// generateDB generates the file-based vuln DB in the directory jsonDir.
|
||||
func generateDB(ctx context.Context, txtarData []byte, jsonDir string, indent bool) error {
|
||||
archive := txtar.Parse(txtarData)
|
||||
|
||||
jsonVulns, entries, err := generateEntries(ctx, archive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
index := make(client.DBIndex, len(jsonVulns))
|
||||
for modulePath, vulns := range jsonVulns {
|
||||
epath, err := client.EscapeModulePath(modulePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeVulns(filepath.Join(jsonDir, epath), vulns, indent); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range vulns {
|
||||
if v.Modified.After(index[modulePath]) {
|
||||
index[modulePath] = v.Modified
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := writeJSON(filepath.Join(jsonDir, "index.json"), index, indent); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeAliasIndex(jsonDir, entries, indent); err != nil {
|
||||
return err
|
||||
}
|
||||
return writeEntriesByID(filepath.Join(jsonDir, idDirectory), entries, indent)
|
||||
}
|
||||
|
||||
func generateEntries(_ context.Context, archive *txtar.Archive) (map[string][]osv.Entry, []osv.Entry, error) {
|
||||
now := time.Now()
|
||||
jsonVulns := map[string][]osv.Entry{}
|
||||
var entries []osv.Entry
|
||||
for _, f := range archive.Files {
|
||||
if !strings.HasSuffix(f.Name, ".yaml") {
|
||||
continue
|
||||
}
|
||||
r, err := readReport(bytes.NewReader(f.Data))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
name := strings.TrimSuffix(filepath.Base(f.Name), filepath.Ext(f.Name))
|
||||
linkName := fmt.Sprintf("%s%s", dbURL, name)
|
||||
entry, modulePaths := generateOSVEntry(name, linkName, now, *r)
|
||||
for _, modulePath := range modulePaths {
|
||||
jsonVulns[modulePath] = append(jsonVulns[modulePath], entry)
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
return jsonVulns, entries, nil
|
||||
}
|
||||
|
||||
func writeVulns(outPath string, vulns []osv.Entry, indent bool) error {
|
||||
if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
|
||||
return fmt.Errorf("failed to create directory %q: %s", filepath.Dir(outPath), err)
|
||||
}
|
||||
return writeJSON(outPath+".json", vulns, indent)
|
||||
}
|
||||
|
||||
func writeEntriesByID(idDir string, entries []osv.Entry, indent bool) error {
|
||||
// Write a directory containing entries by ID.
|
||||
if err := os.MkdirAll(idDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create directory %q: %v", idDir, err)
|
||||
}
|
||||
var idIndex []string
|
||||
for _, e := range entries {
|
||||
outPath := filepath.Join(idDir, e.ID+".json")
|
||||
if err := writeJSON(outPath, e, indent); err != nil {
|
||||
return err
|
||||
}
|
||||
idIndex = append(idIndex, e.ID)
|
||||
}
|
||||
// Write an index.json in the ID directory with a list of all the IDs.
|
||||
return writeJSON(filepath.Join(idDir, "index.json"), idIndex, indent)
|
||||
}
|
||||
|
||||
// Write a JSON file containing a map from alias to GO IDs.
|
||||
func writeAliasIndex(dir string, entries []osv.Entry, indent bool) error {
|
||||
aliasToGoIDs := map[string][]string{}
|
||||
for _, e := range entries {
|
||||
for _, a := range e.Aliases {
|
||||
aliasToGoIDs[a] = append(aliasToGoIDs[a], e.ID)
|
||||
}
|
||||
}
|
||||
return writeJSON(filepath.Join(dir, "aliases.json"), aliasToGoIDs, indent)
|
||||
}
|
||||
|
||||
func writeJSON(filename string, value any, indent bool) (err error) {
|
||||
j, err := jsonMarshal(value, indent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(filename, j, 0644)
|
||||
}
|
||||
|
||||
func jsonMarshal(v any, indent bool) ([]byte, error) {
|
||||
if indent {
|
||||
return json.MarshalIndent(v, "", " ")
|
||||
}
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
// generateOSVEntry create an osv.Entry for a report. In addition to the report, it
|
||||
// takes the ID for the vuln and a URL that will point to the entry in the vuln DB.
|
||||
// It returns the osv.Entry and a list of module paths that the vuln affects.
|
||||
func generateOSVEntry(id, url string, lastModified time.Time, r Report) (osv.Entry, []string) {
|
||||
entry := osv.Entry{
|
||||
ID: id,
|
||||
Published: r.Published,
|
||||
Modified: lastModified,
|
||||
Withdrawn: r.Withdrawn,
|
||||
Details: r.Description,
|
||||
}
|
||||
|
||||
moduleMap := make(map[string]bool)
|
||||
for _, m := range r.Modules {
|
||||
switch m.Module {
|
||||
case stdModule:
|
||||
moduleMap[stdFileName] = true
|
||||
case cmdModule:
|
||||
moduleMap[toolchainFileName] = true
|
||||
default:
|
||||
moduleMap[m.Module] = true
|
||||
}
|
||||
entry.Affected = append(entry.Affected, generateAffected(m, url))
|
||||
}
|
||||
for _, ref := range r.References {
|
||||
entry.References = append(entry.References, osv.Reference{
|
||||
Type: string(ref.Type),
|
||||
URL: ref.URL,
|
||||
})
|
||||
}
|
||||
|
||||
var modulePaths []string
|
||||
for module := range moduleMap {
|
||||
modulePaths = append(modulePaths, module)
|
||||
}
|
||||
// TODO: handle missing fields - Aliases
|
||||
|
||||
return entry, modulePaths
|
||||
}
|
||||
|
||||
func generateAffectedRanges(versions []VersionRange) osv.Affects {
|
||||
a := osv.AffectsRange{Type: osv.TypeSemver}
|
||||
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()})
|
||||
}
|
||||
}
|
||||
return osv.Affects{a}
|
||||
}
|
||||
|
||||
func generateImports(m *Module) (imps []osv.EcosystemSpecificImport) {
|
||||
for _, p := range m.Packages {
|
||||
syms := append([]string{}, p.Symbols...)
|
||||
syms = append(syms, p.DerivedSymbols...)
|
||||
sort.Strings(syms)
|
||||
imps = append(imps, osv.EcosystemSpecificImport{
|
||||
Path: p.Package,
|
||||
GOOS: p.GOOS,
|
||||
GOARCH: p.GOARCH,
|
||||
Symbols: syms,
|
||||
})
|
||||
}
|
||||
return imps
|
||||
}
|
||||
func generateAffected(m *Module, url string) osv.Affected {
|
||||
name := m.Module
|
||||
switch name {
|
||||
case stdModule:
|
||||
name = "stdlib"
|
||||
case cmdModule:
|
||||
name = "toolchain"
|
||||
}
|
||||
return osv.Affected{
|
||||
Package: osv.Package{
|
||||
Name: name,
|
||||
Ecosystem: osv.GoEcosystem,
|
||||
},
|
||||
Ranges: generateAffectedRanges(m.Versions),
|
||||
DatabaseSpecific: osv.DatabaseSpecific{URL: url},
|
||||
EcosystemSpecific: osv.EcosystemSpecific{
|
||||
Imports: generateImports(m),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// 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.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package vulntest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewDatabase(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
in := []byte(`
|
||||
-- GO-2020-0001.yaml --
|
||||
modules:
|
||||
- module: github.com/gin-gonic/gin
|
||||
versions:
|
||||
- fixed: 1.6.0
|
||||
packages:
|
||||
- package: github.com/gin-gonic/gin
|
||||
symbols:
|
||||
- defaultLogFormatter
|
||||
description: |
|
||||
Something.
|
||||
published: 2021-04-14T20:04:52Z
|
||||
references:
|
||||
- fix: https://github.com/gin-gonic/gin/pull/2237
|
||||
`)
|
||||
|
||||
db, err := NewDatabase(ctx, in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Clean()
|
||||
|
||||
cli, err := NewClient(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := cli.GetByID(ctx, "GO-2020-0001")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.ID != "GO-2020-0001" {
|
||||
m, _ := json.Marshal(got)
|
||||
t.Errorf("got %s\nwant GO-2020-0001 entry", m)
|
||||
}
|
||||
gotAll, err := cli.GetByModule(ctx, "github.com/gin-gonic/gin")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(gotAll) != 1 || gotAll[0].ID != "GO-2020-0001" {
|
||||
m, _ := json.Marshal(got)
|
||||
t.Errorf("got %s\nwant GO-2020-0001 entry", m)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
// 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.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package vulntest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
//
|
||||
// The following was selectively copied from golang.org/x/vulndb/internal/report
|
||||
//
|
||||
|
||||
// readReport reads a Report in YAML format.
|
||||
func readReport(in io.Reader) (*Report, error) {
|
||||
d := yaml.NewDecoder(in)
|
||||
// Require that all fields in the file are in the struct.
|
||||
// This corresponds to v2's UnmarshalStrict.
|
||||
d.KnownFields(true)
|
||||
var r Report
|
||||
if err := d.Decode(&r); err != nil {
|
||||
return nil, fmt.Errorf("yaml.Decode: %v", err)
|
||||
}
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
// Report represents a vulnerability report in the vulndb.
|
||||
// Remember to update doc/format.md when this structure changes.
|
||||
type Report struct {
|
||||
Modules []*Module `yaml:",omitempty"`
|
||||
|
||||
// Description is the CVE description from an existing CVE. If we are
|
||||
// assigning a CVE ID ourselves, use CVEMetadata.Description instead.
|
||||
Description string `yaml:",omitempty"`
|
||||
Published time.Time `yaml:",omitempty"`
|
||||
Withdrawn *time.Time `yaml:",omitempty"`
|
||||
|
||||
References []*Reference `yaml:",omitempty"`
|
||||
}
|
||||
|
||||
// Write writes r to filename in YAML format.
|
||||
func (r *Report) Write(filename string) (err error) {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.encode(f)
|
||||
err2 := f.Close()
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ToString encodes r to a YAML string.
|
||||
func (r *Report) ToString() (string, error) {
|
||||
var b strings.Builder
|
||||
if err := r.encode(&b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func (r *Report) encode(w io.Writer) error {
|
||||
e := yaml.NewEncoder(w)
|
||||
defer e.Close()
|
||||
e.SetIndent(4)
|
||||
return e.Encode(r)
|
||||
}
|
||||
|
||||
type VersionRange struct {
|
||||
Introduced Version `yaml:"introduced,omitempty"`
|
||||
Fixed Version `yaml:"fixed,omitempty"`
|
||||
}
|
||||
|
||||
type Module struct {
|
||||
Module string `yaml:",omitempty"`
|
||||
Versions []VersionRange `yaml:",omitempty"`
|
||||
Packages []*Package `yaml:",omitempty"`
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
Package string `yaml:",omitempty"`
|
||||
GOOS []string `yaml:"goos,omitempty"`
|
||||
GOARCH []string `yaml:"goarch,omitempty"`
|
||||
// Symbols originally identified as vulnerable.
|
||||
Symbols []string `yaml:",omitempty"`
|
||||
// Additional vulnerable symbols, computed from Symbols via static analysis
|
||||
// or other technique.
|
||||
DerivedSymbols []string `yaml:"derived_symbols,omitempty"`
|
||||
}
|
||||
|
||||
// Version is an SemVer 2.0.0 semantic version with no leading "v" prefix,
|
||||
// as used by OSV.
|
||||
type Version string
|
||||
|
||||
// V returns the version with a "v" prefix.
|
||||
func (v Version) V() string {
|
||||
return "v" + string(v)
|
||||
}
|
||||
|
||||
// IsValid reports whether v is a valid semantic version string.
|
||||
func (v Version) IsValid() bool {
|
||||
return semver.IsValid(v.V())
|
||||
}
|
||||
|
||||
// Before reports whether v < v2.
|
||||
func (v Version) Before(v2 Version) bool {
|
||||
return semver.Compare(v.V(), v2.V()) < 0
|
||||
}
|
||||
|
||||
// Canonical returns the canonical formatting of the version.
|
||||
func (v Version) Canonical() string {
|
||||
return strings.TrimPrefix(semver.Canonical(v.V()), "v")
|
||||
}
|
||||
|
||||
// Reference type is a reference (link) type.
|
||||
type ReferenceType string
|
||||
|
||||
const (
|
||||
ReferenceTypeAdvisory = ReferenceType("ADVISORY")
|
||||
ReferenceTypeArticle = ReferenceType("ARTICLE")
|
||||
ReferenceTypeReport = ReferenceType("REPORT")
|
||||
ReferenceTypeFix = ReferenceType("FIX")
|
||||
ReferenceTypePackage = ReferenceType("PACKAGE")
|
||||
ReferenceTypeEvidence = ReferenceType("EVIDENCE")
|
||||
ReferenceTypeWeb = ReferenceType("WEB")
|
||||
)
|
||||
|
||||
// ReferenceTypes is the set of reference types defined in OSV.
|
||||
var ReferenceTypes = []ReferenceType{
|
||||
ReferenceTypeAdvisory,
|
||||
ReferenceTypeArticle,
|
||||
ReferenceTypeReport,
|
||||
ReferenceTypeFix,
|
||||
ReferenceTypePackage,
|
||||
ReferenceTypeEvidence,
|
||||
ReferenceTypeWeb,
|
||||
}
|
||||
|
||||
// A Reference is a link to some external resource.
|
||||
//
|
||||
// For ease of typing, References are represented in the YAML as a
|
||||
// single-element mapping of type to URL.
|
||||
type Reference struct {
|
||||
Type ReferenceType `json:"type,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
func (r *Reference) MarshalYAML() (interface{}, error) {
|
||||
return map[string]string{
|
||||
strings.ToLower(string(r.Type)): r.URL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Reference) UnmarshalYAML(n *yaml.Node) (err error) {
|
||||
if n.Kind != yaml.MappingNode || len(n.Content) != 2 || n.Content[0].Kind != yaml.ScalarNode || n.Content[1].Kind != yaml.ScalarNode {
|
||||
return &yaml.TypeError{Errors: []string{
|
||||
fmt.Sprintf("line %d: report.Reference must contain a mapping with one value", n.Line),
|
||||
}}
|
||||
}
|
||||
r.Type = ReferenceType(strings.ToUpper(n.Content[0].Value))
|
||||
r.URL = n.Content[1].Value
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// 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.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package vulntest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func readAll(t *testing.T, filename string) io.Reader {
|
||||
d, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return bytes.NewReader(d)
|
||||
}
|
||||
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
// A report shouldn't change after being read and then written.
|
||||
in := filepath.Join("testdata", "report.yaml")
|
||||
r, err := readReport(readAll(t, in))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out := filepath.Join(t.TempDir(), "report.yaml")
|
||||
if err := r.Write(out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want, err := os.ReadFile(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := os.ReadFile(out)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("mismatch (-want, +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// 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.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package vulntest
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
)
|
||||
|
||||
// maybeStdlib reports whether the given import path could be part of the Go
|
||||
// standard library, by reporting whether the first component lacks a '.'.
|
||||
func maybeStdlib(path string) bool {
|
||||
if err := module.CheckImportPath(path); err != nil {
|
||||
return false
|
||||
}
|
||||
if i := strings.IndexByte(path, '/'); i != -1 {
|
||||
path = path[:i]
|
||||
}
|
||||
return !strings.Contains(path, ".")
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// 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.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package vulntest
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMaybeStdlib(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
want bool
|
||||
}{
|
||||
{"", false},
|
||||
{"math/crypto", true},
|
||||
{"github.com/pkg/errors", false},
|
||||
{"Path is unknown", false},
|
||||
} {
|
||||
got := maybeStdlib(test.in)
|
||||
if got != test.want {
|
||||
t.Errorf("%q: got %t, want %t", test.in, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
modules:
|
||||
- module: github.com/gin-gonic/gin
|
||||
versions:
|
||||
- fixed: 1.6.0
|
||||
packages:
|
||||
- package: github.com/gin-gonic/gin
|
||||
symbols:
|
||||
- defaultLogFormatter
|
||||
description: |
|
||||
The default Formatter for the Logger middleware (LoggerConfig.Formatter),
|
||||
which is included in the Default engine, allows attackers to inject arbitrary
|
||||
log entries by manipulating the request path.
|
||||
references:
|
||||
- fix: https://github.com/gin-gonic/gin/pull/1234
|
||||
- fix: https://github.com/gin-gonic/gin/commit/abcdefg
|
||||
Loading…
Reference in New Issue