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

409
vendor/golang.org/x/vuln/internal/sarif/handler.go generated vendored Normal file
View File

@@ -0,0 +1,409 @@
// 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 sarif
import (
"encoding/json"
"fmt"
"io"
"path/filepath"
"sort"
"golang.org/x/vuln/internal"
"golang.org/x/vuln/internal/govulncheck"
"golang.org/x/vuln/internal/osv"
"golang.org/x/vuln/internal/traces"
)
// handler for sarif output.
type handler struct {
w io.Writer
cfg *govulncheck.Config
osvs map[string]*osv.Entry
// findings contains same-level findings for an
// OSV at the most precise level of granularity
// available. This means, for instance, that if
// an osv is indeed called, then all findings for
// the osv will have call stack info.
findings map[string][]*govulncheck.Finding
}
func NewHandler(w io.Writer) *handler {
return &handler{
w: w,
osvs: make(map[string]*osv.Entry),
findings: make(map[string][]*govulncheck.Finding),
}
}
func (h *handler) Config(c *govulncheck.Config) error {
h.cfg = c
return nil
}
func (h *handler) Progress(p *govulncheck.Progress) error {
return nil // not needed by sarif
}
func (h *handler) SBOM(s *govulncheck.SBOM) error {
return nil // not needed by sarif
}
func (h *handler) OSV(e *osv.Entry) error {
h.osvs[e.ID] = e
return nil
}
// moreSpecific favors a call finding over a non-call
// finding and a package finding over a module finding.
func moreSpecific(f1, f2 *govulncheck.Finding) int {
if len(f1.Trace) > 1 && len(f2.Trace) > 1 {
// Both are call stack findings.
return 0
}
if len(f1.Trace) > 1 {
return -1
}
if len(f2.Trace) > 1 {
return 1
}
fr1, fr2 := f1.Trace[0], f2.Trace[0]
if fr1.Function != "" && fr2.Function == "" {
return -1
}
if fr1.Function == "" && fr2.Function != "" {
return 1
}
if fr1.Package != "" && fr2.Package == "" {
return -1
}
if fr1.Package == "" && fr2.Package != "" {
return -1
}
return 0 // findings always have module info
}
func (h *handler) Finding(f *govulncheck.Finding) error {
fs := h.findings[f.OSV]
if len(fs) == 0 {
fs = []*govulncheck.Finding{f}
} else {
if ms := moreSpecific(f, fs[0]); ms == -1 {
// The new finding is more specific, so we need
// to erase existing findings and add the new one.
fs = []*govulncheck.Finding{f}
} else if ms == 0 {
// The new finding is equal to an existing one and
// because of the invariant on h.findings, it is
// also equal to all existing ones.
fs = append(fs, f)
}
// Otherwise, the new finding is at a less precise level.
}
h.findings[f.OSV] = fs
return nil
}
// Flush is used to print out to w the sarif json output.
// This is needed as sarif is not streamed.
func (h *handler) Flush() error {
sLog := toSarif(h)
s, err := json.MarshalIndent(sLog, "", " ")
if err != nil {
return err
}
h.w.Write(s)
return nil
}
func toSarif(h *handler) Log {
cfg := h.cfg
r := Run{
Tool: Tool{
Driver: Driver{
Name: cfg.ScannerName,
Version: cfg.ScannerVersion,
InformationURI: "https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck",
Properties: *cfg,
Rules: rules(h),
},
},
Results: results(h),
}
return Log{
Version: "2.1.0",
Schema: "https://json.schemastore.org/sarif-2.1.0.json",
Runs: []Run{r},
}
}
func rules(h *handler) []Rule {
rs := make([]Rule, 0, len(h.findings)) // must not be nil
for id := range h.findings {
osv := h.osvs[id]
// s is either summary if it exists, or details
// otherwise. Govulncheck text does the same.
s := osv.Summary
if s == "" {
s = osv.Details
}
rs = append(rs, Rule{
ID: osv.ID,
ShortDescription: Description{Text: fmt.Sprintf("[%s] %s", osv.ID, s)},
FullDescription: Description{Text: s},
HelpURI: fmt.Sprintf("https://pkg.go.dev/vuln/%s", osv.ID),
Help: Description{Text: osv.Details},
Properties: RuleTags{Tags: tags(osv)},
})
}
sort.SliceStable(rs, func(i, j int) bool { return rs[i].ID < rs[j].ID })
return rs
}
// tags returns an slice of zero or
// more aliases of o.
func tags(o *osv.Entry) []string {
if len(o.Aliases) > 0 {
return o.Aliases
}
return []string{} // must not be nil
}
func results(h *handler) []Result {
results := make([]Result, 0, len(h.findings)) // must not be nil
for osv, fs := range h.findings {
var locs []Location
if h.cfg.ScanMode != govulncheck.ScanModeBinary {
// Attach result to the go.mod file for source analysis.
// But there is no such place for binaries.
locs = []Location{{PhysicalLocation: PhysicalLocation{
ArtifactLocation: ArtifactLocation{
URI: "go.mod",
URIBaseID: SrcRootID,
},
Region: Region{StartLine: 1}, // for now, point to the first line
},
Message: Description{Text: fmt.Sprintf("Findings for vulnerability %s", osv)}, // not having a message here results in an invalid sarif
}}
}
res := Result{
RuleID: osv,
Level: level(fs[0], h.cfg),
Message: Description{Text: resultMessage(fs, h.cfg)},
Stacks: stacks(h, fs),
CodeFlows: codeFlows(h, fs),
Locations: locs,
}
results = append(results, res)
}
sort.SliceStable(results, func(i, j int) bool { return results[i].RuleID < results[j].RuleID }) // for deterministic output
return results
}
func resultMessage(findings []*govulncheck.Finding, cfg *govulncheck.Config) string {
// We can infer the findings' level by just looking at the
// top trace frame of any finding.
frame := findings[0].Trace[0]
uniqueElems := make(map[string]bool)
if frame.Function == "" && frame.Package == "" { // module level findings
for _, f := range findings {
uniqueElems[f.Trace[0].Module] = true
}
} else { // symbol and package level findings
for _, f := range findings {
uniqueElems[f.Trace[0].Package] = true
}
}
var elems []string
for e := range uniqueElems {
elems = append(elems, e)
}
sort.Strings(elems)
l := len(elems)
elemList := list(elems)
main, addition := "", ""
const runCallAnalysis = "Run the call-level analysis to understand whether your code actually calls the vulnerabilities."
switch {
case frame.Function != "":
main = fmt.Sprintf("calls vulnerable functions in %d package%s (%s).", l, choose("", "s", l == 1), elemList)
case frame.Package != "":
main = fmt.Sprintf("imports %d vulnerable package%s (%s)", l, choose("", "s", l == 1), elemList)
addition = choose(", but doesnt appear to call any of the vulnerable symbols.", ". "+runCallAnalysis, cfg.ScanLevel.WantSymbols())
default:
main = fmt.Sprintf("depends on %d vulnerable module%s (%s)", l, choose("", "s", l == 1), elemList)
informational := ", but doesn't appear to " + choose("call", "import", cfg.ScanLevel.WantSymbols()) + " any of the vulnerable symbols."
addition = choose(informational, ". "+runCallAnalysis, cfg.ScanLevel.WantPackages())
}
return fmt.Sprintf("Your code %s%s", main, addition)
}
const (
errorLevel = "error"
warningLevel = "warning"
informationalLevel = "note"
)
func level(f *govulncheck.Finding, cfg *govulncheck.Config) string {
fr := f.Trace[0]
switch {
case cfg.ScanLevel.WantSymbols():
if fr.Function != "" {
return errorLevel
}
if fr.Package != "" {
return warningLevel
}
return informationalLevel
case cfg.ScanLevel.WantPackages():
if fr.Package != "" {
return errorLevel
}
return warningLevel
default:
return errorLevel
}
}
func stacks(h *handler, fs []*govulncheck.Finding) []Stack {
if fs[0].Trace[0].Function == "" { // not call level findings
return nil
}
var stacks []Stack
for _, f := range fs {
stacks = append(stacks, stack(h, f))
}
// Sort stacks for deterministic output. We sort by message
// which is effectively sorting by full symbol name. The
// performance should not be an issue here.
sort.SliceStable(stacks, func(i, j int) bool { return stacks[i].Message.Text < stacks[j].Message.Text })
return stacks
}
// stack transforms call stack in f to a sarif stack.
func stack(h *handler, f *govulncheck.Finding) Stack {
trace := f.Trace
top := trace[len(trace)-1] // belongs to top level module
frames := make([]Frame, 0, len(trace)) // must not be nil
for i := len(trace) - 1; i >= 0; i-- { // vulnerable symbol is at the top frame
frame := trace[i]
pos := govulncheck.Position{Line: 1, Column: 1}
if frame.Position != nil {
pos = *frame.Position
}
sf := Frame{
Module: frame.Module + "@" + frame.Version,
Location: Location{Message: Description{Text: symbol(frame)}}, // show the (full) symbol name
}
file, base := fileURIInfo(pos.Filename, top.Module, frame.Module, frame.Version)
if h.cfg.ScanMode != govulncheck.ScanModeBinary {
sf.Location.PhysicalLocation = PhysicalLocation{
ArtifactLocation: ArtifactLocation{
URI: file,
URIBaseID: base,
},
Region: Region{
StartLine: pos.Line,
StartColumn: pos.Column,
},
}
}
frames = append(frames, sf)
}
return Stack{
Frames: frames,
Message: Description{Text: fmt.Sprintf("A call stack for vulnerable function %s", symbol(trace[0]))},
}
}
func codeFlows(h *handler, fs []*govulncheck.Finding) []CodeFlow {
if fs[0].Trace[0].Function == "" { // not call level findings
return nil
}
// group call stacks per symbol. There should
// be one call stack currently per symbol, but
// this might change in the future.
m := make(map[govulncheck.Frame][]*govulncheck.Finding)
for _, f := range fs {
// fr.Position is currently the position
// of the definition of the vuln symbol
fr := *f.Trace[0]
m[fr] = append(m[fr], f)
}
var codeFlows []CodeFlow
for fr, fs := range m {
tfs := threadFlows(h, fs)
codeFlows = append(codeFlows, CodeFlow{
ThreadFlows: tfs,
// TODO: should we instead show the message from govulncheck text output?
Message: Description{Text: fmt.Sprintf("A summarized code flow for vulnerable function %s", symbol(&fr))},
})
}
// Sort flows for deterministic output. We sort by message
// which is effectively sorting by full symbol name. The
// performance should not be an issue here.
sort.SliceStable(codeFlows, func(i, j int) bool { return codeFlows[i].Message.Text < codeFlows[j].Message.Text })
return codeFlows
}
func threadFlows(h *handler, fs []*govulncheck.Finding) []ThreadFlow {
tfs := make([]ThreadFlow, 0, len(fs)) // must not be nil
for _, f := range fs {
trace := traces.Compact(f)
top := trace[len(trace)-1] // belongs to top level module
var tf []ThreadFlowLocation
for i := len(trace) - 1; i >= 0; i-- { // vulnerable symbol is at the top frame
// TODO: should we, similar to govulncheck text output, only
// mention three elements of the compact trace?
frame := trace[i]
pos := govulncheck.Position{Line: 1, Column: 1}
if frame.Position != nil {
pos = *frame.Position
}
tfl := ThreadFlowLocation{
Module: frame.Module + "@" + frame.Version,
Location: Location{Message: Description{Text: symbol(frame)}}, // show the (full) symbol name
}
file, base := fileURIInfo(pos.Filename, top.Module, frame.Module, frame.Version)
if h.cfg.ScanMode != govulncheck.ScanModeBinary {
tfl.Location.PhysicalLocation = PhysicalLocation{
ArtifactLocation: ArtifactLocation{
URI: file,
URIBaseID: base,
},
Region: Region{
StartLine: pos.Line,
StartColumn: pos.Column,
},
}
}
tf = append(tf, tfl)
}
tfs = append(tfs, ThreadFlow{Locations: tf})
}
return tfs
}
func fileURIInfo(filename, top, module, version string) (string, string) {
if top == module {
return filename, SrcRootID
}
if module == internal.GoStdModulePath {
return filename, GoRootID
}
return filepath.ToSlash(filepath.Join(module+"@"+version, filename)), GoModCacheID
}

