cmd/vulnreport: exhaustively search for aliases

Change-Id: I1bcb1f185bb3227d2eca1b1d1adb87fa8166314b
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/530597
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
Tatiana Bradley 2023-09-22 15:53:22 -04:00
Родитель 4a6d9fe05a
Коммит fa8260e475
3 изменённых файлов: 190 добавлений и 73 удалений

Просмотреть файл

@ -0,0 +1,89 @@
// Copyright 2023 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 main
import (
"context"
"fmt"
"golang.org/x/exp/slices"
"golang.org/x/vulndb/internal/cveschema5"
"golang.org/x/vulndb/internal/ghsa"
"golang.org/x/vulndb/internal/report"
)
// addMissingAliases uses the existing aliases in a report to find
// any missing aliases, and adds them to the report.
func addMissingAliases(ctx context.Context, r *report.Report, gc *ghsa.Client) (added int) {
return r.AddAliases(allAliases(ctx, r.Aliases(), gc))
}
// allAliases returns a list of all aliases associated with the given knownAliases,
// (including the knownAliases themselves).
func allAliases(ctx context.Context, knownAliases []string, gc *ghsa.Client) []string {
aliasesFor := func(ctx context.Context, alias string) ([]string, error) {
switch {
case ghsa.IsGHSA(alias):
return aliasesForGHSA(ctx, alias, gc)
case cveschema5.IsCVE(alias):
return aliasesForCVE(ctx, alias, gc)
default:
return nil, fmt.Errorf("unsupported alias %s", alias)
}
}
return aliasesBFS(ctx, knownAliases, aliasesFor)
}
func aliasesBFS(ctx context.Context, knownAliases []string,
aliasesFor func(ctx context.Context, alias string) ([]string, error)) (all []string) {
var queue []string
var seen = make(map[string]bool)
queue = append(queue, knownAliases...)
for len(queue) > 0 {
alias := queue[0]
queue = queue[1:]
if seen[alias] {
continue
}
seen[alias] = true
all = append(all, alias)
aliases, err := aliasesFor(ctx, alias)
if err != nil {
errlog.Printf(err.Error())
continue
}
queue = append(queue, aliases...)
}
slices.Sort(all)
return slices.Compact(all)
}
func aliasesForGHSA(ctx context.Context, alias string, gc *ghsa.Client) (aliases []string, err error) {
sa, err := gc.FetchGHSA(ctx, alias)
if err != nil {
return nil, fmt.Errorf("could not fetch GHSA record for %s", alias)
}
for _, id := range sa.Identifiers {
if id.Type == "CVE" || id.Type == "GHSA" {
aliases = append(aliases, id.Value)
}
}
return aliases, nil
}
func aliasesForCVE(ctx context.Context, cve string, gc *ghsa.Client) (aliases []string, err error) {
sas, err := gc.ListForCVE(ctx, cve)
if err != nil {
return nil, fmt.Errorf("could not find GHSAs for CVE %s", cve)
}
for _, sa := range sas {
aliases = append(aliases, sa.ID)
}
return aliases, nil
}

Просмотреть файл

@ -0,0 +1,94 @@
// Copyright 2023 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 main
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestAliasesBFS(t *testing.T) {
tests := []struct {
knownAliases []string
aliasesFor func(ctx context.Context, alias string) ([]string, error)
want []string
}{
{
knownAliases: []string{"CVE-2023-0001"},
aliasesFor: func(ctx context.Context, alias string) ([]string, error) {
switch alias {
case "CVE-2023-0001":
return []string{"GHSA-1234567890"}, nil
default:
return nil, fmt.Errorf("unsupported alias %s", alias)
}
},
want: []string{"CVE-2023-0001", "GHSA-1234567890"},
},
{
knownAliases: []string{"CVE-2023-0001", "GHSA-1234567890"},
aliasesFor: func(ctx context.Context, alias string) ([]string, error) {
switch alias {
case "CVE-2023-0001":
return []string{"GHSA-1234567890"}, nil
case "GHSA-1234567890":
return []string{"CVE-2023-0001"}, nil
default:
return nil, fmt.Errorf("unsupported alias %s", alias)
}
},
want: []string{"CVE-2023-0001", "GHSA-1234567890"},
},
{
knownAliases: []string{"CVE-2023-0001", "GHSA-1234567890"},
aliasesFor: func(ctx context.Context, alias string) ([]string, error) {
switch alias {
case "CVE-2023-0001":
return []string{"GHSA-1234567890", "CVE-2023-0002"}, nil
case "GHSA-1234567890":
return []string{"CVE-2023-0001", "CVE-2023-0002"}, nil
case "CVE-2023-0002":
return []string{"CVE-2023-0001", "GHSA-1234567890"}, nil
default:
return nil, fmt.Errorf("unsupported alias %s", alias)
}
},
want: []string{"CVE-2023-0001", "CVE-2023-0002", "GHSA-1234567890"},
},
{
knownAliases: []string{"CVE-2023-0001"},
aliasesFor: func(ctx context.Context, alias string) ([]string, error) {
switch alias {
case "CVE-2023-0001":
return []string{"GHSA-1234567890"}, nil
case "GHSA-1234567890":
return []string{"CVE-2023-0002"}, nil
case "CVE-2023-0002":
return []string{"GHSA-1234567890"}, nil
default:
return nil, fmt.Errorf("unsupported alias %s", alias)
}
},
want: []string{"CVE-2023-0001", "CVE-2023-0002", "GHSA-1234567890"},
},
{
knownAliases: []string{},
aliasesFor: func(ctx context.Context, alias string) ([]string, error) {
return nil, fmt.Errorf("unsupported alias %s", alias)
},
want: nil,
},
}
for _, test := range tests {
got := aliasesBFS(context.Background(), test.knownAliases, test.aliasesFor)
if diff := cmp.Diff(test.want, got); diff != "" {
t.Errorf("aliasesBFS(%v) = %v, want %v", test.knownAliases, got, test.want)
}
}
}

