cmd/triage: add command triage

Add a command, whose usage is "triage CVE-XXXX-YYYY"
(or "triage GHSA-xxxx-yyyy-zzzz", but that is trivial as GHSA's
specify their ecosystem explicitly) that gives direct
access to the worker triage algorithm.

This helps with experimentation and testing of tweaks to
the algorithm. (The goal is to make this much faster
by greatly reducing the number of requests made to pkgsite).

Change-Id: I74d54e60afbb1fe7ebf26fce4ae2d079ecb63b4b
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/601379
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Tatiana Bradley 2024-07-09 14:06:45 -04:00
Родитель 2b185852c9
Коммит 9da573a4ce
4 изменённых файлов: 238 добавлений и 1 удалений

71
cmd/triage/main.go Normal file
Просмотреть файл

@ -0,0 +1,71 @@
// Copyright 2024 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.
// Command triage provides direct access to the triage algorithm
// in internal/triage (used by the worker), which determines whether
// an external vuln likely affects Go or not.
package main
import (
"context"
"flag"
"fmt"
"log"
"text/tabwriter"
"golang.org/x/vulndb/internal/cveutils"
"golang.org/x/vulndb/internal/idstr"
)
func init() {
out := flag.CommandLine.Output()
flag.Usage = func() {
fmt.Fprintf(out, "usage:\n")
tw := tabwriter.NewWriter(out, 2, 4, 2, ' ', 0)
fmt.Fprintf(tw, " triage\t%s\t%s\n", "[<GHSA> | <CVE>]", "triage the given IDs")
fmt.Fprintf(tw, " triage\t%s\t%s\n", "latest-cves", "triage all the CVEs added/updated in the last month (VERY SLOW)")
tw.Flush()
}
}
func main() {
flag.Parse()
args := flag.Args()[0:]
if len(args) < 1 {
flag.Usage()
log.Fatal("argument(s) required")
}
ctx := context.Background()
if len(args) == 1 {
switch args[0] {
case "latest-cves":
cves, err := cveutils.Latest()
if err != nil {
log.Fatal(err)
}
triageCVEs(ctx, cves)
return
}
}
var ghsas, cves []string
for _, arg := range args {
switch {
case idstr.IsCVE(arg):
cves = append(cves, arg)
case idstr.IsGHSA(arg):
ghsas = append(ghsas, arg)
default:
flag.Usage()
log.Fatalf("unrecognized arg %s", arg)
}
}
triageCVEs(ctx, cves)
triageGHSAs(ctx, ghsas)
}

111
cmd/triage/triager.go Normal file
Просмотреть файл

@ -0,0 +1,111 @@
// Copyright 2024 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"
vlog "golang.org/x/vulndb/cmd/vulnreport/log"
"golang.org/x/vulndb/internal/cve5"
"golang.org/x/vulndb/internal/genericosv"
"golang.org/x/vulndb/internal/pkgsite"
"golang.org/x/vulndb/internal/report"
"golang.org/x/vulndb/internal/triage"
)
type triager interface {
triage(context.Context, string) error
}
type cveTriager struct {
report.Fetcher
pc *pkgsite.Client
}
func (t *cveTriager) triage(ctx context.Context, id string) error {
cve, err := fetchAs[*cve5.CVERecord](ctx, t, id)
if err != nil {
return err
}
result, err := triage.RefersToGoModule(ctx, cve, t.pc)
if err != nil {
return err
}
printResult(id, result)
return nil
}
type ghsaTriager struct {
report.Fetcher
}
func (t *ghsaTriager) triage(ctx context.Context, id string) error {
ghsa, err := fetchAs[*genericosv.Entry](ctx, t, id)
if err != nil {
return err
}
result := triage.ContainsGoModule(ghsa)
printResult(id, result)
return nil
}
func printResult(id string, result *triage.Result) {
if result == nil {
vlog.Infof("%s does not appear to be a Go vulnerability", id)
return
}
vlog.Outf("%s is likely a Go vulnerability", id)
if result.ModulePath != "" {
vlog.Outf("Module: %s", result.ModulePath)
}
if result.PackagePath != "" {
vlog.Outf("Package: %s", result.PackagePath)
}
if result.Reason != "" {
vlog.Outf("Reason: %s", result.Reason)
}
}
func fetchAs[T any](ctx context.Context, f report.Fetcher, id string) (T, error) {
var zero T
src, err := f.Fetch(ctx, id)
if err != nil {
return zero, err
}
v, ok := src.(T)
if !ok {
return zero, fmt.Errorf("%s cannot be cast as %T", src, zero)
}
return v, nil
}
func triageCVEs(ctx context.Context, cves []string) {
if len(cves) == 0 {
return
}
t := &cveTriager{Fetcher: cve5.NewFetcher(), pc: pkgsite.Default()}
triageBatch(ctx, t, cves)
}
func triageGHSAs(ctx context.Context, ghsas []string) {
if len(ghsas) == 0 {
return
}
t := &ghsaTriager{Fetcher: genericosv.NewFetcher()}
triageBatch(ctx, t, ghsas)
}
func triageBatch(ctx context.Context, t triager, ids []string) {
for _, id := range ids {
if err := t.triage(ctx, id); err != nil {
vlog.Err(err)
}
}
}

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

