blob: 31432832233de72e02c92d5661e21e5af4970bb4 [file] [log] [blame]
// 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
// Command govulncheck reports known vulnerabilities filed in a vulnerability database
// (see https://golang.org/design/draft-vulndb) that affect a given package or binary.
//
// It uses static analysis or the binary's symbol table to narrow down reports to only
// those that potentially affect the application.
//
// WARNING WARNING WARNING
//
// govulncheck is still experimental and neither its output or the vulnerability
// database should be relied on to be stable or comprehensive.
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"go/build"
"log"
"os"
"strings"
"golang.org/x/exp/vulncheck"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/packages"
"golang.org/x/vuln/client"
)
var (
jsonFlag = flag.Bool("json", false, "")
verboseFlag = flag.Bool("v", false, "")
testsFlag = flag.Bool("tests", false, "")
)
const usage = `govulncheck: identify known vulnerabilities by call graph traversal.
Usage:
govulncheck [-json] [-all] [-tests] [-tags] {package pattern...}
govulncheck {binary path}
Flags:
-json Print vulnerability findings in JSON format.
-tags Comma-separated list of build tags.
-tests Boolean flag indicating if test files should be analyzed too.
govulncheck can be used with either one or more package patterns (i.e. golang.org/x/crypto/...
or ./...) or with a single path to a Go binary. In the latter case module and symbol
information will be extracted from the binary in order to detect vulnerable symbols.
The environment variable GOVULNDB can be set to a comma-separate list of vulnerability
database URLs, with http://, https://, or file:// protocols. Entries from multiple
databases are merged.
`
func init() {
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
}
func main() {
flag.Usage = func() { fmt.Fprint(os.Stderr, usage) }
flag.Parse()
if len(flag.Args()) == 0 {
fmt.Fprint(os.Stderr, usage)
os.Exit(1)
}
dbs := []string{"https://storage.googleapis.com/go-vulndb"}
if GOVULNDB := os.Getenv("GOVULNDB"); GOVULNDB != "" {
dbs = strings.Split(GOVULNDB, ",")
}
dbClient, err := client.NewClient(dbs, client.Options{HTTPCache: defaultCache()})
if err != nil {
fmt.Fprintf(os.Stderr, "govulncheck: %s\n", err)
os.Exit(1)
}
cfg := &packages.Config{
Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedDeps | packages.NeedModule,
Tests: *testsFlag,
BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(build.Default.BuildTags, ","))},
}
r, err := run(cfg, flag.Args(), dbClient)
if err != nil {
fmt.Fprintf(os.Stderr, "govulncheck: %s\n", err)
os.Exit(1)
}
writeOut(r, *jsonFlag)
}
func writeOut(r *vulncheck.Result, toJson bool) {
if !toJson {
fmt.Println(r)
return
}
b, err := json.MarshalIndent(r, "", "\t")
if err != nil {
fmt.Fprintf(os.Stderr, "govulncheck: %s\n", err)
os.Exit(1)
}
os.Stdout.Write(b)
os.Stdout.Write([]byte{'\n'})
}
func isFile(path string) bool {
s, err := os.Stat(path)
if err != nil {
return false
}
return !s.IsDir()
}
func run(cfg *packages.Config, patterns []string, dbClient client.Client) (*vulncheck.Result, error) {
vcfg := &vulncheck.Config{
Client: dbClient,
}
if len(patterns) == 1 && isFile(patterns[0]) {
f, err := os.Open(patterns[0])
if err != nil {
return nil, err
}
defer f.Close()
return vulncheck.Binary(context.Background(), f, vcfg)
}
// Load packages.
if *verboseFlag {
log.Println("loading packages...")
}
pkgs, err := packages.Load(cfg, patterns...)
if err != nil {
return nil, err
}
if packages.PrintErrors(pkgs) > 0 {
return nil, fmt.Errorf("packages contain errors")
}
if *verboseFlag {
log.Printf("\t%d loaded packages\n", len(pkgs))
}
return vulncheck.Source(context.Background(), vulncheck.Convert(pkgs), vcfg)
}