зеркало из https://github.com/golang/vulndb.git
226 строки
6.2 KiB
Go
226 строки
6.2 KiB
Go
// 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.
|
|
|
|
// Command inspect provides insights into the current contents of vulndb.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"text/tabwriter"
|
|
"time"
|
|
|
|
"golang.org/x/exp/maps"
|
|
"golang.org/x/vulndb/internal/genericosv"
|
|
"golang.org/x/vulndb/internal/ghsarepo"
|
|
"golang.org/x/vulndb/internal/report"
|
|
"golang.org/x/vulndb/internal/stdlib"
|
|
)
|
|
|
|
var (
|
|
localGHSA = flag.String("local-ghsa", "", "path to local GHSA repo, instead of cloning remote")
|
|
detail = flag.Bool("detail", false, "if true, print more details on GHSAs not yet in vulndb")
|
|
)
|
|
|
|
func main() {
|
|
start := time.Now()
|
|
flag.Parse()
|
|
|
|
ctx := context.Background()
|
|
rc, err := report.NewLocalClient(ctx, ".")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
var gc *ghsarepo.Client
|
|
if *localGHSA != "" {
|
|
gc, err = ghsarepo.NewLocalClient(ctx, *localGHSA)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
} else {
|
|
log.Println("cloning remote GHSA repo (use -local-ghsa to speed this up)...")
|
|
gc, err = ghsarepo.NewDefaultClient()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
overall, byYear := summarize(gc.List(), rc.List())
|
|
|
|
display(overall, byYear)
|
|
if *detail {
|
|
fmt.Println("\n=== GHSAs not yet in vulndb ===")
|
|
displayGHSAs(overall.ghsasNotInVDB)
|
|
}
|
|
|
|
fmt.Printf("\n%s\n", time.Since(start).Truncate(time.Millisecond))
|
|
}
|
|
|
|
func display(overall *summary, byYear map[int]*summary) {
|
|
tw := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0)
|
|
years := maps.Keys(byYear)
|
|
sort.Sort(sort.Reverse(sort.IntSlice(years))) // sort descending
|
|
|
|
headings := func() {
|
|
fmt.Fprintf(tw, "\t%s", "Total")
|
|
for _, year := range years {
|
|
fmt.Fprintf(tw, "\t%4d", year)
|
|
}
|
|
fmt.Fprintf(tw, "\n")
|
|
fmt.Fprintf(tw, "\t%s", "-----")
|
|
for range years {
|
|
fmt.Fprintf(tw, "\t%4s", "----")
|
|
}
|
|
fmt.Fprintf(tw, "\n")
|
|
}
|
|
data := func(desc string, indent int, getData func(s *summary) int) {
|
|
var indentS string
|
|
if indent > 0 {
|
|
indentS = strings.Repeat("--", indent) + " "
|
|
}
|
|
fmt.Fprintf(tw, "%s%s\t%4d", indentS, desc, getData(overall))
|
|
for _, year := range years {
|
|
fmt.Fprintf(tw, "\t%4d", getData(byYear[year]))
|
|
}
|
|
fmt.Fprintf(tw, "\n")
|
|
}
|
|
newline := func() {
|
|
fmt.Fprintf(tw, "%s\n", strings.Repeat("\t", len(years))) // preserve tab formatting
|
|
}
|
|
|
|
// Summary of Go reports by year.
|
|
headings()
|
|
data("Go reports", 0, func(s *summary) int { return s.reports })
|
|
data("Regular reports", 1, func(s *summary) int { return s.regular })
|
|
for _, rs := range []report.ReviewStatus{report.Reviewed, report.Unreviewed} {
|
|
data(rs.String(), 2, func(s *summary) int { return s.regularByReview[rs] })
|
|
}
|
|
data("Withdrawn reports", 1, func(s *summary) int { return s.withdrawn })
|
|
data("Excluded reports", 1, func(s *summary) int { return s.excluded })
|
|
for _, er := range report.ExcludedReasons {
|
|
data(string(er), 2, func(s *summary) int { return s.excludedByReason[er] })
|
|
}
|
|
data("Reports with no GHSA (+)", 1, func(s *summary) int { return s.noGHSA })
|
|
data("Stdlib, toolchain and x/ reports", 1, func(s *summary) int { return s.firstParty })
|
|
|
|
// Summary of GHSAs by year.
|
|
newline()
|
|
headings()
|
|
data("GHSAs affecting Go", 0, func(s *summary) int { return s.ghsas })
|
|
data("GHSAs not yet in vulndb (++)", 1, func(s *summary) int { return len(s.ghsasNotInVDB) })
|
|
|
|
// Additional context.
|
|
newline()
|
|
fmt.Fprintln(tw, "(+) \"Go reports with no GHSA\" are published third-party Go reports\nwith no corresponding GHSA. (This isn't a problem; it's informational only.)")
|
|
fmt.Fprintln(tw, "(++) \"GHSAs not yet in vulndb\" are published GHSAs with no corresponding\nGo report. There may already be an open issue on the tracker for these.")
|
|
|
|
tw.Flush()
|
|
}
|
|
|
|
func displayGHSAs(ghsas []string) {
|
|
for i, g := range ghsas {
|
|
fmt.Println()
|
|
fmt.Printf("%d) %s\n", i+1, g)
|
|
fmt.Printf("https://github.com/advisories/%s\n", g)
|
|
fmt.Printf("search issue tracker: https://github.com/golang/vulndb/issues?q=is%%3Aissue+%s\n", g)
|
|
}
|
|
}
|
|
|
|
type summary struct {
|
|
reports, regular, withdrawn, excluded, noGHSA, firstParty int
|
|
ghsas int
|
|
ghsasNotInVDB []string
|
|
excludedByReason map[report.ExcludedReason]int
|
|
regularByReview map[report.ReviewStatus]int
|
|
}
|
|
|
|
func newSummary() *summary {
|
|
return &summary{
|
|
excludedByReason: make(map[report.ExcludedReason]int),
|
|
regularByReview: make(map[report.ReviewStatus]int),
|
|
}
|
|
}
|
|
|
|
func summarize(ghsas []*genericosv.Entry, reports []*report.Report) (*summary, map[int]*summary) {
|
|
overall := newSummary()
|
|
byYear := make(map[int]*summary)
|
|
|
|
ghsasWithReport := make(map[string]bool)
|
|
for _, r := range reports {
|
|
year, err := strconv.Atoi(strings.Split(r.ID, "-")[1])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if _, ok := byYear[year]; !ok {
|
|
byYear[year] = newSummary()
|
|
}
|
|
yearSummary := byYear[year]
|
|
|
|
overall.reports++
|
|
yearSummary.reports++
|
|
|
|
if isFirstParty(r) {
|
|
overall.firstParty++
|
|
yearSummary.firstParty++
|
|
}
|
|
|
|
if r.IsExcluded() {
|
|
overall.excluded++
|
|
overall.excludedByReason[r.Excluded]++
|
|
|
|
yearSummary.excluded++
|
|
yearSummary.excludedByReason[r.Excluded]++
|
|
} else if r.Withdrawn != nil {
|
|
overall.withdrawn++
|
|
yearSummary.withdrawn++
|
|
} else {
|
|
overall.regular++
|
|
yearSummary.regular++
|
|
|
|
overall.regularByReview[r.ReviewStatus]++
|
|
yearSummary.regularByReview[r.ReviewStatus]++
|
|
}
|
|
|
|
if len(r.GHSAs) == 0 && r.CVEMetadata == nil {
|
|
overall.noGHSA++
|
|
yearSummary.noGHSA++
|
|
}
|
|
for _, ghsa := range r.GHSAs {
|
|
ghsasWithReport[ghsa] = true
|
|
}
|
|
}
|
|
|
|
for _, ghsa := range ghsas {
|
|
year := ghsa.Published.Year()
|
|
if _, ok := byYear[year]; !ok {
|
|
byYear[year] = newSummary()
|
|
}
|
|
yearSummary := byYear[year]
|
|
overall.ghsas++
|
|
yearSummary.ghsas++
|
|
if _, ok := ghsasWithReport[ghsa.ID]; !ok {
|
|
overall.ghsasNotInVDB = append(overall.ghsasNotInVDB, ghsa.ID)
|
|
yearSummary.ghsasNotInVDB = append(yearSummary.ghsasNotInVDB, ghsa.ID)
|
|
}
|
|
}
|
|
|
|
return overall, byYear
|
|
}
|
|
|
|
func isFirstParty(r *report.Report) bool {
|
|
for _, m := range r.Modules {
|
|
if stdlib.IsStdModule(m.Module) || stdlib.IsCmdModule(m.Module) || stdlib.IsXModule(m.Module) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|