Просмотреть файл

@ -508,17 +508,12 @@ func newReport(ctx context.Context, cfg *createCfg, parsed *parsedIssue) (*repor
}
r.ID = parsed.id
if err := addAliases(ctx, r, cfg.ghsaClient); err != nil {
return nil, err
}
// Fill an any CVEs and GHSAs we found that may have been missed
// in report creation.
if r.CVEMetadata == nil {
r.CVEs = dedupeAndSort(append(r.CVEs, parsed.cves...))
}
r.GHSAs = dedupeAndSort(append(r.GHSAs, parsed.ghsas...))
// Ensure all source aliases are added to the report.
r.AddAliases(parsed.ghsas)
r.AddAliases(parsed.cves)
// Find any additional aliases referenced by the source aliases.
addMissingAliases(ctx, r, cfg.ghsaClient)
return r, nil
}
@ -765,8 +760,8 @@ func fix(ctx context.Context, filename string, ghsaClient *ghsa.Client, pc *prox
}
if !*skipAlias {
infolog.Printf("%s: checking for missing GHSAs and CVEs (use -skip-alias to skip this)", r.ID)
if err := addAliases(ctx, r, ghsaClient); err != nil {
return err
if added := addMissingAliases(ctx, r, ghsaClient); added > 0 {
infolog.Printf("%s: added %d missing aliases", r.ID, added)
}
}
@ -1206,64 +1201,3 @@ func setDates(_ context.Context, filename string, dates map[string]gitrepo.Dates
r.Published = d.Oldest
return r.Write(filename)
}
func dedupeAndSort[T constraints.Ordered](s []T) []T {
s = slices.Clone(s)
slices.Sort(s)
return slices.Compact(s)
}
// addAliases finds and adds missing GHSAs and CVEs that correspond to the
// aliases already in the report.
// For now, this function does not do an exhaustive search for aliases (i.e., it does not
// search for aliases associated with the new ones we found), as it is relatively rare
// for a vuln to have more than one CVE and GHSA.
func addAliases(ctx context.Context, r *report.Report, gc *ghsa.Client) error {
origG := len(r.GHSAs)
r.GHSAs = dedupeAndSort(append(r.GHSAs, findGHSAs(ctx, r, gc)...))
origC := len(r.CVEs)
r.CVEs = dedupeAndSort(append(r.CVEs, findCVEs(ctx, r, gc)...))
if newG, newC := len(r.GHSAs)-origG, len(r.CVEs)-origC; newG+newC > 0 {
infolog.Printf("%s: found %d new GHSAs and %d new CVEs\n", r.ID, newG, newC)
}
return nil
}
func findGHSAs(ctx context.Context, r *report.Report, gc *ghsa.Client) []string {
var ghsas []string
for _, cve := range r.AllCVEs() {
sas, err := gc.ListForCVE(ctx, cve)
if err != nil {
errlog.Printf("%s: could not find GHSAs for %s", r.ID, cve)
continue
}
for _, sa := range sas {
ghsas = append(ghsas, sa.ID)
}
}
return ghsas
}
func findCVEs(ctx context.Context, r *report.Report, gc *ghsa.Client) []string {
var cves []string
for _, ghsa := range r.GHSAs {
sa, err := gc.FetchGHSA(ctx, ghsa)
if err != nil {
errlog.Printf("%s: could not fetch record for %s", r.ID, ghsa)
continue
}
for _, id := range sa.Identifiers {
if id.Type == "CVE" {
// Skip CVEs that we assigned, as we already know about them,
// and they are stored in "cve_metadata" instead of "cves".
if id.Value == r.GoCVE() {
continue
}
cves = append(cves, id.Value)
}
}
}
return cves
}