Initialize module and dependencies

This commit is contained in:
dwrz
2026-01-04 20:57:40 +00:00
commit a3b390c008
514 changed files with 310495 additions and 0 deletions

110
vendor/golang.org/x/vuln/internal/scan/binary.go generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 == "")
}