Initialize module and dependencies
This commit is contained in:
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
|
||||
}
|
||||
Reference in New Issue
Block a user