@ -16,6 +16,8 @@ import (
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
const deltaLogURL = "https://raw.githubusercontent.com/CVEProject/cvelistV5/main/cves/deltaLog.json"
// List returns the ids for all CVEs added or updated in the // List returns the ids for all CVEs added or updated in the
// cvelistV5 repo at or after the given 'since' time. // cvelistV5 repo at or after the given 'since' time.
// //
@ -23,10 +25,15 @@ import (
// the CVEs are pulled from a log maintained by the CVE program // the CVEs are pulled from a log maintained by the CVE program
// which only contains updates from the past month. // which only contains updates from the past month.
func List(since time.Time) ([]string, error) { func List(since time.Time) ([]string, error) {
const deltaLogURL = "https://raw.githubusercontent.com/CVEProject/cvelistV5/main/cves/deltaLog.json"
return list(http.DefaultClient, deltaLogURL, since) return list(http.DefaultClient, deltaLogURL, since)
} }
// Latest returns the ids of all CVEs that were added or updated
// in the past month, according to the latest version of the "delta log".
func Latest() ([]string, error) {
return latest(http.DefaultClient, deltaLogURL)
}
type deltaLog []*updateMeta type deltaLog []*updateMeta
type updateMeta struct { type updateMeta struct {
@ -101,6 +108,31 @@ func list(c *http.Client, url string, since time.Time) ([]string, error) {
return cves, nil return cves, nil
} }
func latest(c *http.Client, url string) ([]string, error) {
b, err := fetch(c, url)
if err != nil {
return nil, err
}
var dl deltaLog
if err := json.Unmarshal(b, &dl); err != nil {
return nil, err
}
var cves []string
for _, um := range dl {
for _, c := range um.cves() {
cves = append(cves, c.ID)
}
}
// Remove any duplicates.
slices.Sort(cves)
cves = slices.Compact(cves)
return cves, nil
}
func fetch(c *http.Client, url string) ([]byte, error) { func fetch(c *http.Client, url string) ([]byte, error) {
resp, err := c.Get(url) resp, err := c.Get(url)
if err != nil { if err != nil {

23
internal/triage/osv.go Normal file
Просмотреть файл

@ -0,0 +1,23 @@
// Copyright 2024 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 triage
import (
"fmt"
"golang.org/x/vulndb/internal/genericosv"
)
func ContainsGoModule(osv *genericosv.Entry) *Result {
for _, a := range osv.Affected {
if a.Package.Ecosystem == genericosv.EcosystemGo {
return &Result{
ModulePath: a.Package.Name,
Reason: fmt.Sprintf("%q is marked as Go ecosystem", a.Package.Name),
}
}
}
return nil
}