213
vendor/golang.org/x/vuln/internal/sarif/sarif.go generated vendored Normal file
View File

@@ -0,0 +1,213 @@
// 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 sarif defines Static Analysis Results Interchange Format
// (SARIF) types supported by govulncheck.
//
// The implementation covers the subset of the specification available
// at https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=sarif.
//
// The sarif encoding models govulncheck findings as Results. Each
// Result encodes findings for a unique OSV entry at the most precise
// detected level only. CodeFlows summarize call stacks, similar to govulncheck
// textual output, while Stacks contain call stack information verbatim.
//
// The result Levels are defined by the govulncheck.ScanLevel and the most
// precise level at which the finding was detected. Result error is produced
// when the finding level matches the user desired level of scan precision;
// all other finding levels are then classified as progressively weaker.
// For instance, if the user specified symbol scan level and govulncheck
// detected a use of a vulnerable symbol, then the Result will have error
// Level. If the symbol was not used but its package was imported, then the
// Result Level is warning, and so on.
//
// Each Result is attached to the first line of the go.mod file. Other
// ArtifactLocations are paths relative to their enclosing modules.
// Similar to JSON output format, this makes govulncheck sarif locations
// portable.
//
// The relative paths in PhysicalLocations also come with a URIBaseID offset.
// Paths for the source module analyzed, the Go standard library, and third-party
// dependencies are relative to %SRCROOT%, %GOROOT%, and %GOMODCACHE% offsets,
// resp. We note that the URIBaseID offsets are not explicitly defined in
// the sarif output. It is the clients responsibility to set them to resolve
// paths at their local machines.
//
// All paths use "/" delimiter for portability.
//
// Properties field of a Tool.Driver is a govulncheck.Config used for the
// invocation of govulncheck producing the Results. Properties field of
// a Rule contains information on CVE and GHSA aliases for the corresponding
// rule OSV. Clients can use this information to, say, suppress and filter
// vulnerabilities.
//
// Please see the definition of types below for more information.
package sarif
import "golang.org/x/vuln/internal/govulncheck"
// Log is the top-level SARIF object encoded in UTF-8.
type Log struct {
// Version should always be "2.1.0"
Version string `json:"version,omitempty"`
// Schema should always be "https://json.schemastore.org/sarif-2.1.0.json"
Schema string `json:"$schema,omitempty"`
// Runs describes executions of static analysis tools. For govulncheck,
// there will be only one run object.
Runs []Run `json:"runs,omitempty"`
}
// Run summarizes results of a single invocation of a static analysis tool,
// in this case govulncheck.
type Run struct {
Tool Tool `json:"tool,omitempty"`
// Results contain govulncheck findings. There should be exactly one
// Result per a detected use of an OSV.
Results []Result `json:"results"`
}
// Tool captures information about govulncheck analysis that was run.
type Tool struct {
Driver Driver `json:"driver,omitempty"`
}
// Driver provides details about the govulncheck binary being executed.
type Driver struct {
// Name is "govulncheck"
Name string `json:"name,omitempty"`
// Version is the govulncheck version
Version string `json:"semanticVersion,omitempty"`
// InformationURI points to the description of govulncheck tool
InformationURI string `json:"informationUri,omitempty"`
// Properties are govulncheck run metadata, such as vuln db, Go version, etc.
Properties govulncheck.Config `json:"properties,omitempty"`
Rules []Rule `json:"rules"`
}
// Rule corresponds to the static analysis rule/analyzer that
// produces findings. For govulncheck, rules are OSVs.
type Rule struct {
// ID is OSV.ID
ID string `json:"id,omitempty"`
ShortDescription Description `json:"shortDescription,omitempty"`
FullDescription Description `json:"fullDescription,omitempty"`
Help Description `json:"help,omitempty"`
HelpURI string `json:"helpUri,omitempty"`
// Properties contain OSV.Aliases (CVEs and GHSAs) as tags.
// Consumers of govulncheck SARIF can use these tags to filter
// results.
Properties RuleTags `json:"properties,omitempty"`
}
// RuleTags defines properties.tags.
type RuleTags struct {
Tags []string `json:"tags"`
}
// Description is a text in its raw or markdown form.
type Description struct {
Text string `json:"text,omitempty"`
Markdown string `json:"markdown,omitempty"`
}
// Result is a set of govulncheck findings for an OSV. For call stack
// mode, it will contain call stacks for the OSV. There is exactly
// one Result per detected OSV. Only findings at the most precise
// detected level appear in the Result. For instance, if there are
// symbol findings for an OSV, those findings will be in the Result,
// but not the package and module level findings for the same OSV.
type Result struct {
// RuleID is the Rule.ID/OSV producing the finding.
RuleID string `json:"ruleId,omitempty"`
// Level is one of "error", "warning", and "note".
Level string `json:"level,omitempty"`
// Message explains the overall findings.
Message Description `json:"message,omitempty"`
// Locations to which the findings are associated. Always
// a single location pointing to the first line of the go.mod
// file. The path to the file is "go.mod".
Locations []Location `json:"locations,omitempty"`
// CodeFlows summarize call stacks produced by govulncheck.
CodeFlows []CodeFlow `json:"codeFlows,omitempty"`
// Stacks encode call stacks produced by govulncheck.
Stacks []Stack `json:"stacks,omitempty"`
}
// CodeFlow summarizes a detected offending flow of information in terms of
// code locations. More precisely, it can contain several related information
// flows, keeping them together. In govulncheck, those can be all call stacks
// for, say, a particular symbol or package.
type CodeFlow struct {
// ThreadFlows is effectively a set of related information flows.
ThreadFlows []ThreadFlow `json:"threadFlows"`
Message Description `json:"message,omitempty"`
}
// ThreadFlow encodes an information flow as a sequence of locations.
// For govulncheck, it can encode a call stack.
type ThreadFlow struct {
Locations []ThreadFlowLocation `json:"locations,omitempty"`
}
type ThreadFlowLocation struct {
// Module is module information in the form <module-path>@<version>.
// <version> can be empty when the module version is not known as
// with, say, the source module analyzed.
Module string `json:"module,omitempty"`
// Location also contains a Message field.
Location Location `json:"location,omitempty"`
}
// Stack is a sequence of frames and can encode a govulncheck call stack.
type Stack struct {
Message Description `json:"message,omitempty"`
Frames []Frame `json:"frames"`
}
// Frame is effectively a module location. It can also contain thread and
// parameter info, but those are not needed for govulncheck.
type Frame struct {
// Module is module information in the form <module-path>@<version>.
// <version> can be empty when the module version is not known as
// with, say, the source module analyzed.
Module string `json:"module,omitempty"`
Location Location `json:"location,omitempty"`
}
// Location is currently a physical location annotated with a message.
type Location struct {
PhysicalLocation PhysicalLocation `json:"physicalLocation,omitempty"`
Message Description `json:"message,omitempty"`
}
type PhysicalLocation struct {
ArtifactLocation ArtifactLocation `json:"artifactLocation,omitempty"`
Region Region `json:"region,omitempty"`
}
const (
SrcRootID = "%SRCROOT%"
GoRootID = "%GOROOT%"
GoModCacheID = "%GOMODCACHE%"
)
// ArtifactLocation is a path to an offending file.
type ArtifactLocation struct {
// URI is a path relative to URIBaseID.
URI string `json:"uri,omitempty"`
// URIBaseID is offset for URI, one of %SRCROOT%, %GOROOT%,
// and %GOMODCACHE%.
URIBaseID string `json:"uriBaseId,omitempty"`
}
// Region is a target region within a file.
type Region struct {
StartLine int `json:"startLine,omitempty"`
StartColumn int `json:"startColumn,omitempty"`
EndLine int `json:"endLine,omitempty"`
EndColumn int `json:"endColumn,omitempty"`
}

46
vendor/golang.org/x/vuln/internal/sarif/utils.go generated vendored Normal file
View File

@@ -0,0 +1,46 @@
// 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 sarif
import (
"strings"
"golang.org/x/vuln/internal/govulncheck"
)
func choose(s1, s2 string, cond bool) string {
if cond {
return s1
}
return s2
}
func list(elems []string) string {
l := len(elems)
if l == 0 {
return ""
}
if l == 1 {
return elems[0]
}
cList := strings.Join(elems[:l-1], ", ")
return cList + choose("", ",", l == 2) + " and " + elems[l-1]
}
// symbol is simplified adaptation of internal/scan/symbol.
func symbol(fr *govulncheck.Frame) string {
if fr.Function == "" {
return ""
}
sym := strings.Split(fr.Function, "$")[0]
if fr.Receiver != "" {
sym = fr.Receiver + "." + sym
}
if fr.Package != "" {
sym = fr.Package + "." + sym
}
return sym
}