зеркало из https://github.com/golang/vulndb.git
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:
Родитель
2b185852c9
Коммит
9da573a4ce
|
@ -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)
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче