whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
// 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
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
|
||||
"golang.org/x/vuln/internal/buildinfo"
|
||||
"golang.org/x/vuln/internal/client"
|
||||
"golang.org/x/vuln/internal/derrors"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/vulncheck"
|
||||
)
|
||||
|
||||
// runBinary detects presence of vulnerable symbols in an executable or its minimal blob representation.
|
||||
func runBinary(ctx context.Context, handler govulncheck.Handler, cfg *config, client *client.Client) (err error) {
|
||||
defer derrors.Wrap(&err, "govulncheck")
|
||||
|
||||
bin, err := createBin(cfg.patterns[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := &govulncheck.Progress{Message: binaryProgressMessage}
|
||||
if err := handler.Progress(p); err != nil {
|
||||
return err
|
||||
}
|
||||
return vulncheck.Binary(ctx, handler, bin, &cfg.Config, client)
|
||||
}
|
||||
|
||||
func createBin(path string) (*vulncheck.Bin, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// First check if the path points to a Go binary. Otherwise, blob
|
||||
// parsing might json decode a Go binary which takes time.
|
||||
//
|
||||
// TODO(#64716): use fingerprinting to make this precise, clean, and fast.
|
||||
mods, packageSymbols, bi, err := buildinfo.ExtractPackagesAndSymbols(f)
|
||||
if err == nil {
|
||||
return &vulncheck.Bin{
|
||||
Modules: mods,
|
||||
PkgSymbols: packageSymbols,
|
||||
GoVersion: bi.GoVersion,
|
||||
GOOS: findSetting("GOOS", bi),
|
||||
GOARCH: findSetting("GOARCH", bi),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Otherwise, see if the path points to a valid blob.
|
||||
bin := parseBlob(f)
|
||||
if bin != nil {
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("unrecognized binary format")
|
||||
}
|
||||
|
||||
// parseBlob extracts vulncheck.Bin from a valid blob. If it
|
||||
// cannot recognize a valid blob, returns nil.
|
||||
func parseBlob(from io.Reader) *vulncheck.Bin {
|
||||
dec := json.NewDecoder(from)
|
||||
|
||||
var h header
|
||||
if err := dec.Decode(&h); err != nil {
|
||||
return nil // no header
|
||||
} else if h.Name != extractModeID || h.Version != extractModeVersion {
|
||||
return nil // invalid header
|
||||
}
|
||||
|
||||
var b vulncheck.Bin
|
||||
if err := dec.Decode(&b); err != nil {
|
||||
return nil // no body
|
||||
}
|
||||
if dec.More() {
|
||||
return nil // we want just header and body, nothing else
|
||||
}
|
||||
return &b
|
||||
}
|
||||
|
||||
// findSetting returns value of setting from bi if present.
|
||||
// Otherwise, returns "".
|
||||
func findSetting(setting string, bi *debug.BuildInfo) string {
|
||||
for _, s := range bi.Settings {
|
||||
if s.Key == setting {
|
||||
return s.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// 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.
|
||||
package scan
|
||||
|
||||
const (
|
||||
// These are all the constants for the terminal escape strings
|
||||
|
||||
colorEscape = "\033["
|
||||
colorEnd = "m"
|
||||
|
||||
colorReset = colorEscape + "0" + colorEnd
|
||||
colorBold = colorEscape + "1" + colorEnd
|
||||
colorFaint = colorEscape + "2" + colorEnd
|
||||
colorUnderline = colorEscape + "4" + colorEnd
|
||||
colorBlink = colorEscape + "5" + colorEnd
|
||||
|
||||
fgBlack = colorEscape + "30" + colorEnd
|
||||
fgRed = colorEscape + "31" + colorEnd
|
||||
fgGreen = colorEscape + "32" + colorEnd
|
||||
fgYellow = colorEscape + "33" + colorEnd
|
||||
fgBlue = colorEscape + "34" + colorEnd
|
||||
fgMagenta = colorEscape + "35" + colorEnd
|
||||
fgCyan = colorEscape + "36" + colorEnd
|
||||
fgWhite = colorEscape + "37" + colorEnd
|
||||
|
||||
bgBlack = colorEscape + "40" + colorEnd
|
||||
bgRed = colorEscape + "41" + colorEnd
|
||||
bgGreen = colorEscape + "42" + colorEnd
|
||||
bgYellow = colorEscape + "43" + colorEnd
|
||||
bgBlue = colorEscape + "44" + colorEnd
|
||||
bgMagenta = colorEscape + "45" + colorEnd
|
||||
bgCyan = colorEscape + "46" + colorEnd
|
||||
bgWhite = colorEscape + "47" + colorEnd
|
||||
|
||||
fgBlackHi = colorEscape + "90" + colorEnd
|
||||
fgRedHi = colorEscape + "91" + colorEnd
|
||||
fgGreenHi = colorEscape + "92" + colorEnd
|
||||
fgYellowHi = colorEscape + "93" + colorEnd
|
||||
fgBlueHi = colorEscape + "94" + colorEnd
|
||||
fgMagentaHi = colorEscape + "95" + colorEnd
|
||||
fgCyanHi = colorEscape + "96" + colorEnd
|
||||
fgWhiteHi = colorEscape + "97" + colorEnd
|
||||
|
||||
bgBlackHi = colorEscape + "100" + colorEnd
|
||||
bgRedHi = colorEscape + "101" + colorEnd
|
||||
bgGreenHi = colorEscape + "102" + colorEnd
|
||||
bgYellowHi = colorEscape + "103" + colorEnd
|
||||
bgBlueHi = colorEscape + "104" + colorEnd
|
||||
bgMagentaHi = colorEscape + "105" + colorEnd
|
||||
bgCyanHi = colorEscape + "106" + colorEnd
|
||||
bgWhiteHi = colorEscape + "107" + colorEnd
|
||||
)
|
||||
|
||||
const (
|
||||
_ = colorReset
|
||||
_ = colorBold
|
||||
_ = colorFaint
|
||||
_ = colorUnderline
|
||||
_ = colorBlink
|
||||
|
||||
_ = fgBlack
|
||||
_ = fgRed
|
||||
_ = fgGreen
|
||||
_ = fgYellow
|
||||
_ = fgBlue
|
||||
_ = fgMagenta
|
||||
_ = fgCyan
|
||||
_ = fgWhite
|
||||
|
||||
_ = fgBlackHi
|
||||
_ = fgRedHi
|
||||
_ = fgGreenHi
|
||||
_ = fgYellowHi
|
||||
_ = fgBlueHi
|
||||
_ = fgMagentaHi
|
||||
_ = fgCyanHi
|
||||
_ = fgWhiteHi
|
||||
|
||||
_ = bgBlack
|
||||
_ = bgRed
|
||||
_ = bgGreen
|
||||
_ = bgYellow
|
||||
_ = bgBlue
|
||||
_ = bgMagenta
|
||||
_ = bgCyan
|
||||
_ = bgWhite
|
||||
|
||||
_ = bgBlackHi
|
||||
_ = bgRedHi
|
||||
_ = bgGreenHi
|
||||
_ = bgYellowHi
|
||||
_ = bgBlueHi
|
||||
_ = bgMagentaHi
|
||||
_ = bgCyanHi
|
||||
_ = bgWhiteHi
|
||||
)
|
||||
@@ -0,0 +1,67 @@
|
||||
// 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//lint:file-ignore ST1005 Ignore staticcheck message about error formatting
|
||||
var (
|
||||
// ErrVulnerabilitiesFound indicates that vulnerabilities were detected
|
||||
// when running govulncheck. This returns exit status 3 when running
|
||||
// without the -json flag.
|
||||
errVulnerabilitiesFound = &exitCodeError{message: "vulnerabilities found", code: 3}
|
||||
|
||||
// errHelp indicates that usage help was requested.
|
||||
errHelp = &exitCodeError{message: "help requested", code: 0}
|
||||
|
||||
// errUsage indicates that there was a usage error on the command line.
|
||||
//
|
||||
// In this case, we assume that the user does not know how to run
|
||||
// govulncheck, and print the usage message with exit status 2.
|
||||
errUsage = &exitCodeError{message: "invalid usage", code: 2}
|
||||
|
||||
// errGoVersionMismatch is used to indicate that there is a mismatch between
|
||||
// the Go version used to build govulncheck and the one currently on PATH.
|
||||
errGoVersionMismatch = errors.New(`Loading packages failed, possibly due to a mismatch between the Go version
|
||||
used to build govulncheck and the Go version on PATH. Consider rebuilding
|
||||
govulncheck with the current Go version.`)
|
||||
|
||||
// errNoGoMod indicates that a go.mod file was not found in this module.
|
||||
errNoGoMod = errors.New(`no go.mod file
|
||||
|
||||
govulncheck only works with Go modules. Try navigating to your module directory.
|
||||
Otherwise, run go mod init to make your project a module.
|
||||
|
||||
See https://go.dev/doc/modules/managing-dependencies for more information.`)
|
||||
|
||||
// errNoBinaryFlag indicates that govulncheck was run on a file, without
|
||||
// the -mode=binary flag.
|
||||
errNoBinaryFlag = errors.New(`By default, govulncheck runs source analysis on Go modules.
|
||||
|
||||
Did you mean to run govulncheck with -mode=binary?
|
||||
|
||||
For details, run govulncheck -h.`)
|
||||
)
|
||||
|
||||
type exitCodeError struct {
|
||||
message string
|
||||
code int
|
||||
}
|
||||
|
||||
func (e *exitCodeError) Error() string { return e.message }
|
||||
func (e *exitCodeError) ExitCode() int { return e.code }
|
||||
|
||||
// isGoVersionMismatchError checks if err is due to mismatch between
|
||||
// the Go version used to build govulncheck and the one currently
|
||||
// on PATH.
|
||||
func isGoVersionMismatchError(err error) bool {
|
||||
msg := err.Error()
|
||||
// See golang.org/x/tools/go/packages/packages.go.
|
||||
return strings.Contains(msg, "This application uses version go") &&
|
||||
strings.Contains(msg, "It may fail to process source files")
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/vuln/internal/derrors"
|
||||
"golang.org/x/vuln/internal/vulncheck"
|
||||
)
|
||||
|
||||
const (
|
||||
// extractModeID is the unique name of the extract mode protocol
|
||||
extractModeID = "govulncheck-extract"
|
||||
extractModeVersion = "0.1.0"
|
||||
)
|
||||
|
||||
// header information for the blob output.
|
||||
type header struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// runExtract dumps the extracted abstraction of binary at cfg.patterns to out.
|
||||
// It prints out exactly two blob messages, one with the header and one with
|
||||
// the vulncheck.Bin as the body.
|
||||
func runExtract(cfg *config, out io.Writer) (err error) {
|
||||
defer derrors.Wrap(&err, "govulncheck")
|
||||
|
||||
bin, err := createBin(cfg.patterns[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sortBin(bin) // sort for easier testing and validation
|
||||
header := header{
|
||||
Name: extractModeID,
|
||||
Version: extractModeVersion,
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(out)
|
||||
|
||||
if err := enc.Encode(header); err != nil {
|
||||
return fmt.Errorf("marshaling blob header: %v", err)
|
||||
}
|
||||
if err := enc.Encode(bin); err != nil {
|
||||
return fmt.Errorf("marshaling blob body: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sortBin(bin *vulncheck.Bin) {
|
||||
sort.SliceStable(bin.PkgSymbols, func(i, j int) bool {
|
||||
return bin.PkgSymbols[i].Pkg+"."+bin.PkgSymbols[i].Name < bin.PkgSymbols[j].Pkg+"."+bin.PkgSymbols[j].Name
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AbsRelShorter takes path and returns its path relative
|
||||
// to the current directory, if shorter. Returns path
|
||||
// when path is an empty string or upon any error.
|
||||
func AbsRelShorter(path string) string {
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
c, err := filepath.Abs(".")
|
||||
if err != nil {
|
||||
return path
|
||||
}
|
||||
r, err := filepath.Rel(c, path)
|
||||
if err != nil {
|
||||
return path
|
||||
}
|
||||
|
||||
rSegments := strings.Split(r, string(filepath.Separator))
|
||||
pathSegments := strings.Split(path, string(filepath.Separator))
|
||||
if len(rSegments) < len(pathSegments) {
|
||||
return r
|
||||
}
|
||||
return path
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAbsRelShorter(t *testing.T) {
|
||||
thisFileAbs, _ := filepath.Abs("filepath_test.go")
|
||||
|
||||
for _, test := range []struct {
|
||||
l string
|
||||
want string
|
||||
}{
|
||||
{"filepath_test.go", "filepath_test.go"},
|
||||
{thisFileAbs, "filepath_test.go"},
|
||||
} {
|
||||
if got := AbsRelShorter(test.l); got != test.want {
|
||||
t.Errorf("want %s; got %s", test.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
govulncheck.Config
|
||||
patterns []string
|
||||
mode string
|
||||
db string
|
||||
json bool
|
||||
dir string
|
||||
tags []string
|
||||
test bool
|
||||
show []string
|
||||
env []string
|
||||
}
|
||||
|
||||
const (
|
||||
modeBinary = "binary"
|
||||
modeSource = "source"
|
||||
modeConvert = "convert" // only intended for use by gopls
|
||||
modeQuery = "query" // only intended for use by gopls
|
||||
modeExtract = "extract" // currently, only binary extraction is supported
|
||||
)
|
||||
|
||||
func parseFlags(cfg *config, stderr io.Writer, args []string) error {
|
||||
var tagsFlag buildutil.TagsFlag
|
||||
var showFlag showFlag
|
||||
var version bool
|
||||
flags := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
flags.SetOutput(stderr)
|
||||
flags.BoolVar(&cfg.json, "json", false, "output JSON")
|
||||
flags.BoolVar(&cfg.test, "test", false, "analyze test files (only valid for source mode, default false)")
|
||||
flags.StringVar(&cfg.dir, "C", "", "change to `dir` before running govulncheck")
|
||||
flags.StringVar(&cfg.db, "db", "https://vuln.go.dev", "vulnerability database `url`")
|
||||
flags.StringVar(&cfg.mode, "mode", modeSource, "supports source or binary")
|
||||
flags.Var(&tagsFlag, "tags", "comma-separated `list` of build tags")
|
||||
flags.Var(&showFlag, "show", "enable display of additional information specified by the comma separated `list`\nThe supported values are 'traces','color', 'version', and 'verbose'")
|
||||
flags.BoolVar(&version, "version", false, "print the version information")
|
||||
scanLevel := flags.String("scan", "symbol", "set the scanning level desired, one of module, package or symbol")
|
||||
flags.Usage = func() {
|
||||
fmt.Fprint(flags.Output(), `Govulncheck reports known vulnerabilities in dependencies.
|
||||
|
||||
Usage:
|
||||
|
||||
govulncheck [flags] [patterns]
|
||||
govulncheck -mode=binary [flags] [binary]
|
||||
|
||||
`)
|
||||
flags.PrintDefaults()
|
||||
fmt.Fprintf(flags.Output(), "\n%s\n", detailsMessage)
|
||||
}
|
||||
if err := flags.Parse(args); err != nil {
|
||||
if err == flag.ErrHelp {
|
||||
return errHelp
|
||||
}
|
||||
return err
|
||||
}
|
||||
cfg.patterns = flags.Args()
|
||||
cfg.tags = tagsFlag
|
||||
cfg.show = showFlag
|
||||
if version {
|
||||
cfg.show = append(cfg.show, "version")
|
||||
}
|
||||
cfg.ScanLevel = govulncheck.ScanLevel(*scanLevel)
|
||||
if err := validateConfig(cfg); err != nil {
|
||||
fmt.Fprintln(flags.Output(), err)
|
||||
return errUsage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var supportedModes = map[string]bool{
|
||||
modeSource: true,
|
||||
modeBinary: true,
|
||||
modeConvert: true,
|
||||
modeQuery: true,
|
||||
modeExtract: true,
|
||||
}
|
||||
|
||||
var supportedLevels = map[string]bool{
|
||||
govulncheck.ScanLevelModule: true,
|
||||
govulncheck.ScanLevelPackage: true,
|
||||
govulncheck.ScanLevelSymbol: true,
|
||||
}
|
||||
|
||||
func validateConfig(cfg *config) error {
|
||||
if _, ok := supportedModes[cfg.mode]; !ok {
|
||||
return fmt.Errorf("%q is not a valid mode", cfg.mode)
|
||||
}
|
||||
if _, ok := supportedLevels[string(cfg.ScanLevel)]; !ok {
|
||||
return fmt.Errorf("%q is not a valid scan level", cfg.ScanLevel)
|
||||
}
|
||||
switch cfg.mode {
|
||||
case modeSource:
|
||||
if len(cfg.patterns) == 1 && isFile(cfg.patterns[0]) {
|
||||
return fmt.Errorf("%q is a file.\n\n%v", cfg.patterns[0], errNoBinaryFlag)
|
||||
}
|
||||
if cfg.ScanLevel == govulncheck.ScanLevelModule && len(cfg.patterns) != 0 {
|
||||
return fmt.Errorf("patterns are not accepted for module only scanning")
|
||||
}
|
||||
case modeBinary:
|
||||
if cfg.test {
|
||||
return fmt.Errorf("the -test flag is not supported in binary mode")
|
||||
}
|
||||
if len(cfg.tags) > 0 {
|
||||
return fmt.Errorf("the -tags flag is not supported in binary mode")
|
||||
}
|
||||
if len(cfg.patterns) != 1 {
|
||||
return fmt.Errorf("only 1 binary can be analyzed at a time")
|
||||
}
|
||||
if !isFile(cfg.patterns[0]) {
|
||||
return fmt.Errorf("%q is not a file", cfg.patterns[0])
|
||||
}
|
||||
case modeExtract:
|
||||
if cfg.test {
|
||||
return fmt.Errorf("the -test flag is not supported in extract mode")
|
||||
}
|
||||
if len(cfg.tags) > 0 {
|
||||
return fmt.Errorf("the -tags flag is not supported in extract mode")
|
||||
}
|
||||
if len(cfg.patterns) != 1 {
|
||||
return fmt.Errorf("only 1 binary can be extracted at a time")
|
||||
}
|
||||
if cfg.json {
|
||||
return fmt.Errorf("the -json flag must be off in extract mode")
|
||||
}
|
||||
if !isFile(cfg.patterns[0]) {
|
||||
return fmt.Errorf("%q is not a file (source extraction is not supported)", cfg.patterns[0])
|
||||
}
|
||||
case modeConvert:
|
||||
if len(cfg.patterns) != 0 {
|
||||
return fmt.Errorf("patterns are not accepted in convert mode")
|
||||
}
|
||||
if cfg.dir != "" {
|
||||
return fmt.Errorf("the -C flag is not supported in convert mode")
|
||||
}
|
||||
if cfg.test {
|
||||
return fmt.Errorf("the -test flag is not supported in convert mode")
|
||||
}
|
||||
if len(cfg.tags) > 0 {
|
||||
return fmt.Errorf("the -tags flag is not supported in convert mode")
|
||||
}
|
||||
case modeQuery:
|
||||
if cfg.test {
|
||||
return fmt.Errorf("the -test flag is not supported in query mode")
|
||||
}
|
||||
if len(cfg.tags) > 0 {
|
||||
return fmt.Errorf("the -tags flag is not supported in query mode")
|
||||
}
|
||||
if !cfg.json {
|
||||
return fmt.Errorf("the -json flag must be set in query mode")
|
||||
}
|
||||
for _, pattern := range cfg.patterns {
|
||||
// Parse the input here so that we can catch errors before
|
||||
// outputting the Config.
|
||||
if _, _, err := parseModuleQuery(pattern); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if cfg.json && len(cfg.show) > 0 {
|
||||
return fmt.Errorf("the -show flag is not supported for JSON output")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isFile(path string) bool {
|
||||
s, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return !s.IsDir()
|
||||
}
|
||||
|
||||
type showFlag []string
|
||||
|
||||
func (v *showFlag) Set(s string) error {
|
||||
*v = append(*v, strings.Split(s, ",")...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *showFlag) Get() interface{} { return *f }
|
||||
func (f *showFlag) String() string { return "<options>" }
|
||||
@@ -0,0 +1,79 @@
|
||||
// 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.
|
||||
package scan_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/scan"
|
||||
)
|
||||
|
||||
var update = flag.Bool("update", false, "update test files with results")
|
||||
|
||||
func TestPrinting(t *testing.T) {
|
||||
testdata := os.DirFS("testdata")
|
||||
inputs, err := fs.Glob(testdata, "*.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, input := range inputs {
|
||||
name := strings.TrimSuffix(input, ".json")
|
||||
rawJSON, _ := fs.ReadFile(testdata, input)
|
||||
textfiles, err := fs.Glob(testdata, name+"*.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, textfile := range textfiles {
|
||||
textname := strings.TrimSuffix(textfile, ".txt")
|
||||
t.Run(textname, func(t *testing.T) {
|
||||
wantText, _ := fs.ReadFile(testdata, textfile)
|
||||
got := &bytes.Buffer{}
|
||||
handler := scan.NewTextHandler(got)
|
||||
handler.Show(strings.Split(textname, "_")[1:])
|
||||
testRunHandler(t, rawJSON, handler)
|
||||
if diff := cmp.Diff(string(wantText), got.String()); diff != "" {
|
||||
if *update {
|
||||
// write the output back to the file
|
||||
os.WriteFile(filepath.Join("testdata", textfile), got.Bytes(), 0644)
|
||||
return
|
||||
}
|
||||
t.Errorf("Readable mismatch (-want, +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
t.Run(name+"_json", func(t *testing.T) {
|
||||
// this effectively tests that we can round trip the json
|
||||
got := &strings.Builder{}
|
||||
testRunHandler(t, rawJSON, govulncheck.NewJSONHandler(got))
|
||||
if diff := cmp.Diff(strings.TrimSpace(string(rawJSON)), strings.TrimSpace(got.String())); diff != "" {
|
||||
t.Errorf("JSON mismatch (-want, +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testRunHandler(t *testing.T, rawJSON []byte, handler govulncheck.Handler) {
|
||||
if err := govulncheck.HandleJSON(bytes.NewReader(rawJSON), handler); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err := scan.Flush(handler)
|
||||
switch e := err.(type) {
|
||||
case nil:
|
||||
case interface{ ExitCode() int }:
|
||||
if e.ExitCode() != 0 && e.ExitCode() != 3 {
|
||||
// not success or vulnerabilities found
|
||||
t.Fatal(err)
|
||||
}
|
||||
default:
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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 scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"golang.org/x/vuln/internal/client"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
isem "golang.org/x/vuln/internal/semver"
|
||||
)
|
||||
|
||||
// runQuery reports vulnerabilities that apply to the queries in the config.
|
||||
func runQuery(ctx context.Context, handler govulncheck.Handler, cfg *config, c *client.Client) error {
|
||||
reqs := make([]*client.ModuleRequest, len(cfg.patterns))
|
||||
for i, query := range cfg.patterns {
|
||||
mod, ver, err := parseModuleQuery(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := handler.Progress(queryProgressMessage(mod, ver)); err != nil {
|
||||
return err
|
||||
}
|
||||
reqs[i] = &client.ModuleRequest{
|
||||
Path: mod, Version: ver,
|
||||
}
|
||||
}
|
||||
|
||||
resps, err := c.ByModules(ctx, reqs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ids := make(map[string]bool)
|
||||
for _, resp := range resps {
|
||||
for _, entry := range resp.Entries {
|
||||
if _, ok := ids[entry.ID]; !ok {
|
||||
err := handler.OSV(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ids[entry.ID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func queryProgressMessage(module, version string) *govulncheck.Progress {
|
||||
return &govulncheck.Progress{
|
||||
Message: fmt.Sprintf("Looking up vulnerabilities in %s at %s...", module, version),
|
||||
}
|
||||
}
|
||||
|
||||
var modQueryRegex = regexp.MustCompile(`(.+)@(.+)`)
|
||||
|
||||
func parseModuleQuery(pattern string) (_ string, _ string, err error) {
|
||||
matches := modQueryRegex.FindStringSubmatch(pattern)
|
||||
// matches should be [module@version, module, version]
|
||||
if len(matches) != 3 {
|
||||
return "", "", fmt.Errorf("invalid query %s: must be of the form module@version", pattern)
|
||||
}
|
||||
mod, ver := matches[1], matches[2]
|
||||
if !isem.Valid(ver) {
|
||||
return "", "", fmt.Errorf("version %s is not valid semver", ver)
|
||||
}
|
||||
|
||||
return mod, ver, nil
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// 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 scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/vuln/internal/client"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
"golang.org/x/vuln/internal/test"
|
||||
)
|
||||
|
||||
func TestRunQuery(t *testing.T) {
|
||||
e := &osv.Entry{
|
||||
ID: "GO-1999-0001",
|
||||
Affected: []osv.Affected{{
|
||||
Module: osv.Module{Path: "bad.com"},
|
||||
Ranges: []osv.Range{{
|
||||
Type: osv.RangeTypeSemver,
|
||||
Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "1.2.3"}},
|
||||
}},
|
||||
EcosystemSpecific: osv.EcosystemSpecific{
|
||||
Packages: []osv.Package{{
|
||||
Path: "bad.com",
|
||||
}, {
|
||||
Path: "bad.com/bad",
|
||||
}},
|
||||
},
|
||||
}, {
|
||||
Module: osv.Module{Path: "unfixable.com"},
|
||||
Ranges: []osv.Range{{
|
||||
Type: osv.RangeTypeSemver,
|
||||
Events: []osv.RangeEvent{{Introduced: "0"}}, // no fix
|
||||
}},
|
||||
EcosystemSpecific: osv.EcosystemSpecific{
|
||||
Packages: []osv.Package{{
|
||||
Path: "unfixable.com",
|
||||
}},
|
||||
},
|
||||
}},
|
||||
}
|
||||
e2 := &osv.Entry{
|
||||
ID: "GO-1999-0002",
|
||||
Affected: []osv.Affected{{
|
||||
Module: osv.Module{Path: "bad.com"},
|
||||
Ranges: []osv.Range{{
|
||||
Type: osv.RangeTypeSemver,
|
||||
Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "1.2.0"}},
|
||||
}},
|
||||
EcosystemSpecific: osv.EcosystemSpecific{
|
||||
Packages: []osv.Package{{
|
||||
Path: "bad.com/pkg",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
stdlib := &osv.Entry{
|
||||
ID: "GO-2000-0003",
|
||||
Affected: []osv.Affected{{
|
||||
Module: osv.Module{Path: "stdlib"},
|
||||
Ranges: []osv.Range{{
|
||||
Type: osv.RangeTypeSemver,
|
||||
Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "1.19.4"}},
|
||||
}},
|
||||
EcosystemSpecific: osv.EcosystemSpecific{
|
||||
Packages: []osv.Package{{
|
||||
Path: "net/http",
|
||||
}},
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
c, err := client.NewInMemoryClient([]*osv.Entry{e, e2, stdlib})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
for _, tc := range []struct {
|
||||
query []string
|
||||
want []*osv.Entry
|
||||
}{
|
||||
{
|
||||
query: []string{"stdlib@go1.18"},
|
||||
want: []*osv.Entry{stdlib},
|
||||
},
|
||||
{
|
||||
query: []string{"stdlib@1.18"},
|
||||
want: []*osv.Entry{stdlib},
|
||||
},
|
||||
{
|
||||
query: []string{"stdlib@v1.18.0"},
|
||||
want: []*osv.Entry{stdlib},
|
||||
},
|
||||
{
|
||||
query: []string{"bad.com@1.2.3"},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
query: []string{"bad.com@v1.1.0"},
|
||||
want: []*osv.Entry{e, e2},
|
||||
},
|
||||
{
|
||||
query: []string{"unfixable.com@2.0.0"},
|
||||
want: []*osv.Entry{e},
|
||||
},
|
||||
{
|
||||
// each entry should only show up once
|
||||
query: []string{"bad.com@1.1.0", "unfixable.com@2.0.0"},
|
||||
want: []*osv.Entry{e, e2},
|
||||
},
|
||||
{
|
||||
query: []string{"stdlib@1.18", "unfixable.com@2.0.0"},
|
||||
want: []*osv.Entry{stdlib, e},
|
||||
},
|
||||
} {
|
||||
t.Run(strings.Join(tc.query, ","), func(t *testing.T) {
|
||||
h := test.NewMockHandler()
|
||||
err := runQuery(ctx, h, &config{patterns: tc.query}, c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(h.OSVMessages, tc.want); diff != "" {
|
||||
t.Errorf("runQuery: unexpected diff:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModuleQuery(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
pattern, wantMod, wantVer string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
pattern: "stdlib@go1.18",
|
||||
wantMod: "stdlib",
|
||||
wantVer: "go1.18",
|
||||
},
|
||||
{
|
||||
pattern: "golang.org/x/tools@v0.0.0-20140414041502-123456789012",
|
||||
wantMod: "golang.org/x/tools",
|
||||
wantVer: "v0.0.0-20140414041502-123456789012",
|
||||
},
|
||||
{
|
||||
pattern: "golang.org/x/tools",
|
||||
wantErr: "invalid query",
|
||||
},
|
||||
{
|
||||
pattern: "golang.org/x/tools@1.0.0.2",
|
||||
wantErr: "not valid semver",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.pattern, func(t *testing.T) {
|
||||
gotMod, gotVer, err := parseModuleQuery(tc.pattern)
|
||||
if tc.wantErr == "" {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if gotMod != tc.wantMod || gotVer != tc.wantVer {
|
||||
t.Errorf("parseModuleQuery = (%s, %s), want (%s, %s)", gotMod, gotVer, tc.wantMod, tc.wantVer)
|
||||
}
|
||||
} else {
|
||||
if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
|
||||
t.Errorf("parseModuleQuery = %v, want err containing %s", err, tc.wantErr)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
func TestFrame(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
frame *govulncheck.Frame
|
||||
short bool
|
||||
wantFunc string
|
||||
wantPos string
|
||||
}{
|
||||
{
|
||||
name: "position and function",
|
||||
frame: &govulncheck.Frame{
|
||||
Package: "golang.org/x/vuln/internal/vulncheck",
|
||||
Function: "Foo",
|
||||
Position: &govulncheck.Position{Filename: "some/path/file.go", Line: 12},
|
||||
},
|
||||
wantFunc: "golang.org/x/vuln/internal/vulncheck.Foo",
|
||||
wantPos: "some/path/file.go:12",
|
||||
},
|
||||
{
|
||||
name: "receiver",
|
||||
frame: &govulncheck.Frame{
|
||||
Package: "golang.org/x/vuln/internal/vulncheck",
|
||||
Receiver: "Bar",
|
||||
Function: "Foo",
|
||||
},
|
||||
wantFunc: "golang.org/x/vuln/internal/vulncheck.Bar.Foo",
|
||||
},
|
||||
{
|
||||
name: "function and receiver",
|
||||
frame: &govulncheck.Frame{Receiver: "*ServeMux", Function: "Handle"},
|
||||
wantFunc: "ServeMux.Handle",
|
||||
},
|
||||
{
|
||||
name: "package and function",
|
||||
frame: &govulncheck.Frame{Package: "net/http", Function: "Get"},
|
||||
wantFunc: "net/http.Get",
|
||||
},
|
||||
{
|
||||
name: "package, function and receiver",
|
||||
frame: &govulncheck.Frame{Package: "net/http", Receiver: "*ServeMux", Function: "Handle"},
|
||||
wantFunc: "net/http.ServeMux.Handle",
|
||||
},
|
||||
{
|
||||
name: "short",
|
||||
frame: &govulncheck.Frame{Package: "net/http", Function: "Get"},
|
||||
short: true,
|
||||
wantFunc: "http.Get",
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
buf := &strings.Builder{}
|
||||
addSymbolName(buf, test.frame, test.short)
|
||||
got := buf.String()
|
||||
if got != test.wantFunc {
|
||||
t.Errorf("want %v func name; got %v", test.wantFunc, got)
|
||||
}
|
||||
if got := posToString(test.frame.Position); got != test.wantPos {
|
||||
t.Errorf("want %v call position; got %v", test.wantPos, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/vuln/internal/client"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
// RunGovulncheck performs main govulncheck functionality and exits the
|
||||
// program upon success with an appropriate exit status. Otherwise,
|
||||
// returns an error.
|
||||
func RunGovulncheck(ctx context.Context, env []string, r io.Reader, stdout io.Writer, stderr io.Writer, args []string) error {
|
||||
cfg := &config{env: env}
|
||||
if err := parseFlags(cfg, stderr, args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := client.NewClient(cfg.db, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating client: %w", err)
|
||||
}
|
||||
|
||||
prepareConfig(ctx, cfg, client)
|
||||
var handler govulncheck.Handler
|
||||
switch {
|
||||
case cfg.json:
|
||||
handler = govulncheck.NewJSONHandler(stdout)
|
||||
default:
|
||||
th := NewTextHandler(stdout)
|
||||
th.Show(cfg.show)
|
||||
handler = th
|
||||
}
|
||||
|
||||
// Write the introductory message to the user.
|
||||
if err := handler.Config(&cfg.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch cfg.mode {
|
||||
case modeSource:
|
||||
dir := filepath.FromSlash(cfg.dir)
|
||||
err = runSource(ctx, handler, cfg, client, dir)
|
||||
case modeBinary:
|
||||
err = runBinary(ctx, handler, cfg, client)
|
||||
case modeExtract:
|
||||
return runExtract(cfg, stdout)
|
||||
case modeQuery:
|
||||
err = runQuery(ctx, handler, cfg, client)
|
||||
case modeConvert:
|
||||
err = govulncheck.HandleJSON(r, handler)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Flush(handler)
|
||||
}
|
||||
|
||||
func prepareConfig(ctx context.Context, cfg *config, client *client.Client) {
|
||||
cfg.ProtocolVersion = govulncheck.ProtocolVersion
|
||||
cfg.DB = cfg.db
|
||||
if cfg.mode == modeSource && cfg.GoVersion == "" {
|
||||
const goverPrefix = "GOVERSION="
|
||||
for _, env := range cfg.env {
|
||||
if val := strings.TrimPrefix(env, goverPrefix); val != env {
|
||||
cfg.GoVersion = val
|
||||
}
|
||||
}
|
||||
if cfg.GoVersion == "" {
|
||||
if out, err := exec.Command("go", "env", "GOVERSION").Output(); err == nil {
|
||||
cfg.GoVersion = strings.TrimSpace(string(out))
|
||||
}
|
||||
}
|
||||
}
|
||||
if bi, ok := debug.ReadBuildInfo(); ok {
|
||||
scannerVersion(cfg, bi)
|
||||
}
|
||||
if mod, err := client.LastModifiedTime(ctx); err == nil {
|
||||
cfg.DBLastModified = &mod
|
||||
}
|
||||
}
|
||||
|
||||
// scannerVersion reconstructs the current version of
|
||||
// this binary used from the build info.
|
||||
func scannerVersion(cfg *config, bi *debug.BuildInfo) {
|
||||
if bi.Path != "" {
|
||||
cfg.ScannerName = path.Base(bi.Path)
|
||||
}
|
||||
if bi.Main.Version != "" && bi.Main.Version != "(devel)" {
|
||||
cfg.ScannerVersion = bi.Main.Version
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(https://go.dev/issue/29228): we need to manually construct the
|
||||
// version string when it is "(devel)" until #29228 is resolved.
|
||||
var revision, at string
|
||||
for _, s := range bi.Settings {
|
||||
if s.Key == "vcs.revision" {
|
||||
revision = s.Value
|
||||
}
|
||||
if s.Key == "vcs.time" {
|
||||
at = s.Value
|
||||
}
|
||||
}
|
||||
buf := strings.Builder{}
|
||||
buf.WriteString("v0.0.0")
|
||||
if revision != "" {
|
||||
buf.WriteString("-")
|
||||
buf.WriteString(revision[:12])
|
||||
}
|
||||
if at != "" {
|
||||
// commit time is of the form 2023-01-25T19:57:54Z
|
||||
p, err := time.Parse(time.RFC3339, at)
|
||||
if err == nil {
|
||||
buf.WriteString("-")
|
||||
buf.WriteString(p.Format("20060102150405"))
|
||||
}
|
||||
}
|
||||
cfg.ScannerVersion = buf.String()
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGovulncheckVersion(t *testing.T) {
|
||||
bi := &debug.BuildInfo{
|
||||
Settings: []debug.BuildSetting{
|
||||
{Key: "vcs.revision", Value: "1234567890001234"},
|
||||
{Key: "vcs.time", Value: "2023-01-25T19:57:54Z"},
|
||||
},
|
||||
}
|
||||
|
||||
want := "v0.0.0-123456789000-20230125195754"
|
||||
got := &config{}
|
||||
scannerVersion(got, bi)
|
||||
if got.ScannerVersion != want {
|
||||
t.Errorf("got %s; want %s", got.ScannerVersion, want)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/vuln/internal/client"
|
||||
"golang.org/x/vuln/internal/derrors"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/vulncheck"
|
||||
)
|
||||
|
||||
// runSource reports vulnerabilities that affect the analyzed packages.
|
||||
//
|
||||
// Vulnerabilities can be called (affecting the package, because a vulnerable
|
||||
// symbol is actually exercised) or just imported by the package
|
||||
// (likely having a non-affecting outcome).
|
||||
func runSource(ctx context.Context, handler govulncheck.Handler, cfg *config, client *client.Client, dir string) (err error) {
|
||||
defer derrors.Wrap(&err, "govulncheck")
|
||||
|
||||
if cfg.ScanLevel.WantPackages() && len(cfg.patterns) == 0 {
|
||||
return nil // don't throw an error here
|
||||
}
|
||||
if !gomodExists(dir) {
|
||||
return errNoGoMod
|
||||
}
|
||||
var pkgs []*packages.Package
|
||||
var mods []*packages.Module
|
||||
graph := vulncheck.NewPackageGraph(cfg.GoVersion)
|
||||
pkgConfig := &packages.Config{
|
||||
Dir: dir,
|
||||
Tests: cfg.test,
|
||||
Env: cfg.env,
|
||||
}
|
||||
pkgs, mods, err = graph.LoadPackagesAndMods(pkgConfig, cfg.tags, cfg.patterns)
|
||||
if err != nil {
|
||||
if isGoVersionMismatchError(err) {
|
||||
return fmt.Errorf("%v\n\n%v", errGoVersionMismatch, err)
|
||||
}
|
||||
return fmt.Errorf("loading packages: %w", err)
|
||||
}
|
||||
|
||||
if err := handler.Progress(sourceProgressMessage(pkgs, len(mods)-1, cfg.ScanLevel)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.ScanLevel.WantPackages() && len(pkgs) == 0 {
|
||||
return nil // early exit
|
||||
}
|
||||
return vulncheck.Source(ctx, handler, pkgs, mods, &cfg.Config, client, graph)
|
||||
}
|
||||
|
||||
// sourceProgressMessage returns a string of the form
|
||||
//
|
||||
// "Scanning your code and P packages across M dependent modules for known vulnerabilities..."
|
||||
//
|
||||
// P is the number of strictly dependent packages of
|
||||
// topPkgs and Y is the number of their modules. If P
|
||||
// is 0, then the following message is returned
|
||||
//
|
||||
// "No packages matching the provided pattern."
|
||||
func sourceProgressMessage(topPkgs []*packages.Package, mods int, mode govulncheck.ScanLevel) *govulncheck.Progress {
|
||||
var pkgsPhrase, modsPhrase string
|
||||
|
||||
if mode.WantPackages() {
|
||||
if len(topPkgs) == 0 {
|
||||
// The package pattern is valid, but no packages are matching.
|
||||
// Example is pkg/strace/... (see #59623).
|
||||
return &govulncheck.Progress{Message: "No packages matching the provided pattern."}
|
||||
}
|
||||
pkgs := depPkgs(topPkgs)
|
||||
pkgsPhrase = fmt.Sprintf(" and %d package%s", pkgs, choose(pkgs != 1, "s", ""))
|
||||
}
|
||||
modsPhrase = fmt.Sprintf(" %d dependent module%s", mods, choose(mods != 1, "s", ""))
|
||||
|
||||
msg := fmt.Sprintf("Scanning your code%s across%s for known vulnerabilities...", pkgsPhrase, modsPhrase)
|
||||
return &govulncheck.Progress{Message: msg}
|
||||
}
|
||||
|
||||
// depPkgs returns the number of packages that topPkgs depend on
|
||||
func depPkgs(topPkgs []*packages.Package) int {
|
||||
tops := make(map[string]bool)
|
||||
depPkgs := make(map[string]bool)
|
||||
|
||||
for _, t := range topPkgs {
|
||||
tops[t.PkgPath] = true
|
||||
}
|
||||
|
||||
var visit func(*packages.Package, bool)
|
||||
visit = func(p *packages.Package, top bool) {
|
||||
path := p.PkgPath
|
||||
if depPkgs[path] {
|
||||
return
|
||||
}
|
||||
if tops[path] && !top {
|
||||
// A top package that is a dependency
|
||||
// will not be in depPkgs, so we skip
|
||||
// reiterating on it here.
|
||||
return
|
||||
}
|
||||
|
||||
// We don't count a top-level package as
|
||||
// a dependency even when they are used
|
||||
// as a dependent package.
|
||||
if !tops[path] {
|
||||
depPkgs[path] = true
|
||||
}
|
||||
|
||||
for _, d := range p.Imports {
|
||||
visit(d, false)
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range topPkgs {
|
||||
visit(t, true)
|
||||
}
|
||||
|
||||
return len(depPkgs)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
func TestSummarizeCallStack(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in, want string
|
||||
}{
|
||||
{"ma.a.F", "a.F"},
|
||||
{"m1.p1.F", "p1.F"},
|
||||
{"mv.v.V", "v.V"},
|
||||
{
|
||||
"m1.p1.F mv.v.V",
|
||||
"p1.F calls v.V",
|
||||
},
|
||||
{
|
||||
"m1.p1.F m1.p2.G mv.v.V1 mv.v.v2",
|
||||
"p2.G calls v.V1, which calls v.v2",
|
||||
},
|
||||
{
|
||||
"m1.p1.F m1.p2.G mv.v.V$1 mv.v.V1",
|
||||
"p2.G calls v.V, which calls v.V1",
|
||||
},
|
||||
{
|
||||
"m1.p1.F m1.p2.G$1 mv.v.V1",
|
||||
"p2.G calls v.V1",
|
||||
},
|
||||
{
|
||||
"m1.p1.F m1.p2.G$1 mv.v.V$1 mv.v.V1",
|
||||
"p2.G calls v.V, which calls v.V1",
|
||||
},
|
||||
{
|
||||
"m1.p1.F w.x.Y m1.p2.G ma.a.H mb.b.I mc.c.J mv.v.V",
|
||||
"p2.G calls a.H, which eventually calls v.V",
|
||||
},
|
||||
{
|
||||
"m1.p1.F w.x.Y m1.p2.G ma.a.H mb.b.I mc.c.J mv.v.V$1 mv.v.V1",
|
||||
"p2.G calls a.H, which eventually calls v.V1",
|
||||
},
|
||||
{
|
||||
"m1.p1.F m1.p1.F$1 ma.a.H mb.b.I mv.v.V1",
|
||||
"p1.F calls a.H, which eventually calls v.V1",
|
||||
},
|
||||
} {
|
||||
in := stringToFinding(test.in)
|
||||
got := compactTrace(in)
|
||||
if got != test.want {
|
||||
t.Errorf("%s:\ngot %s\nwant %s", test.in, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stringToFinding(s string) *govulncheck.Finding {
|
||||
f := &govulncheck.Finding{}
|
||||
entries := strings.Fields(s)
|
||||
for i := len(entries) - 1; i >= 0; i-- {
|
||||
e := entries[i]
|
||||
firstDot := strings.Index(e, ".")
|
||||
lastDot := strings.LastIndex(e, ".")
|
||||
f.Trace = append(f.Trace, &govulncheck.Frame{
|
||||
Module: e[:firstDot],
|
||||
Package: e[:firstDot] + "/" + e[firstDot+1:lastDot],
|
||||
Function: e[lastDot+1:],
|
||||
})
|
||||
}
|
||||
return f
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// Support functions for standard library packages.
|
||||
// These are copied from the internal/stdlib package in the pkgsite repo.
|
||||
|
||||
// semverToGoTag returns the Go standard library repository tag corresponding
|
||||
// to semver, a version string without the initial "v".
|
||||
// Go tags differ from standard semantic versions in a few ways,
|
||||
// such as beginning with "go" instead of "v".
|
||||
func semverToGoTag(v string) string {
|
||||
if strings.HasPrefix(v, "v0.0.0") {
|
||||
return "master"
|
||||
}
|
||||
// Special case: v1.0.0 => go1.
|
||||
if v == "v1.0.0" {
|
||||
return "go1"
|
||||
}
|
||||
if !semver.IsValid(v) {
|
||||
return fmt.Sprintf("<!%s:invalid semver>", v)
|
||||
}
|
||||
goVersion := semver.Canonical(v)
|
||||
prerelease := semver.Prerelease(goVersion)
|
||||
versionWithoutPrerelease := strings.TrimSuffix(goVersion, prerelease)
|
||||
patch := strings.TrimPrefix(versionWithoutPrerelease, semver.MajorMinor(goVersion)+".")
|
||||
if patch == "0" {
|
||||
versionWithoutPrerelease = strings.TrimSuffix(versionWithoutPrerelease, ".0")
|
||||
}
|
||||
goVersion = fmt.Sprintf("go%s", strings.TrimPrefix(versionWithoutPrerelease, "v"))
|
||||
if prerelease != "" {
|
||||
// Go prereleases look like "beta1" instead of "beta.1".
|
||||
// "beta1" is bad for sorting (since beta10 comes before beta9), so
|
||||
// require the dot form.
|
||||
i := finalDigitsIndex(prerelease)
|
||||
if i >= 1 {
|
||||
if prerelease[i-1] != '.' {
|
||||
return fmt.Sprintf("<!%s:final digits in a prerelease must follow a period>", v)
|
||||
}
|
||||
// Remove the dot.
|
||||
prerelease = prerelease[:i-1] + prerelease[i:]
|
||||
}
|
||||
goVersion += strings.TrimPrefix(prerelease, "-")
|
||||
}
|
||||
return goVersion
|
||||
}
|
||||
|
||||
// finalDigitsIndex returns the index of the first digit in the sequence of digits ending s.
|
||||
// If s doesn't end in digits, it returns -1.
|
||||
func finalDigitsIndex(s string) int {
|
||||
// Assume ASCII (since the semver package does anyway).
|
||||
var i int
|
||||
for i = len(s) - 1; i >= 0; i-- {
|
||||
if s[i] < '0' || s[i] > '9' {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i == len(s)-1 {
|
||||
return -1
|
||||
}
|
||||
return i + 1
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
// 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"io"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
)
|
||||
|
||||
type findingSummary struct {
|
||||
*govulncheck.Finding
|
||||
Compact string
|
||||
OSV *osv.Entry
|
||||
}
|
||||
|
||||
type summaryCounters struct {
|
||||
VulnerabilitiesCalled int
|
||||
ModulesCalled int
|
||||
VulnerabilitiesImported int
|
||||
VulnerabilitiesRequired int
|
||||
StdlibCalled bool
|
||||
}
|
||||
|
||||
func fixupFindings(osvs []*osv.Entry, findings []*findingSummary) {
|
||||
for _, f := range findings {
|
||||
f.OSV = getOSV(osvs, f.Finding.OSV)
|
||||
}
|
||||
}
|
||||
|
||||
func groupByVuln(findings []*findingSummary) [][]*findingSummary {
|
||||
return groupBy(findings, func(left, right *findingSummary) int {
|
||||
return -strings.Compare(left.OSV.ID, right.OSV.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func groupByModule(findings []*findingSummary) [][]*findingSummary {
|
||||
return groupBy(findings, func(left, right *findingSummary) int {
|
||||
return strings.Compare(left.Trace[0].Module, right.Trace[0].Module)
|
||||
})
|
||||
}
|
||||
|
||||
func groupBy(findings []*findingSummary, compare func(left, right *findingSummary) int) [][]*findingSummary {
|
||||
switch len(findings) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return [][]*findingSummary{findings}
|
||||
}
|
||||
sort.SliceStable(findings, func(i, j int) bool {
|
||||
return compare(findings[i], findings[j]) < 0
|
||||
})
|
||||
result := [][]*findingSummary{}
|
||||
first := 0
|
||||
for i, next := range findings {
|
||||
if i == first {
|
||||
continue
|
||||
}
|
||||
if compare(findings[first], next) != 0 {
|
||||
result = append(result, findings[first:i])
|
||||
first = i
|
||||
}
|
||||
}
|
||||
result = append(result, findings[first:])
|
||||
return result
|
||||
}
|
||||
|
||||
func isRequired(findings []*findingSummary) bool {
|
||||
for _, f := range findings {
|
||||
if f.Trace[0].Module != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isImported(findings []*findingSummary) bool {
|
||||
for _, f := range findings {
|
||||
if f.Trace[0].Package != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isCalled(findings []*findingSummary) bool {
|
||||
for _, f := range findings {
|
||||
if f.Trace[0].Function != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getOSV(osvs []*osv.Entry, id string) *osv.Entry {
|
||||
for _, entry := range osvs {
|
||||
if entry.ID == id {
|
||||
return entry
|
||||
}
|
||||
}
|
||||
return &osv.Entry{
|
||||
ID: id,
|
||||
DatabaseSpecific: &osv.DatabaseSpecific{},
|
||||
}
|
||||
}
|
||||
|
||||
func newFindingSummary(f *govulncheck.Finding) *findingSummary {
|
||||
return &findingSummary{
|
||||
Finding: f,
|
||||
Compact: compactTrace(f),
|
||||
}
|
||||
}
|
||||
|
||||
// platforms returns a string describing the GOOS, GOARCH,
|
||||
// or GOOS/GOARCH pairs that the vuln affects for a particular
|
||||
// module mod. If it affects all of them, it returns the empty
|
||||
// string.
|
||||
//
|
||||
// When mod is an empty string, returns platform information for
|
||||
// all modules of e.
|
||||
func platforms(mod string, e *osv.Entry) []string {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
platforms := map[string]bool{}
|
||||
for _, a := range e.Affected {
|
||||
if mod != "" && a.Module.Path != mod {
|
||||
continue
|
||||
}
|
||||
for _, p := range a.EcosystemSpecific.Packages {
|
||||
for _, os := range p.GOOS {
|
||||
// In case there are no specific architectures,
|
||||
// just list the os entries.
|
||||
if len(p.GOARCH) == 0 {
|
||||
platforms[os] = true
|
||||
continue
|
||||
}
|
||||
// Otherwise, list all the os+arch combinations.
|
||||
for _, arch := range p.GOARCH {
|
||||
platforms[os+"/"+arch] = true
|
||||
}
|
||||
}
|
||||
// Cover the case where there are no specific
|
||||
// operating systems listed.
|
||||
if len(p.GOOS) == 0 {
|
||||
for _, arch := range p.GOARCH {
|
||||
platforms[arch] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var keys []string
|
||||
for k := range platforms {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
func posToString(p *govulncheck.Position) string {
|
||||
if p == nil || p.Line <= 0 {
|
||||
return ""
|
||||
}
|
||||
return token.Position{
|
||||
Filename: AbsRelShorter(p.Filename),
|
||||
Offset: p.Offset,
|
||||
Line: p.Line,
|
||||
Column: p.Column,
|
||||
}.String()
|
||||
}
|
||||
|
||||
func symbol(frame *govulncheck.Frame, short bool) string {
|
||||
buf := &strings.Builder{}
|
||||
addSymbolName(buf, frame, short)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// compactTrace returns a short description of the call stack.
|
||||
// It prefers to show you the edge from the top module to other code, along with
|
||||
// the vulnerable symbol.
|
||||
// Where the vulnerable symbol directly called by the users code, it will only
|
||||
// show those two points.
|
||||
// If the vulnerable symbol is in the users code, it will show the entry point
|
||||
// and the vulnerable symbol.
|
||||
func compactTrace(finding *govulncheck.Finding) string {
|
||||
if len(finding.Trace) < 1 {
|
||||
return ""
|
||||
}
|
||||
iTop := len(finding.Trace) - 1
|
||||
topModule := finding.Trace[iTop].Module
|
||||
// search for the exit point of the top module
|
||||
for i, frame := range finding.Trace {
|
||||
if frame.Module == topModule {
|
||||
iTop = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if iTop == 0 {
|
||||
// all in one module, reset to the end
|
||||
iTop = len(finding.Trace) - 1
|
||||
}
|
||||
|
||||
buf := &strings.Builder{}
|
||||
topPos := posToString(finding.Trace[iTop].Position)
|
||||
if topPos != "" {
|
||||
buf.WriteString(topPos)
|
||||
buf.WriteString(": ")
|
||||
}
|
||||
|
||||
if iTop > 0 {
|
||||
addSymbolName(buf, finding.Trace[iTop], true)
|
||||
buf.WriteString(" calls ")
|
||||
}
|
||||
if iTop > 1 {
|
||||
addSymbolName(buf, finding.Trace[iTop-1], true)
|
||||
buf.WriteString(", which")
|
||||
if iTop > 2 {
|
||||
buf.WriteString(" eventually")
|
||||
}
|
||||
buf.WriteString(" calls ")
|
||||
}
|
||||
addSymbolName(buf, finding.Trace[0], true)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// notIdentifier reports whether ch is an invalid identifier character.
|
||||
func notIdentifier(ch rune) bool {
|
||||
return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' ||
|
||||
'0' <= ch && ch <= '9' ||
|
||||
ch == '_' ||
|
||||
ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch)))
|
||||
}
|
||||
|
||||
// importPathToAssumedName is taken from goimports, it works out the natural imported name
|
||||
// for a package.
|
||||
// This is used to get a shorter identifier in the compact stack trace
|
||||
func importPathToAssumedName(importPath string) string {
|
||||
base := path.Base(importPath)
|
||||
if strings.HasPrefix(base, "v") {
|
||||
if _, err := strconv.Atoi(base[1:]); err == nil {
|
||||
dir := path.Dir(importPath)
|
||||
if dir != "." {
|
||||
base = path.Base(dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
base = strings.TrimPrefix(base, "go-")
|
||||
if i := strings.IndexFunc(base, notIdentifier); i >= 0 {
|
||||
base = base[:i]
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func addSymbolName(w io.Writer, frame *govulncheck.Frame, short bool) {
|
||||
if frame.Function == "" {
|
||||
return
|
||||
}
|
||||
if frame.Package != "" {
|
||||
pkg := frame.Package
|
||||
if short {
|
||||
pkg = importPathToAssumedName(frame.Package)
|
||||
}
|
||||
io.WriteString(w, pkg)
|
||||
io.WriteString(w, ".")
|
||||
}
|
||||
if frame.Receiver != "" {
|
||||
if frame.Receiver[0] == '*' {
|
||||
io.WriteString(w, frame.Receiver[1:])
|
||||
} else {
|
||||
io.WriteString(w, frame.Receiver)
|
||||
}
|
||||
io.WriteString(w, ".")
|
||||
}
|
||||
funcname := strings.Split(frame.Function, "$")[0]
|
||||
io.WriteString(w, funcname)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "symbol"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0001",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Third-party vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"amd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0001"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "golang.org/vmod",
|
||||
"function": "Vuln"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0002",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Stdlib vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "stdlib",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0002"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0002",
|
||||
"trace": [
|
||||
{
|
||||
"module": "stdlib",
|
||||
"version": "v0.0.1",
|
||||
"package": "net/http",
|
||||
"function": "Vuln2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
=== Symbol Results ===
|
||||
|
||||
Vulnerability #1: GO-0000-0002
|
||||
Stdlib vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0002
|
||||
Standard library
|
||||
Found in: net/http@go0.0.1
|
||||
Fixed in: N/A
|
||||
Example traces found:
|
||||
#1: http.Vuln2
|
||||
|
||||
Vulnerability #2: GO-0000-0001
|
||||
Third-party vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0001
|
||||
Module: golang.org/vmod
|
||||
Found in: golang.org/vmod@v0.0.1
|
||||
Fixed in: golang.org/vmod@v0.1.3
|
||||
Platforms: amd
|
||||
Example traces found:
|
||||
#1: vmod.Vuln
|
||||
|
||||
Your code is affected by 2 vulnerabilities from 1 module and the Go standard library.
|
||||
This scan found no other vulnerabilities in packages you import or modules you
|
||||
require.
|
||||
Use '-show verbose' for more details.
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "symbol"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0001",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Third-party vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"amd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0001"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "golang.org/vmod",
|
||||
"function": "VulnFoo"
|
||||
},
|
||||
{
|
||||
"module": "golang.org/main",
|
||||
"version": "v0.0.1",
|
||||
"package": "golang.org/main",
|
||||
"function": "main"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
=== Symbol Results ===
|
||||
|
||||
Vulnerability #1: GO-0000-0001
|
||||
Third-party vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0001
|
||||
Module: golang.org/vmod
|
||||
Found in: golang.org/vmod@v0.0.1
|
||||
Fixed in: golang.org/vmod@v0.1.3
|
||||
Platforms: amd
|
||||
Example traces found:
|
||||
#1: main.main calls vmod.VulnFoo
|
||||
|
||||
Your code is affected by 1 vulnerability from 1 module.
|
||||
This scan found no other vulnerabilities in packages you import or modules you
|
||||
require.
|
||||
Use '-show verbose' for more details.
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "module"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0001",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Third-party vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"amd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0001"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
=== Module Results ===
|
||||
|
||||
Vulnerability #1: GO-0000-0001
|
||||
Third-party vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0001
|
||||
Module: golang.org/vmod
|
||||
Found in: golang.org/vmod@v0.0.1
|
||||
Fixed in: golang.org/vmod@v0.1.3
|
||||
Platforms: amd
|
||||
|
||||
Your code may be affected by 1 vulnerability.
|
||||
Use '-scan symbol' for more fine grained vulnerability detection.
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "module"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0001",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Third-party vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"amd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0001"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0002",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Third-party vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0002"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0002",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
=== Module Results ===
|
||||
|
||||
Vulnerability #1: GO-0000-0002
|
||||
Third-party vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0002
|
||||
Module: golang.org/vmod
|
||||
Found in: golang.org/vmod@v0.0.1
|
||||
Fixed in: golang.org/vmod@v0.1.3
|
||||
|
||||
Vulnerability #2: GO-0000-0001
|
||||
Third-party vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0001
|
||||
Module: golang.org/vmod
|
||||
Found in: golang.org/vmod@v0.0.1
|
||||
Fixed in: golang.org/vmod@v0.1.3
|
||||
Platforms: amd
|
||||
|
||||
Your code may be affected by 2 vulnerabilities.
|
||||
Use '-scan symbol' for more fine grained vulnerability detection.
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "symbol"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0001",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Third-party vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"amd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0001"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "vmod",
|
||||
"function": "Vuln"
|
||||
},
|
||||
{
|
||||
"module": "golang.org/main",
|
||||
"version": "v0.0.1",
|
||||
"package": "main",
|
||||
"function": "main"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "vmod",
|
||||
"function": "VulnFoo"
|
||||
},
|
||||
{
|
||||
"module": "golang.org/main",
|
||||
"version": "v0.0.1",
|
||||
"package": "main",
|
||||
"function": "main"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.0.4",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod1",
|
||||
"version": "v0.0.3",
|
||||
"package": "vmod1",
|
||||
"function": "Vuln"
|
||||
},
|
||||
{
|
||||
"module": "golang.org/other",
|
||||
"version": "v2.0.3",
|
||||
"package": "other",
|
||||
"function": "Foo"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.0.4",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod1",
|
||||
"version": "v0.0.3",
|
||||
"package": "vmod1",
|
||||
"function": "VulnFoo"
|
||||
},
|
||||
{
|
||||
"module": "golang.org/other",
|
||||
"version": "v2.0.3",
|
||||
"package": "other",
|
||||
"function": "Bar"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
=== Symbol Results ===
|
||||
|
||||
Vulnerability #1: GO-0000-0001
|
||||
Third-party vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0001
|
||||
Module: golang.org/vmod
|
||||
Found in: golang.org/vmod@v0.0.1
|
||||
Fixed in: golang.org/vmod@v0.1.3
|
||||
Platforms: amd
|
||||
Example traces found:
|
||||
#1: main.main calls vmod.Vuln
|
||||
#2: main.main calls vmod.VulnFoo
|
||||
|
||||
Module: golang.org/vmod1
|
||||
Found in: golang.org/vmod1@v0.0.3
|
||||
Fixed in: golang.org/vmod1@v0.0.4
|
||||
Example traces found:
|
||||
#1: other.Foo calls vmod1.Vuln
|
||||
#2: other.Bar calls vmod1.VulnFoo
|
||||
|
||||
Your code is affected by 1 vulnerability from the Go standard library.
|
||||
This scan found no other vulnerabilities in packages you import or modules you
|
||||
require.
|
||||
Use '-show verbose' for more details.
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "package"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0001",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Third-party vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"amd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0001"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "golang.org/vmod"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
=== Package Results ===
|
||||
|
||||
Vulnerability #1: GO-0000-0001
|
||||
Third-party vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0001
|
||||
Module: golang.org/vmod
|
||||
Found in: golang.org/vmod@v0.0.1
|
||||
Fixed in: golang.org/vmod@v0.1.3
|
||||
Platforms: amd
|
||||
|
||||
Your code may be affected by 1 vulnerability.
|
||||
This scan also found 0 vulnerabilities in modules you require.
|
||||
Use '-scan symbol' for more fine grained vulnerability detection and '-show
|
||||
verbose' for more details.
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "symbol"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "All",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "",
|
||||
"affected": null,
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/All"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "All",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "golang.org/vmod"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
=== Symbol Results ===
|
||||
|
||||
No vulnerabilities found.
|
||||
|
||||
Your code is affected by 0 vulnerabilities.
|
||||
This scan also found 1 vulnerability in packages you import and 0
|
||||
vulnerabilities in modules you require, but your code doesn't appear to call
|
||||
these vulnerabilities.
|
||||
Use '-show verbose' for more details.
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "symbol"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "one-arch-only",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "1.2.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"amd64"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/one-arch-only"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "one-arch-only",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "golang.org/vmod"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
=== Symbol Results ===
|
||||
|
||||
No vulnerabilities found.
|
||||
|
||||
Your code is affected by 0 vulnerabilities.
|
||||
This scan also found 1 vulnerability in packages you import and 0
|
||||
vulnerabilities in modules you require, but your code doesn't appear to call
|
||||
these vulnerabilities.
|
||||
Use '-show verbose' for more details.
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "symbol"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "one-import",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "1.2.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"windows",
|
||||
"linux"
|
||||
],
|
||||
"goarch": [
|
||||
"amd64",
|
||||
"wasm"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/one-import"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "one-import",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "golang.org/vmod"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
=== Symbol Results ===
|
||||
|
||||
No vulnerabilities found.
|
||||
|
||||
Your code is affected by 0 vulnerabilities.
|
||||
This scan also found 1 vulnerability in packages you import and 0
|
||||
vulnerabilities in modules you require, but your code doesn't appear to call
|
||||
these vulnerabilities.
|
||||
Use '-show verbose' for more details.
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "symbol"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "two-imports",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "1.2.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"windows"
|
||||
],
|
||||
"goarch": [
|
||||
"amd64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"goos": [
|
||||
"linux"
|
||||
],
|
||||
"goarch": [
|
||||
"amd64"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/two-imports"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "two-imports",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "golang.org/vmod"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
=== Symbol Results ===
|
||||
|
||||
No vulnerabilities found.
|
||||
|
||||
Your code is affected by 0 vulnerabilities.
|
||||
This scan also found 1 vulnerability in packages you import and 0
|
||||
vulnerabilities in modules you require, but your code doesn't appear to call
|
||||
these vulnerabilities.
|
||||
Use '-show verbose' for more details.
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "symbol"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "two-os-only",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "1.2.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"windows, linux"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/two-os-only"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "two-os-only",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "golang.org/vmod"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
=== Symbol Results ===
|
||||
|
||||
No vulnerabilities found.
|
||||
|
||||
Your code is affected by 0 vulnerabilities.
|
||||
This scan also found 1 vulnerability in packages you import and 0
|
||||
vulnerabilities in modules you require, but your code doesn't appear to call
|
||||
these vulnerabilities.
|
||||
Use '-show verbose' for more details.
|
||||
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "symbol"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0001",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Third-party vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"amd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0001"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "vmod",
|
||||
"function": "Vuln"
|
||||
},
|
||||
{
|
||||
"module": "golang.org/app",
|
||||
"version": "v0.0.1",
|
||||
"package": "main",
|
||||
"function": "main"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0002",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Stdlib vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "stdlib",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0002"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0002",
|
||||
"trace": [
|
||||
{
|
||||
"module": "stdlib",
|
||||
"version": "v0.0.1",
|
||||
"package": "net/http"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
=== Symbol Results ===
|
||||
|
||||
Vulnerability #1: GO-0000-0001
|
||||
Third-party vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0001
|
||||
Module: golang.org/vmod
|
||||
Found in: golang.org/vmod@v0.0.1
|
||||
Fixed in: golang.org/vmod@v0.1.3
|
||||
Platforms: amd
|
||||
Example traces found:
|
||||
#1: main.main calls vmod.Vuln
|
||||
|
||||
Your code is affected by 1 vulnerability from the Go standard library.
|
||||
This scan also found 0 vulnerabilities in packages you import and 1
|
||||
vulnerability in modules you require, but your code doesn't appear to call these
|
||||
vulnerabilities.
|
||||
Use '-show verbose' for more details.
|
||||
@@ -0,0 +1,19 @@
|
||||
=== Symbol Results ===
|
||||
|
||||
Vulnerability #1: GO-0000-0001
|
||||
Third-party vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0001
|
||||
Module: golang.org/vmod
|
||||
Found in: golang.org/vmod@v0.0.1
|
||||
Fixed in: golang.org/vmod@v0.1.3
|
||||
Platforms: amd
|
||||
Example traces found:
|
||||
#1: for function vmod.Vuln
|
||||
main.main
|
||||
vmod.Vuln
|
||||
|
||||
Your code is affected by 1 vulnerability from the Go standard library.
|
||||
This scan also found 0 vulnerabilities in packages you import and 1
|
||||
vulnerability in modules you require, but your code doesn't appear to call these
|
||||
vulnerabilities.
|
||||
Use '-show verbose' for more details.
|
||||
@@ -0,0 +1,481 @@
|
||||
// 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/vuln/internal"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
"golang.org/x/vuln/internal/vulncheck"
|
||||
)
|
||||
|
||||
type style int
|
||||
|
||||
const (
|
||||
defaultStyle = style(iota)
|
||||
osvCalledStyle
|
||||
osvImportedStyle
|
||||
detailsStyle
|
||||
sectionStyle
|
||||
keyStyle
|
||||
valueStyle
|
||||
)
|
||||
|
||||
// NewtextHandler returns a handler that writes govulncheck output as text.
|
||||
func NewTextHandler(w io.Writer) *TextHandler {
|
||||
return &TextHandler{w: w}
|
||||
}
|
||||
|
||||
type TextHandler struct {
|
||||
w io.Writer
|
||||
osvs []*osv.Entry
|
||||
findings []*findingSummary
|
||||
scanLevel govulncheck.ScanLevel
|
||||
|
||||
err error
|
||||
|
||||
showColor bool
|
||||
showTraces bool
|
||||
showVersion bool
|
||||
showAllVulns bool
|
||||
}
|
||||
|
||||
const (
|
||||
detailsMessage = `For details, see https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck.`
|
||||
|
||||
binaryProgressMessage = `Scanning your binary for known vulnerabilities...`
|
||||
|
||||
noVulnsMessage = `No vulnerabilities found.`
|
||||
|
||||
noOtherVulnsMessage = `No other vulnerabilities found.`
|
||||
|
||||
verboseMessage = `'-show verbose' for more details`
|
||||
|
||||
symbolMessage = `'-scan symbol' for more fine grained vulnerability detection`
|
||||
)
|
||||
|
||||
func (h *TextHandler) Show(show []string) {
|
||||
for _, show := range show {
|
||||
switch show {
|
||||
case "traces":
|
||||
h.showTraces = true
|
||||
case "color":
|
||||
h.showColor = true
|
||||
case "version":
|
||||
h.showVersion = true
|
||||
case "verbose":
|
||||
h.showAllVulns = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Flush(h govulncheck.Handler) error {
|
||||
if th, ok := h.(interface{ Flush() error }); ok {
|
||||
return th.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TextHandler) Flush() error {
|
||||
if len(h.findings) == 0 {
|
||||
h.print(noVulnsMessage + "\n")
|
||||
} else {
|
||||
fixupFindings(h.osvs, h.findings)
|
||||
counters := h.allVulns(h.findings)
|
||||
h.summary(counters)
|
||||
}
|
||||
if h.err != nil {
|
||||
return h.err
|
||||
}
|
||||
// We found vulnerabilities when the findings' level matches the scan level.
|
||||
if (isCalled(h.findings) && h.scanLevel == govulncheck.ScanLevelSymbol) ||
|
||||
(isImported(h.findings) && h.scanLevel == govulncheck.ScanLevelPackage) ||
|
||||
(isRequired(h.findings) && h.scanLevel == govulncheck.ScanLevelModule) {
|
||||
return errVulnerabilitiesFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config writes version information only if --version was set.
|
||||
func (h *TextHandler) Config(config *govulncheck.Config) error {
|
||||
if config.ScanLevel != "" {
|
||||
h.scanLevel = config.ScanLevel
|
||||
}
|
||||
if !h.showVersion {
|
||||
return nil
|
||||
}
|
||||
if config.GoVersion != "" {
|
||||
h.style(keyStyle, "Go: ")
|
||||
h.print(config.GoVersion, "\n")
|
||||
}
|
||||
if config.ScannerName != "" {
|
||||
h.style(keyStyle, "Scanner: ")
|
||||
h.print(config.ScannerName)
|
||||
if config.ScannerVersion != "" {
|
||||
h.print(`@`, config.ScannerVersion)
|
||||
}
|
||||
h.print("\n")
|
||||
}
|
||||
if config.DB != "" {
|
||||
h.style(keyStyle, "DB: ")
|
||||
h.print(config.DB, "\n")
|
||||
if config.DBLastModified != nil {
|
||||
h.style(keyStyle, "DB updated: ")
|
||||
h.print(*config.DBLastModified, "\n")
|
||||
}
|
||||
}
|
||||
h.print("\n")
|
||||
return h.err
|
||||
}
|
||||
|
||||
// Progress writes progress updates during govulncheck execution.
|
||||
func (h *TextHandler) Progress(progress *govulncheck.Progress) error {
|
||||
h.print(progress.Message, "\n\n")
|
||||
return h.err
|
||||
}
|
||||
|
||||
// OSV gathers osv entries to be written.
|
||||
func (h *TextHandler) OSV(entry *osv.Entry) error {
|
||||
h.osvs = append(h.osvs, entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finding gathers vulnerability findings to be written.
|
||||
func (h *TextHandler) Finding(finding *govulncheck.Finding) error {
|
||||
if err := validateFindings(finding); err != nil {
|
||||
return err
|
||||
}
|
||||
h.findings = append(h.findings, newFindingSummary(finding))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TextHandler) allVulns(findings []*findingSummary) summaryCounters {
|
||||
byVuln := groupByVuln(findings)
|
||||
var called, imported, required [][]*findingSummary
|
||||
mods := map[string]struct{}{}
|
||||
stdlibCalled := false
|
||||
for _, findings := range byVuln {
|
||||
switch {
|
||||
case isStdFindings(findings):
|
||||
if isCalled(findings) {
|
||||
called = append(called, findings)
|
||||
stdlibCalled = true
|
||||
} else {
|
||||
required = append(required, findings)
|
||||
}
|
||||
case isCalled(findings):
|
||||
called = append(called, findings)
|
||||
mods[findings[0].Trace[0].Module] = struct{}{}
|
||||
case isImported(findings):
|
||||
imported = append(imported, findings)
|
||||
default:
|
||||
required = append(required, findings)
|
||||
}
|
||||
}
|
||||
|
||||
if h.scanLevel.WantSymbols() {
|
||||
h.style(sectionStyle, "=== Symbol Results ===\n\n")
|
||||
if len(called) == 0 {
|
||||
h.print(noVulnsMessage, "\n\n")
|
||||
}
|
||||
for index, findings := range called {
|
||||
h.vulnerability(index, findings)
|
||||
}
|
||||
}
|
||||
|
||||
if h.scanLevel == govulncheck.ScanLevelPackage || (h.scanLevel.WantPackages() && h.showAllVulns) {
|
||||
h.style(sectionStyle, "=== Package Results ===\n\n")
|
||||
if len(imported) == 0 {
|
||||
h.print(choose(!h.scanLevel.WantSymbols(), noVulnsMessage, noOtherVulnsMessage), "\n\n")
|
||||
}
|
||||
for index, findings := range imported {
|
||||
h.vulnerability(index, findings)
|
||||
}
|
||||
}
|
||||
|
||||
if h.showAllVulns || h.scanLevel == govulncheck.ScanLevelModule {
|
||||
h.style(sectionStyle, "=== Module Results ===\n\n")
|
||||
if len(required) == 0 {
|
||||
h.print(choose(!h.scanLevel.WantPackages(), noVulnsMessage, noOtherVulnsMessage), "\n\n")
|
||||
}
|
||||
for index, findings := range required {
|
||||
h.vulnerability(index, findings)
|
||||
}
|
||||
}
|
||||
|
||||
return summaryCounters{
|
||||
VulnerabilitiesCalled: len(called),
|
||||
VulnerabilitiesImported: len(imported),
|
||||
VulnerabilitiesRequired: len(required),
|
||||
ModulesCalled: len(mods),
|
||||
StdlibCalled: stdlibCalled,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *TextHandler) vulnerability(index int, findings []*findingSummary) {
|
||||
h.style(keyStyle, "Vulnerability")
|
||||
h.print(" #", index+1, ": ")
|
||||
if isCalled(findings) {
|
||||
h.style(osvCalledStyle, findings[0].OSV.ID)
|
||||
} else {
|
||||
h.style(osvImportedStyle, findings[0].OSV.ID)
|
||||
}
|
||||
h.print("\n")
|
||||
h.style(detailsStyle)
|
||||
description := findings[0].OSV.Summary
|
||||
if description == "" {
|
||||
description = findings[0].OSV.Details
|
||||
}
|
||||
h.wrap(" ", description, 80)
|
||||
h.style(defaultStyle)
|
||||
h.print("\n")
|
||||
h.style(keyStyle, " More info:")
|
||||
h.print(" ", findings[0].OSV.DatabaseSpecific.URL, "\n")
|
||||
|
||||
byModule := groupByModule(findings)
|
||||
first := true
|
||||
for _, module := range byModule {
|
||||
//TODO: this assumes all traces on a module are found and fixed at the same versions
|
||||
lastFrame := module[0].Trace[0]
|
||||
mod := lastFrame.Module
|
||||
path := lastFrame.Module
|
||||
if path == internal.GoStdModulePath {
|
||||
path = lastFrame.Package
|
||||
}
|
||||
foundVersion := moduleVersionString(lastFrame.Module, lastFrame.Version)
|
||||
fixedVersion := moduleVersionString(lastFrame.Module, module[0].FixedVersion)
|
||||
if !first {
|
||||
h.print("\n")
|
||||
}
|
||||
first = false
|
||||
h.print(" ")
|
||||
if mod == internal.GoStdModulePath {
|
||||
h.print("Standard library")
|
||||
} else {
|
||||
h.style(keyStyle, "Module: ")
|
||||
h.print(mod)
|
||||
}
|
||||
h.print("\n ")
|
||||
h.style(keyStyle, "Found in: ")
|
||||
h.print(path, "@", foundVersion, "\n ")
|
||||
h.style(keyStyle, "Fixed in: ")
|
||||
if fixedVersion != "" {
|
||||
h.print(path, "@", fixedVersion)
|
||||
} else {
|
||||
h.print("N/A")
|
||||
}
|
||||
h.print("\n")
|
||||
platforms := platforms(mod, module[0].OSV)
|
||||
if len(platforms) > 0 {
|
||||
h.style(keyStyle, " Platforms: ")
|
||||
for ip, p := range platforms {
|
||||
if ip > 0 {
|
||||
h.print(", ")
|
||||
}
|
||||
h.print(p)
|
||||
}
|
||||
h.print("\n")
|
||||
}
|
||||
h.traces(module)
|
||||
}
|
||||
h.print("\n")
|
||||
}
|
||||
|
||||
func (h *TextHandler) traces(traces []*findingSummary) {
|
||||
first := true
|
||||
count := 1
|
||||
for _, entry := range traces {
|
||||
if entry.Compact == "" {
|
||||
continue
|
||||
}
|
||||
if first {
|
||||
h.style(keyStyle, " Example traces found:\n")
|
||||
}
|
||||
first = false
|
||||
|
||||
h.print(" #", count, ": ")
|
||||
count++
|
||||
if !h.showTraces {
|
||||
h.print(entry.Compact, "\n")
|
||||
} else {
|
||||
h.print("for function ", symbol(entry.Trace[0], false), "\n")
|
||||
for i := len(entry.Trace) - 1; i >= 0; i-- {
|
||||
t := entry.Trace[i]
|
||||
h.print(" ")
|
||||
if t.Position != nil {
|
||||
h.print(posToString(t.Position), ": ")
|
||||
}
|
||||
h.print(symbol(t, false), "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *TextHandler) summary(c summaryCounters) {
|
||||
// print short summary of findings identified at the desired level of scan precision
|
||||
var vulnCount int
|
||||
h.print("Your code ", choose(h.scanLevel.WantSymbols(), "is", "may be"), " affected by ")
|
||||
switch h.scanLevel {
|
||||
case govulncheck.ScanLevelSymbol:
|
||||
vulnCount = c.VulnerabilitiesCalled
|
||||
case govulncheck.ScanLevelPackage:
|
||||
vulnCount = c.VulnerabilitiesImported
|
||||
case govulncheck.ScanLevelModule:
|
||||
vulnCount = c.VulnerabilitiesRequired
|
||||
}
|
||||
h.style(valueStyle, vulnCount)
|
||||
h.print(choose(vulnCount == 1, ` vulnerability`, ` vulnerabilities`))
|
||||
if h.scanLevel.WantSymbols() {
|
||||
h.print(choose(c.ModulesCalled > 0 || c.StdlibCalled, ` from `, ``))
|
||||
if c.ModulesCalled > 0 {
|
||||
h.style(valueStyle, c.ModulesCalled)
|
||||
h.print(choose(c.ModulesCalled == 1, ` module`, ` modules`))
|
||||
}
|
||||
if c.StdlibCalled {
|
||||
if c.ModulesCalled != 0 {
|
||||
h.print(` and `)
|
||||
}
|
||||
h.print(`the Go standard library`)
|
||||
}
|
||||
}
|
||||
h.print(".\n")
|
||||
|
||||
// print summary for vulnerabilities found at other levels of scan precision
|
||||
if other := h.summaryOtherVulns(c); other != "" {
|
||||
h.wrap("", other, 80)
|
||||
h.print("\n")
|
||||
}
|
||||
|
||||
// print suggested flags for more/better info depending on scan level and if in verbose mode
|
||||
if sugg := h.summarySuggestion(); sugg != "" {
|
||||
h.wrap("", sugg, 80)
|
||||
h.print("\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *TextHandler) summaryOtherVulns(c summaryCounters) string {
|
||||
var summary strings.Builder
|
||||
if c.VulnerabilitiesRequired+c.VulnerabilitiesImported == 0 {
|
||||
summary.WriteString("This scan found no other vulnerabilities in ")
|
||||
if h.scanLevel.WantSymbols() {
|
||||
summary.WriteString("packages you import or ")
|
||||
}
|
||||
summary.WriteString("modules you require.")
|
||||
} else {
|
||||
summary.WriteString(choose(h.scanLevel.WantPackages(), "This scan also found ", ""))
|
||||
if h.scanLevel.WantSymbols() {
|
||||
summary.WriteString(fmt.Sprint(c.VulnerabilitiesImported))
|
||||
summary.WriteString(choose(c.VulnerabilitiesImported == 1, ` vulnerability `, ` vulnerabilities `))
|
||||
summary.WriteString("in packages you import and ")
|
||||
}
|
||||
if h.scanLevel.WantPackages() {
|
||||
summary.WriteString(fmt.Sprint(c.VulnerabilitiesRequired))
|
||||
summary.WriteString(choose(c.VulnerabilitiesRequired == 1, ` vulnerability `, ` vulnerabilities `))
|
||||
summary.WriteString("in modules you require")
|
||||
summary.WriteString(choose(h.scanLevel.WantSymbols(), ", but your code doesn't appear to call these vulnerabilities.", "."))
|
||||
}
|
||||
}
|
||||
return summary.String()
|
||||
}
|
||||
|
||||
func (h *TextHandler) summarySuggestion() string {
|
||||
var sugg strings.Builder
|
||||
switch h.scanLevel {
|
||||
case govulncheck.ScanLevelSymbol:
|
||||
if !h.showAllVulns {
|
||||
sugg.WriteString("Use " + verboseMessage + ".")
|
||||
}
|
||||
case govulncheck.ScanLevelPackage:
|
||||
sugg.WriteString("Use " + symbolMessage)
|
||||
if !h.showAllVulns {
|
||||
sugg.WriteString(" and " + verboseMessage)
|
||||
}
|
||||
sugg.WriteString(".")
|
||||
case govulncheck.ScanLevelModule:
|
||||
sugg.WriteString("Use " + symbolMessage + ".")
|
||||
}
|
||||
return sugg.String()
|
||||
}
|
||||
|
||||
func (h *TextHandler) style(style style, values ...any) {
|
||||
if h.showColor {
|
||||
switch style {
|
||||
default:
|
||||
h.print(colorReset)
|
||||
case osvCalledStyle:
|
||||
h.print(colorBold, fgRed)
|
||||
case osvImportedStyle:
|
||||
h.print(colorBold, fgGreen)
|
||||
case detailsStyle:
|
||||
h.print(colorFaint)
|
||||
case sectionStyle:
|
||||
h.print(fgBlue)
|
||||
case keyStyle:
|
||||
h.print(colorFaint, fgYellow)
|
||||
case valueStyle:
|
||||
h.print(colorBold, fgCyan)
|
||||
}
|
||||
}
|
||||
h.print(values...)
|
||||
if h.showColor && len(values) > 0 {
|
||||
h.print(colorReset)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *TextHandler) print(values ...any) int {
|
||||
total, w := 0, 0
|
||||
for _, v := range values {
|
||||
if h.err != nil {
|
||||
return total
|
||||
}
|
||||
// do we need to specialize for some types, like time?
|
||||
w, h.err = fmt.Fprint(h.w, v)
|
||||
total += w
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// wrap wraps s to fit in maxWidth by breaking it into lines at whitespace. If a
|
||||
// single word is longer than maxWidth, it is retained as its own line.
|
||||
func (h *TextHandler) wrap(indent string, s string, maxWidth int) {
|
||||
w := 0
|
||||
for _, f := range strings.Fields(s) {
|
||||
if w > 0 && w+len(f)+1 > maxWidth {
|
||||
// line would be too long with this word
|
||||
h.print("\n")
|
||||
w = 0
|
||||
}
|
||||
if w == 0 {
|
||||
// first field on line, indent
|
||||
w = h.print(indent)
|
||||
} else {
|
||||
// not first word, space separate
|
||||
w += h.print(" ")
|
||||
}
|
||||
// now write the word
|
||||
w += h.print(f)
|
||||
}
|
||||
}
|
||||
|
||||
func choose[t any](b bool, yes, no t) t {
|
||||
if b {
|
||||
return yes
|
||||
}
|
||||
return no
|
||||
}
|
||||
|
||||
func isStdFindings(findings []*findingSummary) bool {
|
||||
for _, f := range findings {
|
||||
if vulncheck.IsStdPackage(f.Trace[0].Package) || f.Trace[0].Module == internal.GoStdModulePath {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"golang.org/x/vuln/internal"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
// validateFindings checks that the supplied findings all obey the protocol
|
||||
// rules.
|
||||
func validateFindings(findings ...*govulncheck.Finding) error {
|
||||
for _, f := range findings {
|
||||
if f.OSV == "" {
|
||||
return fmt.Errorf("invalid finding: all findings must have an associated OSV")
|
||||
}
|
||||
if len(f.Trace) < 1 {
|
||||
return fmt.Errorf("invalid finding: all callstacks must have at least one frame")
|
||||
}
|
||||
for _, frame := range f.Trace {
|
||||
if frame.Version != "" && frame.Module == "" {
|
||||
return fmt.Errorf("invalid finding: if Frame.Version is set, Frame.Module must also be")
|
||||
}
|
||||
if frame.Package != "" && frame.Module == "" {
|
||||
return fmt.Errorf("invalid finding: if Frame.Package is set, Frame.Module must also be")
|
||||
}
|
||||
if frame.Function != "" && frame.Package == "" {
|
||||
return fmt.Errorf("invalid finding: if Frame.Function is set, Frame.Package must also be")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func moduleVersionString(modulePath, version string) string {
|
||||
if version == "" {
|
||||
return ""
|
||||
}
|
||||
if modulePath == internal.GoStdModulePath || modulePath == internal.GoCmdModulePath {
|
||||
version = semverToGoTag(version)
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
func gomodExists(dir string) bool {
|
||||
cmd := exec.Command("go", "env", "GOMOD")
|
||||
cmd.Dir = dir
|
||||
out, err := cmd.Output()
|
||||
output := string(out)
|
||||
// If module-aware mode is enabled, but there is no go.mod, GOMOD will be os.DevNull
|
||||
// If module-aware mode is disabled, GOMOD will be the empty string.
|
||||
return err == nil && !(output == os.DevNull || output == "")
|
||||
}
|
||||
Reference in New Issue
Block a user