Initialize module and dependencies
This commit is contained in:
110
vendor/golang.org/x/vuln/internal/scan/binary.go
generated
vendored
Normal file
110
vendor/golang.org/x/vuln/internal/scan/binary.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
// 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"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"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) {
|
||||
// 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(path)
|
||||
if err == nil {
|
||||
var main *packages.Module
|
||||
if bi.Main.Path != "" {
|
||||
main = &packages.Module{
|
||||
Path: bi.Main.Path,
|
||||
Version: bi.Main.Version,
|
||||
}
|
||||
}
|
||||
|
||||
return &vulncheck.Bin{
|
||||
Path: bi.Path,
|
||||
Main: main,
|
||||
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(path)
|
||||
if bin != nil {
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("unrecognized binary format")
|
||||
}
|
||||
|
||||
// parseBlob extracts vulncheck.Bin from a valid blob at path.
|
||||
// If it cannot recognize a valid blob, returns nil.
|
||||
func parseBlob(path string) *vulncheck.Bin {
|
||||
from, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer from.Close()
|
||||
|
||||
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 ""
|
||||
}
|
||||
97
vendor/golang.org/x/vuln/internal/scan/color.go
generated
vendored
Normal file
97
vendor/golang.org/x/vuln/internal/scan/color.go
generated
vendored
Normal file
@@ -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
|
||||
)
|
||||
67
vendor/golang.org/x/vuln/internal/scan/errors.go
generated
vendored
Normal file
67
vendor/golang.org/x/vuln/internal/scan/errors.go
generated
vendored
Normal file
@@ -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 exit with 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")
|
||||
}
|
||||
60
vendor/golang.org/x/vuln/internal/scan/extract.go
generated
vendored
Normal file
60
vendor/golang.org/x/vuln/internal/scan/extract.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// 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 (
|
||||
"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
|
||||
})
|
||||
}
|
||||
35
vendor/golang.org/x/vuln/internal/scan/filepath.go
generated
vendored
Normal file
35
vendor/golang.org/x/vuln/internal/scan/filepath.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
||||
303
vendor/golang.org/x/vuln/internal/scan/flags.go
generated
vendored
Normal file
303
vendor/golang.org/x/vuln/internal/scan/flags.go
generated
vendored
Normal file
@@ -0,0 +1,303 @@
|
||||
// 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"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
govulncheck.Config
|
||||
patterns []string
|
||||
db string
|
||||
dir string
|
||||
tags buildutil.TagsFlag
|
||||
test bool
|
||||
show ShowFlag
|
||||
format FormatFlag
|
||||
env []string
|
||||
}
|
||||
|
||||
func parseFlags(cfg *config, stderr io.Writer, args []string) error {
|
||||
var version bool
|
||||
var json bool
|
||||
var scanFlag ScanFlag
|
||||
var modeFlag ModeFlag
|
||||
flags := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
flags.SetOutput(stderr)
|
||||
flags.BoolVar(&json, "json", false, "output JSON (Go compatible legacy flag, see format flag)")
|
||||
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.Var(&modeFlag, "mode", "supports 'source', 'binary', and 'extract' (default 'source')")
|
||||
flags.Var(&cfg.tags, "tags", "comma-separated `list` of build tags")
|
||||
flags.Var(&cfg.show, "show", "enable display of additional information specified by the comma separated `list`\nThe supported values are 'traces','color', 'version', and 'verbose'")
|
||||
flags.Var(&cfg.format, "format", "specify format output\nThe supported values are 'text', 'json', 'sarif', and 'openvex' (default 'text')")
|
||||
flags.BoolVar(&version, "version", false, "print the version information")
|
||||
flags.Var(&scanFlag, "scan", "set the scanning level desired, one of 'module', 'package', or 'symbol' (default 'symbol')")
|
||||
|
||||
// We don't want to print the whole usage message on each flags
|
||||
// error, so we set to a no-op and do the printing ourselves.
|
||||
flags.Usage = func() {}
|
||||
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 {
|
||||
usage() // print usage only on help
|
||||
return errHelp
|
||||
}
|
||||
return errUsage
|
||||
}
|
||||
cfg.patterns = flags.Args()
|
||||
if version {
|
||||
cfg.show = append(cfg.show, "version")
|
||||
}
|
||||
cfg.ScanLevel = govulncheck.ScanLevel(scanFlag)
|
||||
cfg.ScanMode = govulncheck.ScanMode(modeFlag)
|
||||
if err := validateConfig(cfg, json); err != nil {
|
||||
fmt.Fprintln(flags.Output(), err)
|
||||
return errUsage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateConfig(cfg *config, json bool) error {
|
||||
// take care of default values
|
||||
if cfg.ScanMode == "" {
|
||||
cfg.ScanMode = govulncheck.ScanModeSource
|
||||
}
|
||||
if cfg.ScanLevel == "" {
|
||||
cfg.ScanLevel = govulncheck.ScanLevelSymbol
|
||||
}
|
||||
if json {
|
||||
if cfg.format != formatUnset {
|
||||
return fmt.Errorf("the -json flag cannot be used with -format flag")
|
||||
}
|
||||
cfg.format = formatJSON
|
||||
} else {
|
||||
if cfg.format == formatUnset {
|
||||
cfg.format = formatText
|
||||
}
|
||||
}
|
||||
|
||||
// show flag is only supported with text output
|
||||
if cfg.format != formatText && len(cfg.show) > 0 {
|
||||
return fmt.Errorf("the -show flag is not supported for %s output", cfg.format)
|
||||
}
|
||||
|
||||
switch cfg.ScanMode {
|
||||
case govulncheck.ScanModeSource:
|
||||
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 govulncheck.ScanModeBinary:
|
||||
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 govulncheck.ScanModeExtract:
|
||||
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.format == formatJSON {
|
||||
return fmt.Errorf("the json format 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 govulncheck.ScanModeConvert:
|
||||
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 govulncheck.ScanModeQuery:
|
||||
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.format != formatJSON {
|
||||
return fmt.Errorf("the json format 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
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isFile(path string) bool {
|
||||
s, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return !s.IsDir()
|
||||
}
|
||||
|
||||
var errFlagParse = errors.New("see -help for details")
|
||||
|
||||
// ShowFlag is used for parsing and validation of
|
||||
// govulncheck -show flag.
|
||||
type ShowFlag []string
|
||||
|
||||
var supportedShows = map[string]bool{
|
||||
"traces": true,
|
||||
"color": true,
|
||||
"verbose": true,
|
||||
"version": true,
|
||||
}
|
||||
|
||||
func (v *ShowFlag) Set(s string) error {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
for _, show := range strings.Split(s, ",") {
|
||||
sh := strings.TrimSpace(show)
|
||||
if _, ok := supportedShows[sh]; !ok {
|
||||
return errFlagParse
|
||||
}
|
||||
*v = append(*v, sh)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *ShowFlag) Get() interface{} { return *v }
|
||||
func (v *ShowFlag) String() string { return "" }
|
||||
|
||||
// Update the text handler h with values of the flag.
|
||||
func (v ShowFlag) Update(h *TextHandler) {
|
||||
for _, show := range v {
|
||||
switch show {
|
||||
case "traces":
|
||||
h.showTraces = true
|
||||
case "color":
|
||||
h.showColor = true
|
||||
case "version":
|
||||
h.showVersion = true
|
||||
case "verbose":
|
||||
h.showVerbose = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FormatFlag is used for parsing and validation of
|
||||
// govulncheck -format flag.
|
||||
type FormatFlag string
|
||||
|
||||
const (
|
||||
formatUnset = ""
|
||||
formatJSON = "json"
|
||||
formatText = "text"
|
||||
formatSarif = "sarif"
|
||||
formatOpenVEX = "openvex"
|
||||
)
|
||||
|
||||
var supportedFormats = map[string]bool{
|
||||
formatJSON: true,
|
||||
formatText: true,
|
||||
formatSarif: true,
|
||||
formatOpenVEX: true,
|
||||
}
|
||||
|
||||
func (f *FormatFlag) Get() interface{} { return *f }
|
||||
func (f *FormatFlag) Set(s string) error {
|
||||
if _, ok := supportedFormats[s]; !ok {
|
||||
return errFlagParse
|
||||
}
|
||||
*f = FormatFlag(s)
|
||||
return nil
|
||||
}
|
||||
func (f *FormatFlag) String() string { return "" }
|
||||
|
||||
// ModeFlag is used for parsing and validation of
|
||||
// govulncheck -mode flag.
|
||||
type ModeFlag string
|
||||
|
||||
var supportedModes = map[string]bool{
|
||||
govulncheck.ScanModeSource: true,
|
||||
govulncheck.ScanModeBinary: true,
|
||||
govulncheck.ScanModeConvert: true,
|
||||
govulncheck.ScanModeQuery: true,
|
||||
govulncheck.ScanModeExtract: true,
|
||||
}
|
||||
|
||||
func (f *ModeFlag) Get() interface{} { return *f }
|
||||
func (f *ModeFlag) Set(s string) error {
|
||||
if _, ok := supportedModes[s]; !ok {
|
||||
return errFlagParse
|
||||
}
|
||||
*f = ModeFlag(s)
|
||||
return nil
|
||||
}
|
||||
func (f *ModeFlag) String() string { return "" }
|
||||
|
||||
// ScanFlag is used for parsing and validation of
|
||||
// govulncheck -scan flag.
|
||||
type ScanFlag string
|
||||
|
||||
var supportedLevels = map[string]bool{
|
||||
govulncheck.ScanLevelModule: true,
|
||||
govulncheck.ScanLevelPackage: true,
|
||||
govulncheck.ScanLevelSymbol: true,
|
||||
}
|
||||
|
||||
func (f *ScanFlag) Get() interface{} { return *f }
|
||||
func (f *ScanFlag) Set(s string) error {
|
||||
if _, ok := supportedLevels[s]; !ok {
|
||||
return errFlagParse
|
||||
}
|
||||
*f = ScanFlag(s)
|
||||
return nil
|
||||
}
|
||||
func (f *ScanFlag) String() string { return "" }
|
||||
74
vendor/golang.org/x/vuln/internal/scan/query.go
generated
vendored
Normal file
74
vendor/golang.org/x/vuln/internal/scan/query.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
||||
160
vendor/golang.org/x/vuln/internal/scan/run.go
generated
vendored
Normal file
160
vendor/golang.org/x/vuln/internal/scan/run.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
// 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/telemetry/counter"
|
||||
"golang.org/x/vuln/internal/client"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/openvex"
|
||||
"golang.org/x/vuln/internal/sarif"
|
||||
)
|
||||
|
||||
// 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 cfg.format {
|
||||
case formatJSON:
|
||||
handler = govulncheck.NewJSONHandler(stdout)
|
||||
case formatSarif:
|
||||
handler = sarif.NewHandler(stdout)
|
||||
case formatOpenVEX:
|
||||
handler = openvex.NewHandler(stdout)
|
||||
default:
|
||||
th := NewTextHandler(stdout)
|
||||
cfg.show.Update(th)
|
||||
handler = th
|
||||
}
|
||||
|
||||
if err := handler.Config(&cfg.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
incTelemetryFlagCounters(cfg)
|
||||
|
||||
switch cfg.ScanMode {
|
||||
case govulncheck.ScanModeSource:
|
||||
dir := filepath.FromSlash(cfg.dir)
|
||||
err = runSource(ctx, handler, cfg, client, dir)
|
||||
case govulncheck.ScanModeBinary:
|
||||
err = runBinary(ctx, handler, cfg, client)
|
||||
case govulncheck.ScanModeExtract:
|
||||
return runExtract(cfg, stdout)
|
||||
case govulncheck.ScanModeQuery:
|
||||
err = runQuery(ctx, handler, cfg, client)
|
||||
case govulncheck.ScanModeConvert:
|
||||
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.ScanMode == govulncheck.ScanModeSource && 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()
|
||||
}
|
||||
|
||||
func incTelemetryFlagCounters(cfg *config) {
|
||||
counter.Inc(fmt.Sprintf("govulncheck/mode:%s", cfg.ScanMode))
|
||||
counter.Inc(fmt.Sprintf("govulncheck/scan:%s", cfg.ScanLevel))
|
||||
counter.Inc(fmt.Sprintf("govulncheck/format:%s", cfg.format))
|
||||
|
||||
if len(cfg.show) == 0 {
|
||||
counter.Inc("govulncheck/show:none")
|
||||
}
|
||||
for _, s := range cfg.show {
|
||||
counter.Inc(fmt.Sprintf("govulncheck/show:%s", s))
|
||||
}
|
||||
}
|
||||
|
||||
func Flush(h govulncheck.Handler) error {
|
||||
if th, ok := h.(interface{ Flush() error }); ok {
|
||||
return th.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
49
vendor/golang.org/x/vuln/internal/scan/source.go
generated
vendored
Normal file
49
vendor/golang.org/x/vuln/internal/scan/source.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
// 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
|
||||
}
|
||||
graph := vulncheck.NewPackageGraph(cfg.GoVersion)
|
||||
pkgConfig := &packages.Config{
|
||||
Dir: dir,
|
||||
Tests: cfg.test,
|
||||
Env: cfg.env,
|
||||
}
|
||||
if err := graph.LoadPackagesAndMods(pkgConfig, cfg.tags, cfg.patterns, cfg.ScanLevel == govulncheck.ScanLevelSymbol); err != nil {
|
||||
if isGoVersionMismatchError(err) {
|
||||
return fmt.Errorf("%v\n\n%v", errGoVersionMismatch, err)
|
||||
}
|
||||
return fmt.Errorf("loading packages: %w", err)
|
||||
}
|
||||
|
||||
if cfg.ScanLevel.WantPackages() && len(graph.TopPkgs()) == 0 {
|
||||
return nil // early exit
|
||||
}
|
||||
return vulncheck.Source(ctx, handler, &cfg.Config, client, graph)
|
||||
}
|
||||
71
vendor/golang.org/x/vuln/internal/scan/stdlib.go
generated
vendored
Normal file
71
vendor/golang.org/x/vuln/internal/scan/stdlib.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
||||
290
vendor/golang.org/x/vuln/internal/scan/template.go
generated
vendored
Normal file
290
vendor/golang.org/x/vuln/internal/scan/template.go
generated
vendored
Normal file
@@ -0,0 +1,290 @@
|
||||
// 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"
|
||||
"golang.org/x/vuln/internal/traces"
|
||||
)
|
||||
|
||||
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{}
|
||||
addSymbol(buf, frame, short)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func symbolName(frame *govulncheck.Frame) string {
|
||||
buf := &strings.Builder{}
|
||||
addSymbolName(buf, frame)
|
||||
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 {
|
||||
compact := traces.Compact(finding)
|
||||
if len(compact) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
l := len(compact)
|
||||
iTop := l - 1
|
||||
buf := &strings.Builder{}
|
||||
topPos := posToString(compact[iTop].Position)
|
||||
if topPos != "" {
|
||||
buf.WriteString(topPos)
|
||||
buf.WriteString(": ")
|
||||
}
|
||||
|
||||
if l > 1 {
|
||||
// print the root of the compact trace
|
||||
addSymbol(buf, compact[iTop], true)
|
||||
buf.WriteString(" calls ")
|
||||
}
|
||||
if l > 2 {
|
||||
// print next element of the trace, if any
|
||||
addSymbol(buf, compact[iTop-1], true)
|
||||
buf.WriteString(", which")
|
||||
if l > 3 {
|
||||
// don't print the third element, just acknowledge it
|
||||
buf.WriteString(" eventually")
|
||||
}
|
||||
buf.WriteString(" calls ")
|
||||
}
|
||||
addSymbol(buf, compact[0], true) // print the vulnerable symbol
|
||||
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 addSymbol(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, ".")
|
||||
}
|
||||
addSymbolName(w, frame)
|
||||
}
|
||||
|
||||
func addSymbolName(w io.Writer, frame *govulncheck.Frame) {
|
||||
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)
|
||||
}
|
||||
575
vendor/golang.org/x/vuln/internal/scan/text.go
generated
vendored
Normal file
575
vendor/golang.org/x/vuln/internal/scan/text.go
generated
vendored
Normal file
@@ -0,0 +1,575 @@
|
||||
// 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"
|
||||
"sort"
|
||||
"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
|
||||
sbom *govulncheck.SBOM
|
||||
osvs []*osv.Entry
|
||||
findings []*findingSummary
|
||||
scanLevel govulncheck.ScanLevel
|
||||
scanMode govulncheck.ScanMode
|
||||
|
||||
err error
|
||||
|
||||
showColor bool
|
||||
showTraces bool
|
||||
showVersion bool
|
||||
showVerbose 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) Flush() error {
|
||||
if h.showVerbose {
|
||||
h.printSBOM()
|
||||
}
|
||||
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 {
|
||||
h.scanLevel = config.ScanLevel
|
||||
h.scanMode = config.ScanMode
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (h *TextHandler) SBOM(sbom *govulncheck.SBOM) error {
|
||||
h.sbom = sbom
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TextHandler) printSBOM() error {
|
||||
if h.sbom == nil {
|
||||
h.print("No packages matched the provided pattern.\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
printed := false
|
||||
|
||||
for i, root := range h.sbom.Roots {
|
||||
if i == 0 {
|
||||
if len(h.sbom.Roots) > 1 {
|
||||
h.print("The package pattern matched the following ", len(h.sbom.Roots), " root packages:\n")
|
||||
} else {
|
||||
h.print("The package pattern matched the following root package:\n")
|
||||
}
|
||||
}
|
||||
|
||||
h.print(" ", root, "\n")
|
||||
printed = true
|
||||
}
|
||||
for i, mod := range h.sbom.Modules {
|
||||
if i == 0 && mod.Path != "stdlib" {
|
||||
h.print("Govulncheck scanned the following ", len(h.sbom.Modules)-1, " modules and the ", h.sbom.GoVersion, " standard library:\n")
|
||||
}
|
||||
|
||||
if mod.Path == "stdlib" {
|
||||
continue
|
||||
}
|
||||
|
||||
h.print(" ", mod.Path)
|
||||
if mod.Version != "" {
|
||||
h.print("@", mod.Version)
|
||||
}
|
||||
h.print("\n")
|
||||
printed = true
|
||||
}
|
||||
if printed {
|
||||
h.print("\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Progress writes progress updates during govulncheck execution.
|
||||
func (h *TextHandler) Progress(progress *govulncheck.Progress) error {
|
||||
if h.showVerbose {
|
||||
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 isCalled(findings):
|
||||
called = append(called, findings)
|
||||
if isStdFindings(findings) {
|
||||
stdlibCalled = true
|
||||
} else {
|
||||
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.showVerbose) {
|
||||
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.showVerbose || 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 {
|
||||
// Note: there can be several findingSummaries for the same vulnerability
|
||||
// emitted during streaming for different scan levels.
|
||||
|
||||
// The module is same for all finding summaries.
|
||||
lastFrame := module[0].Trace[0]
|
||||
mod := lastFrame.Module
|
||||
// For stdlib, try to show package path as module name where
|
||||
// the scan level allows it.
|
||||
// TODO: should this be done in byModule as well?
|
||||
path := lastFrame.Module
|
||||
if stdPkg := h.pkg(module); path == internal.GoStdModulePath && stdPkg != "" {
|
||||
path = stdPkg
|
||||
}
|
||||
// All findings on a module are found and fixed at the same version
|
||||
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")
|
||||
}
|
||||
|
||||
// pkg gives the package information for findings summaries
|
||||
// if one exists. This is only used to print package path
|
||||
// instead of a module for stdlib vulnerabilities at symbol
|
||||
// and package scan level.
|
||||
func (h *TextHandler) pkg(summaries []*findingSummary) string {
|
||||
for _, f := range summaries {
|
||||
if pkg := f.Trace[0].Package; pkg != "" {
|
||||
return pkg
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// traces prints out the most precise trace information
|
||||
// found in the given summaries.
|
||||
func (h *TextHandler) traces(traces []*findingSummary) {
|
||||
// Sort the traces by the vulnerable symbol. This
|
||||
// guarantees determinism since we are currently
|
||||
// showing only one trace per symbol.
|
||||
sort.SliceStable(traces, func(i, j int) bool {
|
||||
return symbol(traces[i].Trace[0], true) < symbol(traces[j].Trace[0], true)
|
||||
})
|
||||
|
||||
// compacts are finding summaries with compact traces
|
||||
// suitable for non-verbose textual output. Currently,
|
||||
// only traces produced by symbol analysis.
|
||||
var compacts []*findingSummary
|
||||
for _, t := range traces {
|
||||
if t.Compact != "" {
|
||||
compacts = append(compacts, t)
|
||||
}
|
||||
}
|
||||
|
||||
// binLimit is a limit on the number of binary traces
|
||||
// to show. Traces for binaries are less interesting
|
||||
// as users cannot act on them and they can hence
|
||||
// spam users.
|
||||
const binLimit = 5
|
||||
binary := h.scanMode == govulncheck.ScanModeBinary
|
||||
for i, entry := range compacts {
|
||||
if i == 0 {
|
||||
if binary {
|
||||
h.style(keyStyle, " Vulnerable symbols found:\n")
|
||||
} else {
|
||||
h.style(keyStyle, " Example traces found:\n")
|
||||
}
|
||||
}
|
||||
|
||||
// skip showing all symbols in binary mode unless '-show traces' is on.
|
||||
if binary && (i+1) > binLimit && !h.showTraces {
|
||||
h.print(" Use '-show traces' to see the other ", len(compacts)-binLimit, " found symbols\n")
|
||||
break
|
||||
}
|
||||
|
||||
h.print(" #", i+1, ": ")
|
||||
|
||||
if !h.showTraces { // show summarized traces
|
||||
h.print(entry.Compact, "\n")
|
||||
continue
|
||||
}
|
||||
|
||||
if binary {
|
||||
// There are no call stacks in binary mode
|
||||
// so just show the full symbol name.
|
||||
h.print(symbol(entry.Trace[0], false), "\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(" ")
|
||||
h.print(symbolName(t))
|
||||
if t.Position != nil {
|
||||
h.print(" @ ", symbolPath(t))
|
||||
}
|
||||
h.print("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// symbolPath returns a user-friendly path to a symbol.
|
||||
func symbolPath(t *govulncheck.Frame) string {
|
||||
// Add module path prefix to symbol paths to be more
|
||||
// explicit to which module the symbols belong to.
|
||||
return t.Module + "/" + posToString(t.Position)
|
||||
}
|
||||
|
||||
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.showVerbose {
|
||||
sugg.WriteString("Use " + verboseMessage + ".")
|
||||
}
|
||||
case govulncheck.ScanLevelPackage:
|
||||
sugg.WriteString("Use " + symbolMessage)
|
||||
if !h.showVerbose {
|
||||
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
|
||||
}
|
||||
60
vendor/golang.org/x/vuln/internal/scan/util.go
generated
vendored
Normal file
60
vendor/golang.org/x/vuln/internal/scan/util.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// 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"
|
||||
"strings"
|
||||
|
||||
"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 (%s) is set, Frame.Module must also be", frame.Version)
|
||||
}
|
||||
if frame.Package != "" && frame.Module == "" {
|
||||
return fmt.Errorf("invalid finding: if Frame.Package (%s) is set, Frame.Module must also be", frame.Package)
|
||||
}
|
||||
if frame.Function != "" && frame.Package == "" {
|
||||
return fmt.Errorf("invalid finding: if Frame.Function (%s) is set, Frame.Package must also be", frame.Function)
|
||||
}
|
||||
}
|
||||
}
|
||||
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 := strings.TrimSpace(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