Initialize module and dependencies
This commit is contained in:
237
vendor/golang.org/x/vuln/internal/vulncheck/binary.go
generated
vendored
Normal file
237
vendor/golang.org/x/vuln/internal/vulncheck/binary.go
generated
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
// Copyright 2021 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 vulncheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/vuln/internal"
|
||||
"golang.org/x/vuln/internal/buildinfo"
|
||||
"golang.org/x/vuln/internal/client"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/semver"
|
||||
)
|
||||
|
||||
// Bin is an abstraction of Go binary containing
|
||||
// minimal information needed by govulncheck.
|
||||
type Bin struct {
|
||||
// Path of the main package.
|
||||
Path string `json:"path,omitempty"`
|
||||
// Main module. When present, it never has empty information.
|
||||
Main *packages.Module `json:"main,omitempty"`
|
||||
Modules []*packages.Module `json:"modules,omitempty"`
|
||||
PkgSymbols []buildinfo.Symbol `json:"pkgSymbols,omitempty"`
|
||||
GoVersion string `json:"goVersion,omitempty"`
|
||||
GOOS string `json:"goos,omitempty"`
|
||||
GOARCH string `json:"goarch,omitempty"`
|
||||
}
|
||||
|
||||
// Binary detects presence of vulnerable symbols in bin and
|
||||
// emits findings to handler.
|
||||
func Binary(ctx context.Context, handler govulncheck.Handler, bin *Bin, cfg *govulncheck.Config, client *client.Client) error {
|
||||
vr, err := binary(ctx, handler, bin, cfg, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cfg.ScanLevel.WantSymbols() {
|
||||
return emitCallFindings(handler, binaryCallstacks(vr))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// binary detects presence of vulnerable symbols in bin.
|
||||
// It does not compute call graphs so the corresponding
|
||||
// info in Result will be empty.
|
||||
func binary(ctx context.Context, handler govulncheck.Handler, bin *Bin, cfg *govulncheck.Config, client *client.Client) (*Result, error) {
|
||||
graph := NewPackageGraph(bin.GoVersion)
|
||||
mods := append(bin.Modules, graph.GetModule(internal.GoStdModulePath))
|
||||
|
||||
if bin.Main != nil {
|
||||
mods = append(mods, bin.Main)
|
||||
}
|
||||
|
||||
graph.AddModules(mods...)
|
||||
|
||||
if err := handler.SBOM(bin.SBOM()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := handler.Progress(&govulncheck.Progress{Message: fetchingVulnsMessage}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mv, err := FetchVulnerabilities(ctx, client, mods)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Emit OSV entries immediately in their raw unfiltered form.
|
||||
if err := emitOSVs(handler, mv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := handler.Progress(&govulncheck.Progress{Message: checkingBinVulnsMessage}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Emit warning message for ancient Go binaries, defined as binaries
|
||||
// built with Go version without support for debug.BuildInfo (< go1.18).
|
||||
if semver.Valid(bin.GoVersion) && semver.Less(bin.GoVersion, "go1.18") {
|
||||
p := &govulncheck.Progress{Message: fmt.Sprintf("warning: binary built with Go version %s, only standard library vulnerabilities will be checked", bin.GoVersion)}
|
||||
if err := handler.Progress(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if bin.GOOS == "" || bin.GOARCH == "" {
|
||||
p := &govulncheck.Progress{Message: fmt.Sprintf("warning: failed to extract build system specification GOOS: %s GOARCH: %s\n", bin.GOOS, bin.GOARCH)}
|
||||
if err := handler.Progress(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
affVulns := affectingVulnerabilities(mv, bin.GOOS, bin.GOARCH)
|
||||
if err := emitModuleFindings(handler, affVulns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !cfg.ScanLevel.WantPackages() || len(affVulns) == 0 {
|
||||
return &Result{}, nil
|
||||
}
|
||||
|
||||
// Group symbols per package to avoid querying affVulns all over again.
|
||||
var pkgSymbols map[string][]string
|
||||
if len(bin.PkgSymbols) == 0 {
|
||||
// The binary exe is stripped. We currently cannot detect inlined
|
||||
// symbols for stripped binaries (see #57764), so we report
|
||||
// vulnerabilities at the go.mod-level precision.
|
||||
pkgSymbols = allKnownVulnerableSymbols(affVulns)
|
||||
} else {
|
||||
pkgSymbols = packagesAndSymbols(bin)
|
||||
}
|
||||
|
||||
impVulns := binImportedVulnPackages(graph, pkgSymbols, affVulns)
|
||||
// Emit information on imported vulnerable packages now to
|
||||
// mimic behavior of source.
|
||||
if err := emitPackageFindings(handler, impVulns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return result immediately if not in symbol mode to mimic the
|
||||
// behavior of source.
|
||||
if !cfg.ScanLevel.WantSymbols() || len(impVulns) == 0 {
|
||||
return &Result{Vulns: impVulns}, nil
|
||||
}
|
||||
|
||||
symVulns := binVulnSymbols(graph, pkgSymbols, affVulns)
|
||||
return &Result{Vulns: symVulns}, nil
|
||||
}
|
||||
|
||||
func packagesAndSymbols(bin *Bin) map[string][]string {
|
||||
pkgSymbols := make(map[string][]string)
|
||||
for _, sym := range bin.PkgSymbols {
|
||||
// If the name of the package is main, we need to expand
|
||||
// it to its full path as that is what vuln db uses.
|
||||
if sym.Pkg == "main" && bin.Path != "" {
|
||||
pkgSymbols[bin.Path] = append(pkgSymbols[bin.Path], sym.Name)
|
||||
} else {
|
||||
pkgSymbols[sym.Pkg] = append(pkgSymbols[sym.Pkg], sym.Name)
|
||||
}
|
||||
}
|
||||
return pkgSymbols
|
||||
}
|
||||
|
||||
func binImportedVulnPackages(graph *PackageGraph, pkgSymbols map[string][]string, affVulns affectingVulns) []*Vuln {
|
||||
var vulns []*Vuln
|
||||
for pkg := range pkgSymbols {
|
||||
for _, osv := range affVulns.ForPackage(internal.UnknownModulePath, pkg) {
|
||||
vuln := &Vuln{
|
||||
OSV: osv,
|
||||
Package: graph.GetPackage(pkg),
|
||||
}
|
||||
vulns = append(vulns, vuln)
|
||||
}
|
||||
}
|
||||
return vulns
|
||||
}
|
||||
|
||||
func binVulnSymbols(graph *PackageGraph, pkgSymbols map[string][]string, affVulns affectingVulns) []*Vuln {
|
||||
var vulns []*Vuln
|
||||
for pkg, symbols := range pkgSymbols {
|
||||
for _, symbol := range symbols {
|
||||
for _, osv := range affVulns.ForSymbol(internal.UnknownModulePath, pkg, symbol) {
|
||||
vuln := &Vuln{
|
||||
OSV: osv,
|
||||
Symbol: symbol,
|
||||
Package: graph.GetPackage(pkg),
|
||||
}
|
||||
vulns = append(vulns, vuln)
|
||||
}
|
||||
}
|
||||
}
|
||||
return vulns
|
||||
}
|
||||
|
||||
// allKnownVulnerableSymbols returns all known vulnerable symbols for packages in graph.
|
||||
// If all symbols of a package are vulnerable, that is modeled as a wild car symbol "<pkg-path>/*".
|
||||
func allKnownVulnerableSymbols(affVulns affectingVulns) map[string][]string {
|
||||
pkgSymbols := make(map[string][]string)
|
||||
for _, mv := range affVulns {
|
||||
for _, osv := range mv.Vulns {
|
||||
for _, affected := range osv.Affected {
|
||||
for _, p := range affected.EcosystemSpecific.Packages {
|
||||
syms := p.Symbols
|
||||
if len(syms) == 0 {
|
||||
// If every symbol of pkg is vulnerable, we would ideally
|
||||
// compute every symbol mentioned in the pkg and then add
|
||||
// Vuln entry for it, just as we do in Source. However,
|
||||
// we don't have code of pkg here and we don't even have
|
||||
// pkg symbols used in stripped binary, so we add a placeholder
|
||||
// symbol.
|
||||
//
|
||||
// Note: this should not affect output of govulncheck since
|
||||
// in binary mode no symbol/call stack information is
|
||||
// communicated back to the user.
|
||||
syms = []string{fmt.Sprintf("%s/*", p.Path)}
|
||||
}
|
||||
|
||||
pkgSymbols[p.Path] = append(pkgSymbols[p.Path], syms...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return pkgSymbols
|
||||
}
|
||||
|
||||
func (bin *Bin) SBOM() (sbom *govulncheck.SBOM) {
|
||||
sbom = &govulncheck.SBOM{}
|
||||
if bin.Main != nil {
|
||||
sbom.Roots = []string{bin.Main.Path}
|
||||
sbom.Modules = append(sbom.Modules, &govulncheck.Module{
|
||||
Path: bin.Main.Path,
|
||||
Version: bin.Main.Version,
|
||||
})
|
||||
}
|
||||
|
||||
sbom.GoVersion = bin.GoVersion
|
||||
for _, mod := range bin.Modules {
|
||||
if mod.Replace != nil {
|
||||
mod = mod.Replace
|
||||
}
|
||||
sbom.Modules = append(sbom.Modules, &govulncheck.Module{
|
||||
Path: mod.Path,
|
||||
Version: mod.Version,
|
||||
})
|
||||
}
|
||||
|
||||
// add stdlib to mirror source mode output
|
||||
sbom.Modules = append(sbom.Modules, &govulncheck.Module{
|
||||
Path: internal.GoStdModulePath,
|
||||
Version: bin.GoVersion,
|
||||
})
|
||||
|
||||
return sbom
|
||||
}
|
||||
54
vendor/golang.org/x/vuln/internal/vulncheck/doc.go
generated
vendored
Normal file
54
vendor/golang.org/x/vuln/internal/vulncheck/doc.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// 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 vulncheck detects uses of known vulnerabilities
|
||||
in Go programs.
|
||||
|
||||
Vulncheck identifies vulnerability uses in Go programs
|
||||
at the level of call graph, package import graph, and module
|
||||
requires graph. For instance, vulncheck identifies which
|
||||
vulnerable functions and methods are transitively called
|
||||
from the program entry points. vulncheck also detects
|
||||
transitively imported packages and required modules that
|
||||
contain known vulnerable functions and methods.
|
||||
|
||||
We recommend using the command line tool [govulncheck] to
|
||||
detect vulnerabilities in your code.
|
||||
|
||||
# Usage
|
||||
|
||||
The two main APIs of vulncheck, [Source] and [Binary], allow vulnerability
|
||||
detection in Go source code and binaries, respectively.
|
||||
|
||||
[Source] accepts a list of [Package] objects, which
|
||||
are a trimmed version of [golang.org/x/tools/go/packages.Package] objects to
|
||||
reduce memory consumption. [Binary] accepts a path to a Go binary file.
|
||||
|
||||
Both [Source] and [Binary] require information about known
|
||||
vulnerabilities in the form of a vulnerability database,
|
||||
specifically a [golang.org/x/vuln/internal/client.Client].
|
||||
The vulnerabilities
|
||||
are modeled using the [golang.org/x/vuln/internal/osv] format.
|
||||
|
||||
# Results
|
||||
|
||||
The results of vulncheck are slices of the call graph, package imports graph,
|
||||
and module requires graph leading to the use of an identified vulnerability.
|
||||
The parts of these graphs not related to any vulnerabilities are omitted.
|
||||
|
||||
The [CallStacks] and [ImportChains] functions search the returned slices for
|
||||
user-friendly representative call stacks and import chains. These call stacks
|
||||
and import chains are provided as examples of vulnerability uses in the client
|
||||
code.
|
||||
|
||||
# Limitations
|
||||
|
||||
There are some limitations with vulncheck. Please see the
|
||||
[documented limitations] for more information.
|
||||
|
||||
[govulncheck]: https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck
|
||||
[documented limitations]: https://go.dev/security/vulncheck#limitations.
|
||||
*/
|
||||
package vulncheck
|
||||
198
vendor/golang.org/x/vuln/internal/vulncheck/emit.go
generated
vendored
Normal file
198
vendor/golang.org/x/vuln/internal/vulncheck/emit.go
generated
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
// 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 vulncheck
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
// emitOSVs emits all OSV vuln entries in modVulns to handler.
|
||||
func emitOSVs(handler govulncheck.Handler, modVulns []*ModVulns) error {
|
||||
for _, mv := range modVulns {
|
||||
for _, v := range mv.Vulns {
|
||||
if err := handler.OSV(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// emitModuleFindings emits module-level findings for vulnerabilities in modVulns.
|
||||
func emitModuleFindings(handler govulncheck.Handler, affVulns affectingVulns) error {
|
||||
for _, vuln := range affVulns {
|
||||
for _, osv := range vuln.Vulns {
|
||||
if err := handler.Finding(&govulncheck.Finding{
|
||||
OSV: osv.ID,
|
||||
FixedVersion: FixedVersion(modPath(vuln.Module), modVersion(vuln.Module), osv.Affected),
|
||||
Trace: []*govulncheck.Frame{frameFromModule(vuln.Module)},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// emitPackageFinding emits package-level findings fod vulnerabilities in vulns.
|
||||
func emitPackageFindings(handler govulncheck.Handler, vulns []*Vuln) error {
|
||||
for _, v := range vulns {
|
||||
if err := handler.Finding(&govulncheck.Finding{
|
||||
OSV: v.OSV.ID,
|
||||
FixedVersion: FixedVersion(modPath(v.Package.Module), modVersion(v.Package.Module), v.OSV.Affected),
|
||||
Trace: []*govulncheck.Frame{frameFromPackage(v.Package)},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// emitCallFindings emits call-level findings for vulnerabilities
|
||||
// that have a call stack in callstacks.
|
||||
func emitCallFindings(handler govulncheck.Handler, callstacks map[*Vuln]CallStack) error {
|
||||
var vulns []*Vuln
|
||||
for v := range callstacks {
|
||||
vulns = append(vulns, v)
|
||||
}
|
||||
|
||||
for _, vuln := range vulns {
|
||||
stack := callstacks[vuln]
|
||||
if stack == nil {
|
||||
continue
|
||||
}
|
||||
fixed := FixedVersion(modPath(vuln.Package.Module), modVersion(vuln.Package.Module), vuln.OSV.Affected)
|
||||
if err := handler.Finding(&govulncheck.Finding{
|
||||
OSV: vuln.OSV.ID,
|
||||
FixedVersion: fixed,
|
||||
Trace: traceFromEntries(stack),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// traceFromEntries creates a sequence of
|
||||
// frames from vcs. Position of a Frame is the
|
||||
// call position of the corresponding stack entry.
|
||||
func traceFromEntries(vcs CallStack) []*govulncheck.Frame {
|
||||
var frames []*govulncheck.Frame
|
||||
for i := len(vcs) - 1; i >= 0; i-- {
|
||||
e := vcs[i]
|
||||
fr := frameFromPackage(e.Function.Package)
|
||||
fr.Function = e.Function.Name
|
||||
fr.Receiver = e.Function.Receiver()
|
||||
isSink := i == (len(vcs) - 1)
|
||||
fr.Position = posFromStackEntry(e, isSink)
|
||||
frames = append(frames, fr)
|
||||
}
|
||||
return frames
|
||||
}
|
||||
|
||||
func posFromStackEntry(e StackEntry, sink bool) *govulncheck.Position {
|
||||
var p *token.Position
|
||||
var f *FuncNode
|
||||
if sink && e.Function != nil && e.Function.Pos != nil {
|
||||
// For sinks, i.e., vulns we take the position
|
||||
// of the symbol.
|
||||
p = e.Function.Pos
|
||||
f = e.Function
|
||||
} else if e.Call != nil && e.Call.Pos != nil {
|
||||
// Otherwise, we take the position of
|
||||
// the call statement.
|
||||
p = e.Call.Pos
|
||||
f = e.Call.Parent
|
||||
}
|
||||
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return &govulncheck.Position{
|
||||
Filename: pathRelativeToMod(p.Filename, f),
|
||||
Offset: p.Offset,
|
||||
Line: p.Line,
|
||||
Column: p.Column,
|
||||
}
|
||||
}
|
||||
|
||||
// pathRelativeToMod computes a version of path
|
||||
// relative to the module of f. If it does not
|
||||
// have all the necessary information, returns
|
||||
// an empty string.
|
||||
//
|
||||
// The returned paths always use slash as separator
|
||||
// so they can work across different platforms.
|
||||
func pathRelativeToMod(path string, f *FuncNode) string {
|
||||
if path == "" || f == nil || f.Package == nil { // sanity
|
||||
return ""
|
||||
}
|
||||
|
||||
mod := f.Package.Module
|
||||
if mod.Replace != nil {
|
||||
mod = mod.Replace // for replace directive
|
||||
}
|
||||
|
||||
modDir := modDirWithVendor(mod.Dir, path, mod.Path)
|
||||
p, err := filepath.Rel(modDir, path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// make sure paths are portable.
|
||||
return filepath.ToSlash(p)
|
||||
}
|
||||
|
||||
// modDirWithVendor returns modDir if modDir is not empty.
|
||||
// Otherwise, the module might be located in the vendor
|
||||
// directory. This function attempts to reconstruct the
|
||||
// vendored module directory from path and module. It
|
||||
// returns an empty string if reconstruction fails.
|
||||
func modDirWithVendor(modDir, path, module string) string {
|
||||
if modDir != "" {
|
||||
return modDir
|
||||
}
|
||||
|
||||
sep := string(os.PathSeparator)
|
||||
vendor := sep + "vendor" + sep
|
||||
vendorIndex := strings.Index(path, vendor)
|
||||
if vendorIndex == -1 {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(path[:vendorIndex], "vendor", filepath.FromSlash(module))
|
||||
}
|
||||
|
||||
func frameFromPackage(pkg *packages.Package) *govulncheck.Frame {
|
||||
fr := &govulncheck.Frame{}
|
||||
if pkg != nil {
|
||||
fr.Module = pkg.Module.Path
|
||||
fr.Version = pkg.Module.Version
|
||||
fr.Package = pkg.PkgPath
|
||||
}
|
||||
if pkg.Module.Replace != nil {
|
||||
fr.Module = pkg.Module.Replace.Path
|
||||
fr.Version = pkg.Module.Replace.Version
|
||||
}
|
||||
return fr
|
||||
}
|
||||
|
||||
func frameFromModule(mod *packages.Module) *govulncheck.Frame {
|
||||
fr := &govulncheck.Frame{
|
||||
Module: mod.Path,
|
||||
Version: mod.Version,
|
||||
}
|
||||
|
||||
if mod.Replace != nil {
|
||||
fr.Module = mod.Replace.Path
|
||||
fr.Version = mod.Replace.Version
|
||||
}
|
||||
|
||||
return fr
|
||||
}
|
||||
56
vendor/golang.org/x/vuln/internal/vulncheck/entries.go
generated
vendored
Normal file
56
vendor/golang.org/x/vuln/internal/vulncheck/entries.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2021 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 vulncheck
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// entryPoints returns functions of topPackages considered entry
|
||||
// points of govulncheck analysis: main, inits, and exported methods
|
||||
// and functions.
|
||||
//
|
||||
// TODO(https://go.dev/issue/57221): currently, entry functions
|
||||
// that are generics are not considered an entry point.
|
||||
func entryPoints(topPackages []*ssa.Package) []*ssa.Function {
|
||||
var entries []*ssa.Function
|
||||
for _, pkg := range topPackages {
|
||||
if pkg.Pkg.Name() == "main" {
|
||||
// for "main" packages the only valid entry points are the "main"
|
||||
// function and any "init#" functions, even if there are other
|
||||
// exported functions or types. similarly to isEntry it should be
|
||||
// safe to ignore the validity of the main or init# signatures,
|
||||
// since the compiler will reject malformed definitions,
|
||||
// and the init function is synthetic
|
||||
entries = append(entries, memberFuncs(pkg.Members["main"], pkg.Prog)...)
|
||||
for name, member := range pkg.Members {
|
||||
if strings.HasPrefix(name, "init#") || name == "init" {
|
||||
entries = append(entries, memberFuncs(member, pkg.Prog)...)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
for _, member := range pkg.Members {
|
||||
for _, f := range memberFuncs(member, pkg.Prog) {
|
||||
if isEntry(f) {
|
||||
entries = append(entries, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
func isEntry(f *ssa.Function) bool {
|
||||
// it should be safe to ignore checking that the signature of the "init" function
|
||||
// is valid, since it is synthetic
|
||||
if f.Name() == "init" && f.Synthetic == "package initializer" {
|
||||
return true
|
||||
}
|
||||
|
||||
return f.Synthetic == "" && f.Object() != nil && f.Object().Exported()
|
||||
}
|
||||
42
vendor/golang.org/x/vuln/internal/vulncheck/fetch.go
generated
vendored
Normal file
42
vendor/golang.org/x/vuln/internal/vulncheck/fetch.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2021 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 vulncheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/vuln/internal/client"
|
||||
)
|
||||
|
||||
// FetchVulnerabilities fetches vulnerabilities that affect the supplied modules.
|
||||
func FetchVulnerabilities(ctx context.Context, c *client.Client, modules []*packages.Module) ([]*ModVulns, error) {
|
||||
mreqs := make([]*client.ModuleRequest, len(modules))
|
||||
for i, mod := range modules {
|
||||
modPath := mod.Path
|
||||
if mod.Replace != nil {
|
||||
modPath = mod.Replace.Path
|
||||
}
|
||||
mreqs[i] = &client.ModuleRequest{
|
||||
Path: modPath,
|
||||
}
|
||||
}
|
||||
resps, err := c.ByModules(ctx, mreqs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching vulnerabilities: %v", err)
|
||||
}
|
||||
var mv []*ModVulns
|
||||
for i, resp := range resps {
|
||||
if len(resp.Entries) == 0 {
|
||||
continue
|
||||
}
|
||||
mv = append(mv, &ModVulns{
|
||||
Module: modules[i],
|
||||
Vulns: resp.Entries,
|
||||
})
|
||||
}
|
||||
return mv, nil
|
||||
}
|
||||
318
vendor/golang.org/x/vuln/internal/vulncheck/packages.go
generated
vendored
Normal file
318
vendor/golang.org/x/vuln/internal/vulncheck/packages.go
generated
vendored
Normal file
@@ -0,0 +1,318 @@
|
||||
// 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 vulncheck
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/vuln/internal"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/semver"
|
||||
)
|
||||
|
||||
// PackageGraph holds a complete module and package graph.
|
||||
// Its primary purpose is to allow fast access to the nodes
|
||||
// by path and make sure all(stdlib) packages have a module.
|
||||
type PackageGraph struct {
|
||||
// topPkgs are top-level packages specified by the user.
|
||||
// Empty in binary mode.
|
||||
topPkgs []*packages.Package
|
||||
modules map[string]*packages.Module // all modules (even replacing ones)
|
||||
packages map[string]*packages.Package // all packages (even dependencies)
|
||||
}
|
||||
|
||||
func NewPackageGraph(goVersion string) *PackageGraph {
|
||||
graph := &PackageGraph{
|
||||
modules: map[string]*packages.Module{},
|
||||
packages: map[string]*packages.Package{},
|
||||
}
|
||||
|
||||
goRoot := ""
|
||||
if out, err := exec.Command("go", "env", "GOROOT").Output(); err == nil {
|
||||
goRoot = strings.TrimSpace(string(out))
|
||||
}
|
||||
stdlibModule := &packages.Module{
|
||||
Path: internal.GoStdModulePath,
|
||||
Version: semver.GoTagToSemver(goVersion),
|
||||
Dir: goRoot,
|
||||
}
|
||||
graph.AddModules(stdlibModule)
|
||||
return graph
|
||||
}
|
||||
|
||||
func (g *PackageGraph) TopPkgs() []*packages.Package {
|
||||
return g.topPkgs
|
||||
}
|
||||
|
||||
// DepPkgs returns the number of packages that graph.TopPkgs()
|
||||
// strictly depend on. This does not include topPkgs even if
|
||||
// they are dependency of each other.
|
||||
func (g *PackageGraph) DepPkgs() []*packages.Package {
|
||||
topPkgs := g.TopPkgs()
|
||||
tops := make(map[string]bool)
|
||||
depPkgs := make(map[string]*packages.Package)
|
||||
|
||||
for _, t := range topPkgs {
|
||||
tops[t.PkgPath] = true
|
||||
}
|
||||
|
||||
var visit func(*packages.Package, bool)
|
||||
visit = func(p *packages.Package, top bool) {
|
||||
path := p.PkgPath
|
||||
if _, ok := depPkgs[path]; ok {
|
||||
return
|
||||
}
|
||||
if tops[path] && !top {
|
||||
// A top package that is a dependency
|
||||
// will not be in depPkgs, so we skip
|
||||
// reiterating on it here.
|
||||
return
|
||||
}
|
||||
|
||||
// We don't count a top-level package as
|
||||
// a dependency even when they are used
|
||||
// as a dependent package.
|
||||
if !tops[path] {
|
||||
depPkgs[path] = p
|
||||
}
|
||||
|
||||
for _, d := range p.Imports {
|
||||
visit(d, false)
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range topPkgs {
|
||||
visit(t, true)
|
||||
}
|
||||
|
||||
var deps []*packages.Package
|
||||
for _, d := range depPkgs {
|
||||
deps = append(deps, g.GetPackage(d.PkgPath))
|
||||
}
|
||||
return deps
|
||||
}
|
||||
|
||||
func (g *PackageGraph) Modules() []*packages.Module {
|
||||
var mods []*packages.Module
|
||||
for _, m := range g.modules {
|
||||
mods = append(mods, m)
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
// AddModules adds the modules and any replace modules provided.
|
||||
// It will ignore modules that have duplicate paths to ones the
|
||||
// graph already holds.
|
||||
func (g *PackageGraph) AddModules(mods ...*packages.Module) {
|
||||
for _, mod := range mods {
|
||||
if _, found := g.modules[mod.Path]; found {
|
||||
//TODO: check duplicates are okay?
|
||||
continue
|
||||
}
|
||||
g.modules[mod.Path] = mod
|
||||
if mod.Replace != nil {
|
||||
g.AddModules(mod.Replace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetModule gets module at path if one exists. Otherwise,
|
||||
// it creates a module and returns it.
|
||||
func (g *PackageGraph) GetModule(path string) *packages.Module {
|
||||
if mod, ok := g.modules[path]; ok {
|
||||
return mod
|
||||
}
|
||||
mod := &packages.Module{
|
||||
Path: path,
|
||||
Version: "",
|
||||
}
|
||||
g.AddModules(mod)
|
||||
return mod
|
||||
}
|
||||
|
||||
// AddPackages adds the packages and their full graph of imported packages.
|
||||
// It also adds the modules of the added packages. It will ignore packages
|
||||
// that have duplicate paths to ones the graph already holds.
|
||||
func (g *PackageGraph) AddPackages(pkgs ...*packages.Package) {
|
||||
for _, pkg := range pkgs {
|
||||
if _, found := g.packages[pkg.PkgPath]; found {
|
||||
//TODO: check duplicates are okay?
|
||||
continue
|
||||
}
|
||||
g.packages[pkg.PkgPath] = pkg
|
||||
g.fixupPackage(pkg)
|
||||
for _, child := range pkg.Imports {
|
||||
g.AddPackages(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fixupPackage adds the module of pkg, if any, to the set
|
||||
// of all modules in g. If packages is not assigned a module
|
||||
// (likely stdlib package), a module set for pkg.
|
||||
func (g *PackageGraph) fixupPackage(pkg *packages.Package) {
|
||||
if pkg.Module != nil {
|
||||
g.AddModules(pkg.Module)
|
||||
return
|
||||
}
|
||||
pkg.Module = g.findModule(pkg.PkgPath)
|
||||
}
|
||||
|
||||
// findModule finds a module for package.
|
||||
// It does a longest prefix search amongst the existing modules, if that does
|
||||
// not find anything, it returns the "unknown" module.
|
||||
func (g *PackageGraph) findModule(pkgPath string) *packages.Module {
|
||||
//TODO: better stdlib test
|
||||
if IsStdPackage(pkgPath) {
|
||||
return g.GetModule(internal.GoStdModulePath)
|
||||
}
|
||||
for _, m := range g.modules {
|
||||
//TODO: not first match, best match...
|
||||
if pkgPath == m.Path || strings.HasPrefix(pkgPath, m.Path+"/") {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return g.GetModule(internal.UnknownModulePath)
|
||||
}
|
||||
|
||||
// GetPackage returns the package matching the path.
|
||||
// If the graph does not already know about the package, a new one is added.
|
||||
func (g *PackageGraph) GetPackage(path string) *packages.Package {
|
||||
if pkg, ok := g.packages[path]; ok {
|
||||
return pkg
|
||||
}
|
||||
pkg := &packages.Package{
|
||||
PkgPath: path,
|
||||
}
|
||||
g.AddPackages(pkg)
|
||||
return pkg
|
||||
}
|
||||
|
||||
// LoadPackages loads the packages specified by the patterns into the graph.
|
||||
// See golang.org/x/tools/go/packages.Load for details of how it works.
|
||||
func (g *PackageGraph) LoadPackagesAndMods(cfg *packages.Config, tags []string, patterns []string, wantSymbols bool) error {
|
||||
if len(tags) > 0 {
|
||||
cfg.BuildFlags = []string{fmt.Sprintf("-tags=%s", strings.Join(tags, ","))}
|
||||
}
|
||||
|
||||
addLoadMode(cfg, wantSymbols)
|
||||
|
||||
pkgs, err := packages.Load(cfg, patterns...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var perrs []packages.Error
|
||||
packages.Visit(pkgs, nil, func(p *packages.Package) {
|
||||
perrs = append(perrs, p.Errors...)
|
||||
})
|
||||
if len(perrs) > 0 {
|
||||
err = &packageError{perrs}
|
||||
}
|
||||
|
||||
// Add all packages, top-level ones and their imports.
|
||||
// This will also add their respective modules.
|
||||
g.AddPackages(pkgs...)
|
||||
|
||||
// save top-level packages
|
||||
for _, p := range pkgs {
|
||||
g.topPkgs = append(g.topPkgs, g.GetPackage(p.PkgPath))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func addLoadMode(cfg *packages.Config, wantSymbols bool) {
|
||||
cfg.Mode |=
|
||||
packages.NeedModule |
|
||||
packages.NeedName |
|
||||
packages.NeedDeps |
|
||||
packages.NeedImports
|
||||
if wantSymbols {
|
||||
cfg.Mode |= packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo
|
||||
}
|
||||
}
|
||||
|
||||
// packageError contains errors from loading a set of packages.
|
||||
type packageError struct {
|
||||
Errors []packages.Error
|
||||
}
|
||||
|
||||
func (e *packageError) Error() string {
|
||||
var b strings.Builder
|
||||
fmt.Fprintln(&b, "\nThere are errors with the provided package patterns:")
|
||||
fmt.Fprintln(&b, "")
|
||||
for _, e := range e.Errors {
|
||||
fmt.Fprintln(&b, e)
|
||||
}
|
||||
fmt.Fprintln(&b, "\nFor details on package patterns, see https://pkg.go.dev/cmd/go#hdr-Package_lists_and_patterns.")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (g *PackageGraph) SBOM() *govulncheck.SBOM {
|
||||
getMod := func(mod *packages.Module) *govulncheck.Module {
|
||||
if mod.Replace != nil {
|
||||
return &govulncheck.Module{
|
||||
Path: mod.Replace.Path,
|
||||
Version: mod.Replace.Version,
|
||||
}
|
||||
}
|
||||
|
||||
return &govulncheck.Module{
|
||||
Path: mod.Path,
|
||||
Version: mod.Version,
|
||||
}
|
||||
}
|
||||
|
||||
var roots []string
|
||||
rootMods := make(map[string]*govulncheck.Module)
|
||||
for _, pkg := range g.TopPkgs() {
|
||||
roots = append(roots, pkg.PkgPath)
|
||||
mod := getMod(pkg.Module)
|
||||
rootMods[mod.Path] = mod
|
||||
}
|
||||
|
||||
// Govulncheck attempts to put the modules that correspond to the matched package patterns (i.e. the root modules)
|
||||
// at the beginning of the SBOM.Modules message.
|
||||
// Note: This does not guarantee that the first element is the root module.
|
||||
var topMods, depMods []*govulncheck.Module
|
||||
var goVersion string
|
||||
for _, mod := range g.Modules() {
|
||||
mod := getMod(mod)
|
||||
|
||||
if mod.Path == internal.GoStdModulePath {
|
||||
goVersion = semver.SemverToGoTag(mod.Version)
|
||||
}
|
||||
|
||||
// if the mod is not associated with a root package, add it to depMods
|
||||
if rootMods[mod.Path] == nil {
|
||||
depMods = append(depMods, mod)
|
||||
}
|
||||
}
|
||||
|
||||
for _, mod := range rootMods {
|
||||
topMods = append(topMods, mod)
|
||||
}
|
||||
// Sort for deterministic output
|
||||
sortMods(topMods)
|
||||
sortMods(depMods)
|
||||
|
||||
mods := append(topMods, depMods...)
|
||||
|
||||
return &govulncheck.SBOM{
|
||||
GoVersion: goVersion,
|
||||
Modules: mods,
|
||||
Roots: roots,
|
||||
}
|
||||
}
|
||||
|
||||
// Sorts modules alphabetically by path.
|
||||
func sortMods(mods []*govulncheck.Module) {
|
||||
slices.SortFunc(mods, func(a, b *govulncheck.Module) int {
|
||||
return strings.Compare(a.Path, b.Path)
|
||||
})
|
||||
}
|
||||
46
vendor/golang.org/x/vuln/internal/vulncheck/slicing.go
generated
vendored
Normal file
46
vendor/golang.org/x/vuln/internal/vulncheck/slicing.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2021 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 vulncheck
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// forwardSlice computes the transitive closure of functions forward reachable
|
||||
// via calls in cg or referred to in an instruction starting from `sources`.
|
||||
func forwardSlice(sources map[*ssa.Function]bool, cg *callgraph.Graph) map[*ssa.Function]bool {
|
||||
seen := make(map[*ssa.Function]bool)
|
||||
var visit func(f *ssa.Function)
|
||||
visit = func(f *ssa.Function) {
|
||||
if seen[f] {
|
||||
return
|
||||
}
|
||||
seen[f] = true
|
||||
|
||||
if n := cg.Nodes[f]; n != nil {
|
||||
for _, e := range n.Out {
|
||||
if e.Site != nil {
|
||||
visit(e.Callee.Func)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var buf [10]*ssa.Value // avoid alloc in common case
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
for _, op := range instr.Operands(buf[:0]) {
|
||||
if fn, ok := (*op).(*ssa.Function); ok {
|
||||
visit(fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for source := range sources {
|
||||
visit(source)
|
||||
}
|
||||
return seen
|
||||
}
|
||||
312
vendor/golang.org/x/vuln/internal/vulncheck/source.go
generated
vendored
Normal file
312
vendor/golang.org/x/vuln/internal/vulncheck/source.go
generated
vendored
Normal file
@@ -0,0 +1,312 @@
|
||||
// Copyright 2021 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 vulncheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/vuln/internal/client"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
)
|
||||
|
||||
// Source detects vulnerabilities in pkgs and emits the findings to handler.
|
||||
func Source(ctx context.Context, handler govulncheck.Handler, cfg *govulncheck.Config, client *client.Client, graph *PackageGraph) error {
|
||||
vr, err := source(ctx, handler, cfg, client, graph)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.ScanLevel.WantSymbols() {
|
||||
return emitCallFindings(handler, sourceCallstacks(vr))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// source detects vulnerabilities in packages. It emits findings to handler
|
||||
// and produces a Result that contains info on detected vulnerabilities.
|
||||
//
|
||||
// Assumes that pkgs are non-empty and belong to the same program.
|
||||
func source(ctx context.Context, handler govulncheck.Handler, cfg *govulncheck.Config, client *client.Client, graph *PackageGraph) (*Result, error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
// If we are building the callgraph, build ssa and the callgraph in parallel
|
||||
// with fetching vulnerabilities. If the vulns set is empty, return without
|
||||
// waiting for SSA construction or callgraph to finish.
|
||||
var (
|
||||
wg sync.WaitGroup // guards entries, cg, and buildErr
|
||||
entries []*ssa.Function
|
||||
cg *callgraph.Graph
|
||||
buildErr error
|
||||
)
|
||||
if cfg.ScanLevel.WantSymbols() {
|
||||
fset := graph.TopPkgs()[0].Fset
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
prog, ssaPkgs := buildSSA(graph.TopPkgs(), fset)
|
||||
entries = entryPoints(ssaPkgs)
|
||||
cg, buildErr = callGraph(ctx, prog, entries)
|
||||
}()
|
||||
}
|
||||
|
||||
if err := handler.SBOM(graph.SBOM()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := handler.Progress(&govulncheck.Progress{Message: fetchingVulnsMessage}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mv, err := FetchVulnerabilities(ctx, client, graph.Modules())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Emit OSV entries immediately in their raw unfiltered form.
|
||||
if err := emitOSVs(handler, mv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := handler.Progress(&govulncheck.Progress{Message: checkingSrcVulnsMessage}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
affVulns := affectingVulnerabilities(mv, "", "")
|
||||
if err := emitModuleFindings(handler, affVulns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !cfg.ScanLevel.WantPackages() || len(affVulns) == 0 {
|
||||
return &Result{}, nil
|
||||
}
|
||||
|
||||
impVulns := importedVulnPackages(affVulns, graph)
|
||||
// Emit information on imported vulnerable packages now as
|
||||
// call graph computation might take a while.
|
||||
if err := emitPackageFindings(handler, impVulns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return result immediately if not in symbol mode or
|
||||
// if there are no vulnerabilities imported.
|
||||
if !cfg.ScanLevel.WantSymbols() || len(impVulns) == 0 {
|
||||
return &Result{Vulns: impVulns}, nil
|
||||
}
|
||||
|
||||
wg.Wait() // wait for build to finish
|
||||
if buildErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entryFuncs, callVulns := calledVulnSymbols(entries, affVulns, cg, graph)
|
||||
return &Result{EntryFunctions: entryFuncs, Vulns: callVulns}, nil
|
||||
}
|
||||
|
||||
// importedVulnPackages detects imported vulnerable packages.
|
||||
func importedVulnPackages(affVulns affectingVulns, graph *PackageGraph) []*Vuln {
|
||||
var vulns []*Vuln
|
||||
analyzed := make(map[*packages.Package]bool) // skip analyzing the same package multiple times
|
||||
var vulnImports func(pkg *packages.Package)
|
||||
vulnImports = func(pkg *packages.Package) {
|
||||
if analyzed[pkg] {
|
||||
return
|
||||
}
|
||||
|
||||
osvs := affVulns.ForPackage(pkgModPath(pkg), pkg.PkgPath)
|
||||
// Create Vuln entry for each OSV entry for pkg.
|
||||
for _, osv := range osvs {
|
||||
vuln := &Vuln{
|
||||
OSV: osv,
|
||||
Package: graph.GetPackage(pkg.PkgPath),
|
||||
}
|
||||
vulns = append(vulns, vuln)
|
||||
}
|
||||
|
||||
analyzed[pkg] = true
|
||||
for _, imp := range pkg.Imports {
|
||||
vulnImports(imp)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pkg := range graph.TopPkgs() {
|
||||
vulnImports(pkg)
|
||||
}
|
||||
return vulns
|
||||
}
|
||||
|
||||
// calledVulnSymbols detects vuln symbols transitively reachable from sources
|
||||
// via call graph cg.
|
||||
//
|
||||
// A slice of call graph is computed related to the reachable vulnerabilities. Each
|
||||
// reachable Vuln has attached FuncNode that can be upward traversed to the entry points.
|
||||
// Entry points that reach the vulnerable symbols are also returned.
|
||||
func calledVulnSymbols(sources []*ssa.Function, affVulns affectingVulns, cg *callgraph.Graph, graph *PackageGraph) ([]*FuncNode, []*Vuln) {
|
||||
sinksWithVulns := vulnFuncs(cg, affVulns, graph)
|
||||
|
||||
// Compute call graph backwards reachable
|
||||
// from vulnerable functions and methods.
|
||||
var sinks []*callgraph.Node
|
||||
for n := range sinksWithVulns {
|
||||
sinks = append(sinks, n)
|
||||
}
|
||||
bcg := callGraphSlice(sinks, false)
|
||||
|
||||
// Interesect backwards call graph with forward
|
||||
// reachable graph to remove redundant edges.
|
||||
var filteredSources []*callgraph.Node
|
||||
for _, e := range sources {
|
||||
if n, ok := bcg.Nodes[e]; ok {
|
||||
filteredSources = append(filteredSources, n)
|
||||
}
|
||||
}
|
||||
fcg := callGraphSlice(filteredSources, true)
|
||||
|
||||
// Get the sinks that are in fact reachable from entry points.
|
||||
filteredSinks := make(map[*callgraph.Node][]*osv.Entry)
|
||||
for n, vs := range sinksWithVulns {
|
||||
if fn, ok := fcg.Nodes[n.Func]; ok {
|
||||
filteredSinks[fn] = vs
|
||||
}
|
||||
}
|
||||
|
||||
// Transform the resulting call graph slice into
|
||||
// vulncheck representation.
|
||||
return vulnCallGraph(filteredSources, filteredSinks, graph)
|
||||
}
|
||||
|
||||
// callGraphSlice computes a slice of callgraph beginning at starts
|
||||
// in the direction (forward/backward) controlled by forward flag.
|
||||
func callGraphSlice(starts []*callgraph.Node, forward bool) *callgraph.Graph {
|
||||
g := &callgraph.Graph{Nodes: make(map[*ssa.Function]*callgraph.Node)}
|
||||
|
||||
visited := make(map[*callgraph.Node]bool)
|
||||
var visit func(*callgraph.Node)
|
||||
visit = func(n *callgraph.Node) {
|
||||
if visited[n] {
|
||||
return
|
||||
}
|
||||
visited[n] = true
|
||||
|
||||
var edges []*callgraph.Edge
|
||||
if forward {
|
||||
edges = n.Out
|
||||
} else {
|
||||
edges = n.In
|
||||
}
|
||||
|
||||
for _, edge := range edges {
|
||||
nCallee := g.CreateNode(edge.Callee.Func)
|
||||
nCaller := g.CreateNode(edge.Caller.Func)
|
||||
callgraph.AddEdge(nCaller, edge.Site, nCallee)
|
||||
|
||||
if forward {
|
||||
visit(edge.Callee)
|
||||
} else {
|
||||
visit(edge.Caller)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range starts {
|
||||
visit(s)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
// vulnCallGraph creates vulnerability call graph in terms of sources and sinks.
|
||||
func vulnCallGraph(sources []*callgraph.Node, sinks map[*callgraph.Node][]*osv.Entry, graph *PackageGraph) ([]*FuncNode, []*Vuln) {
|
||||
var entries []*FuncNode
|
||||
var vulns []*Vuln
|
||||
nodes := make(map[*ssa.Function]*FuncNode)
|
||||
|
||||
// First create entries and sinks and store relevant information.
|
||||
for _, s := range sources {
|
||||
fn := createNode(nodes, s.Func, graph)
|
||||
entries = append(entries, fn)
|
||||
}
|
||||
|
||||
for s, osvs := range sinks {
|
||||
f := s.Func
|
||||
funNode := createNode(nodes, s.Func, graph)
|
||||
|
||||
// Populate CallSink field for each detected vuln symbol.
|
||||
for _, osv := range osvs {
|
||||
vulns = append(vulns, calledVuln(funNode, osv, dbFuncName(f), funNode.Package))
|
||||
}
|
||||
}
|
||||
|
||||
visited := make(map[*callgraph.Node]bool)
|
||||
var visit func(*callgraph.Node)
|
||||
visit = func(n *callgraph.Node) {
|
||||
if visited[n] {
|
||||
return
|
||||
}
|
||||
visited[n] = true
|
||||
|
||||
for _, edge := range n.In {
|
||||
nCallee := createNode(nodes, edge.Callee.Func, graph)
|
||||
nCaller := createNode(nodes, edge.Caller.Func, graph)
|
||||
|
||||
call := edge.Site
|
||||
cs := &CallSite{
|
||||
Parent: nCaller,
|
||||
Name: call.Common().Value.Name(),
|
||||
RecvType: callRecvType(call),
|
||||
Resolved: resolved(call),
|
||||
Pos: instrPosition(call),
|
||||
}
|
||||
nCallee.CallSites = append(nCallee.CallSites, cs)
|
||||
|
||||
visit(edge.Caller)
|
||||
}
|
||||
}
|
||||
|
||||
for s := range sinks {
|
||||
visit(s)
|
||||
}
|
||||
return entries, vulns
|
||||
}
|
||||
|
||||
// vulnFuncs returns vulnerability information for vulnerable functions in cg.
|
||||
func vulnFuncs(cg *callgraph.Graph, affVulns affectingVulns, graph *PackageGraph) map[*callgraph.Node][]*osv.Entry {
|
||||
m := make(map[*callgraph.Node][]*osv.Entry)
|
||||
for f, n := range cg.Nodes {
|
||||
p := pkgPath(f)
|
||||
vulns := affVulns.ForSymbol(pkgModPath(graph.GetPackage(p)), p, dbFuncName(f))
|
||||
if len(vulns) > 0 {
|
||||
m[n] = vulns
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func createNode(nodes map[*ssa.Function]*FuncNode, f *ssa.Function, graph *PackageGraph) *FuncNode {
|
||||
if fn, ok := nodes[f]; ok {
|
||||
return fn
|
||||
}
|
||||
fn := &FuncNode{
|
||||
Name: f.Name(),
|
||||
Package: graph.GetPackage(pkgPath(f)),
|
||||
RecvType: funcRecvType(f),
|
||||
Pos: funcPosition(f),
|
||||
}
|
||||
nodes[f] = fn
|
||||
return fn
|
||||
}
|
||||
|
||||
func calledVuln(call *FuncNode, osv *osv.Entry, symbol string, pkg *packages.Package) *Vuln {
|
||||
return &Vuln{
|
||||
Symbol: symbol,
|
||||
Package: pkg,
|
||||
OSV: osv,
|
||||
CallSink: call,
|
||||
}
|
||||
}
|
||||
346
vendor/golang.org/x/vuln/internal/vulncheck/utils.go
generated
vendored
Normal file
346
vendor/golang.org/x/vuln/internal/vulncheck/utils.go
generated
vendored
Normal file
@@ -0,0 +1,346 @@
|
||||
// Copyright 2021 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 vulncheck
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/callgraph/cha"
|
||||
"golang.org/x/tools/go/callgraph/vta"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"golang.org/x/vuln/internal"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
"golang.org/x/vuln/internal/semver"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// buildSSA creates an ssa representation for pkgs. Returns
|
||||
// the ssa program encapsulating the packages and top level
|
||||
// ssa packages corresponding to pkgs.
|
||||
func buildSSA(pkgs []*packages.Package, fset *token.FileSet) (*ssa.Program, []*ssa.Package) {
|
||||
prog := ssa.NewProgram(fset, ssa.InstantiateGenerics)
|
||||
|
||||
imports := make(map[*packages.Package]*ssa.Package)
|
||||
var createImports func(map[string]*packages.Package)
|
||||
createImports = func(pkgs map[string]*packages.Package) {
|
||||
for _, p := range pkgs {
|
||||
if _, ok := imports[p]; !ok {
|
||||
i := prog.CreatePackage(p.Types, p.Syntax, p.TypesInfo, true)
|
||||
imports[p] = i
|
||||
createImports(p.Imports)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, tp := range pkgs {
|
||||
createImports(tp.Imports)
|
||||
}
|
||||
|
||||
var ssaPkgs []*ssa.Package
|
||||
for _, tp := range pkgs {
|
||||
if sp, ok := imports[tp]; ok {
|
||||
ssaPkgs = append(ssaPkgs, sp)
|
||||
} else {
|
||||
sp := prog.CreatePackage(tp.Types, tp.Syntax, tp.TypesInfo, false)
|
||||
ssaPkgs = append(ssaPkgs, sp)
|
||||
}
|
||||
}
|
||||
prog.Build()
|
||||
return prog, ssaPkgs
|
||||
}
|
||||
|
||||
// callGraph builds a call graph of prog based on VTA analysis.
|
||||
func callGraph(ctx context.Context, prog *ssa.Program, entries []*ssa.Function) (*callgraph.Graph, error) {
|
||||
entrySlice := make(map[*ssa.Function]bool)
|
||||
for _, e := range entries {
|
||||
entrySlice[e] = true
|
||||
}
|
||||
|
||||
if err := ctx.Err(); err != nil { // cancelled?
|
||||
return nil, err
|
||||
}
|
||||
initial := cha.CallGraph(prog)
|
||||
|
||||
fslice := forwardSlice(entrySlice, initial)
|
||||
if err := ctx.Err(); err != nil { // cancelled?
|
||||
return nil, err
|
||||
}
|
||||
vtaCg := vta.CallGraph(fslice, initial)
|
||||
|
||||
// Repeat the process once more, this time using
|
||||
// the produced VTA call graph as the base graph.
|
||||
fslice = forwardSlice(entrySlice, vtaCg)
|
||||
if err := ctx.Err(); err != nil { // cancelled?
|
||||
return nil, err
|
||||
}
|
||||
cg := vta.CallGraph(fslice, vtaCg)
|
||||
cg.DeleteSyntheticNodes()
|
||||
return cg, nil
|
||||
}
|
||||
|
||||
// dbTypeFormat formats the name of t according how types
|
||||
// are encoded in vulnerability database:
|
||||
// - pointer designation * is skipped
|
||||
// - full path prefix is skipped as well
|
||||
func dbTypeFormat(t types.Type) string {
|
||||
switch tt := t.(type) {
|
||||
case *types.Pointer:
|
||||
return dbTypeFormat(tt.Elem())
|
||||
case *types.Named:
|
||||
return tt.Obj().Name()
|
||||
default:
|
||||
return types.TypeString(t, func(p *types.Package) string { return "" })
|
||||
}
|
||||
}
|
||||
|
||||
// dbFuncName computes a function name consistent with the namings used in vulnerability
|
||||
// databases. Effectively, a qualified name of a function local to its enclosing package.
|
||||
// If a receiver is a pointer, this information is not encoded in the resulting name. If
|
||||
// a function has type argument/parameter, this information is omitted. The name of
|
||||
// anonymous functions is simply "". The function names are unique subject to the enclosing
|
||||
// package, but not globally.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// func (a A) foo (...) {...} -> A.foo
|
||||
// func foo(...) {...} -> foo
|
||||
// func (b *B) bar (...) {...} -> B.bar
|
||||
// func (c C[T]) do(...) {...} -> C.do
|
||||
func dbFuncName(f *ssa.Function) string {
|
||||
selectBound := func(f *ssa.Function) types.Type {
|
||||
// If f is a "bound" function introduced by ssa for a given type, return the type.
|
||||
// When "f" is a "bound" function, it will have 1 free variable of that type within
|
||||
// the function. This is subject to change when ssa changes.
|
||||
if len(f.FreeVars) == 1 && strings.HasPrefix(f.Synthetic, "bound ") {
|
||||
return f.FreeVars[0].Type()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
selectThunk := func(f *ssa.Function) types.Type {
|
||||
// If f is a "thunk" function introduced by ssa for a given type, return the type.
|
||||
// When "f" is a "thunk" function, the first parameter will have that type within
|
||||
// the function. This is subject to change when ssa changes.
|
||||
params := f.Signature.Params() // params.Len() == 1 then params != nil.
|
||||
if strings.HasPrefix(f.Synthetic, "thunk ") && params.Len() >= 1 {
|
||||
if first := params.At(0); first != nil {
|
||||
return first.Type()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var qprefix string
|
||||
if recv := f.Signature.Recv(); recv != nil {
|
||||
qprefix = dbTypeFormat(recv.Type())
|
||||
} else if btype := selectBound(f); btype != nil {
|
||||
qprefix = dbTypeFormat(btype)
|
||||
} else if ttype := selectThunk(f); ttype != nil {
|
||||
qprefix = dbTypeFormat(ttype)
|
||||
}
|
||||
|
||||
if qprefix == "" {
|
||||
return funcName(f)
|
||||
}
|
||||
return qprefix + "." + funcName(f)
|
||||
}
|
||||
|
||||
// funcName returns the name of the ssa function f.
|
||||
// It is f.Name() without additional type argument
|
||||
// information in case of generics.
|
||||
func funcName(f *ssa.Function) string {
|
||||
n, _, _ := strings.Cut(f.Name(), "[")
|
||||
return n
|
||||
}
|
||||
|
||||
// memberFuncs returns functions associated with the `member`:
|
||||
// 1) `member` itself if `member` is a function
|
||||
// 2) `member` methods if `member` is a type
|
||||
// 3) empty list otherwise
|
||||
func memberFuncs(member ssa.Member, prog *ssa.Program) []*ssa.Function {
|
||||
switch t := member.(type) {
|
||||
case *ssa.Type:
|
||||
methods := typeutil.IntuitiveMethodSet(t.Type(), &prog.MethodSets)
|
||||
var funcs []*ssa.Function
|
||||
for _, m := range methods {
|
||||
if f := prog.MethodValue(m); f != nil {
|
||||
funcs = append(funcs, f)
|
||||
}
|
||||
}
|
||||
return funcs
|
||||
case *ssa.Function:
|
||||
return []*ssa.Function{t}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// funcPosition gives the position of `f`. Returns empty token.Position
|
||||
// if no file information on `f` is available.
|
||||
func funcPosition(f *ssa.Function) *token.Position {
|
||||
pos := f.Prog.Fset.Position(f.Pos())
|
||||
return &pos
|
||||
}
|
||||
|
||||
// instrPosition gives the position of `instr`. Returns empty token.Position
|
||||
// if no file information on `instr` is available.
|
||||
func instrPosition(instr ssa.Instruction) *token.Position {
|
||||
pos := instr.Parent().Prog.Fset.Position(instr.Pos())
|
||||
return &pos
|
||||
}
|
||||
|
||||
func resolved(call ssa.CallInstruction) bool {
|
||||
if call == nil {
|
||||
return true
|
||||
}
|
||||
return call.Common().StaticCallee() != nil
|
||||
}
|
||||
|
||||
func callRecvType(call ssa.CallInstruction) string {
|
||||
if !call.Common().IsInvoke() {
|
||||
return ""
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
types.WriteType(buf, call.Common().Value.Type(), nil)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func funcRecvType(f *ssa.Function) string {
|
||||
v := f.Signature.Recv()
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
types.WriteType(buf, v.Type(), nil)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func FixedVersion(modulePath, version string, affected []osv.Affected) string {
|
||||
fixed := earliestValidFix(modulePath, version, affected)
|
||||
// Add "v" prefix if one does not exist. moduleVersionString
|
||||
// will later on replace it with "go" if needed.
|
||||
if fixed != "" && !strings.HasPrefix(fixed, "v") {
|
||||
fixed = "v" + fixed
|
||||
}
|
||||
return fixed
|
||||
}
|
||||
|
||||
// earliestValidFix returns the earliest fix for version of modulePath that
|
||||
// itself is not vulnerable in affected.
|
||||
//
|
||||
// Suppose we have a version "v1.0.0" and we use {...} to denote different
|
||||
// affected regions. Assume for simplicity that all affected apply to the
|
||||
// same input modulePath.
|
||||
//
|
||||
// {[v0.1.0, v0.1.9), [v1.0.0, v2.0.0)} -> v2.0.0
|
||||
// {[v1.0.0, v1.5.0), [v2.0.0, v2.1.0}, {[v1.4.0, v1.6.0)} -> v2.1.0
|
||||
func earliestValidFix(modulePath, version string, affected []osv.Affected) string {
|
||||
var moduleAffected []osv.Affected
|
||||
for _, a := range affected {
|
||||
if a.Module.Path == modulePath {
|
||||
moduleAffected = append(moduleAffected, a)
|
||||
}
|
||||
}
|
||||
|
||||
vFixes := validFixes(version, moduleAffected)
|
||||
for _, fix := range vFixes {
|
||||
if !fixNegated(fix, moduleAffected) {
|
||||
return fix
|
||||
}
|
||||
}
|
||||
return ""
|
||||
|
||||
}
|
||||
|
||||
// validFixes computes all fixes for version in affected and
|
||||
// returns them sorted increasingly. Assumes that all affected
|
||||
// apply to the same module.
|
||||
func validFixes(version string, affected []osv.Affected) []string {
|
||||
var fixes []string
|
||||
for _, a := range affected {
|
||||
for _, r := range a.Ranges {
|
||||
if r.Type != osv.RangeTypeSemver {
|
||||
continue
|
||||
}
|
||||
for _, e := range r.Events {
|
||||
fix := e.Fixed
|
||||
if fix != "" && semver.Less(version, fix) {
|
||||
fixes = append(fixes, fix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.SliceStable(fixes, func(i, j int) bool { return semver.Less(fixes[i], fixes[j]) })
|
||||
return fixes
|
||||
}
|
||||
|
||||
// fixNegated checks if fix is negated to by a re-introduction
|
||||
// of a vulnerability in affected. Assumes that all affected apply
|
||||
// to the same module.
|
||||
func fixNegated(fix string, affected []osv.Affected) bool {
|
||||
for _, a := range affected {
|
||||
for _, r := range a.Ranges {
|
||||
if semver.ContainsSemver(r, fix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func modPath(mod *packages.Module) string {
|
||||
if mod.Replace != nil {
|
||||
return mod.Replace.Path
|
||||
}
|
||||
return mod.Path
|
||||
}
|
||||
|
||||
func modVersion(mod *packages.Module) string {
|
||||
if mod.Replace != nil {
|
||||
return mod.Replace.Version
|
||||
}
|
||||
return mod.Version
|
||||
}
|
||||
|
||||
// pkgPath returns the path of the f's enclosing package, if any.
|
||||
// Otherwise, returns internal.UnknownPackagePath.
|
||||
func pkgPath(f *ssa.Function) string {
|
||||
g := f
|
||||
if f.Origin() != nil {
|
||||
// Instantiations of generics do not have
|
||||
// an associated package. We hence look up
|
||||
// the original function for the package.
|
||||
g = f.Origin()
|
||||
}
|
||||
if g.Package() != nil && g.Package().Pkg != nil {
|
||||
return g.Package().Pkg.Path()
|
||||
}
|
||||
return internal.UnknownPackagePath
|
||||
}
|
||||
|
||||
func pkgModPath(pkg *packages.Package) string {
|
||||
if pkg != nil && pkg.Module != nil {
|
||||
return pkg.Module.Path
|
||||
}
|
||||
return internal.UnknownModulePath
|
||||
}
|
||||
|
||||
func IsStdPackage(pkg string) bool {
|
||||
if pkg == "" || pkg == internal.UnknownPackagePath {
|
||||
return false
|
||||
}
|
||||
// std packages do not have a "." in their path. For instance, see
|
||||
// Contains in pkgsite/+/refs/heads/master/internal/stdlbib/stdlib.go.
|
||||
if i := strings.IndexByte(pkg, '/'); i != -1 {
|
||||
pkg = pkg[:i]
|
||||
}
|
||||
return !strings.Contains(pkg, ".")
|
||||
}
|
||||
336
vendor/golang.org/x/vuln/internal/vulncheck/vulncheck.go
generated
vendored
Normal file
336
vendor/golang.org/x/vuln/internal/vulncheck/vulncheck.go
generated
vendored
Normal file
@@ -0,0 +1,336 @@
|
||||
// Copyright 2021 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 vulncheck
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/vuln/internal"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
"golang.org/x/vuln/internal/semver"
|
||||
)
|
||||
|
||||
const (
|
||||
fetchingVulnsMessage = "Fetching vulnerabilities from the database..."
|
||||
checkingSrcVulnsMessage = "Checking the code against the vulnerabilities..."
|
||||
checkingBinVulnsMessage = "Checking the binary against the vulnerabilities..."
|
||||
)
|
||||
|
||||
// Result contains information on detected vulnerabilities.
|
||||
// For call graph analysis, it provides information on reachability
|
||||
// of vulnerable symbols through entry points of the program.
|
||||
type Result struct {
|
||||
// EntryFunctions are a subset of Functions representing vulncheck entry points.
|
||||
EntryFunctions []*FuncNode
|
||||
|
||||
// Vulns contains information on detected vulnerabilities.
|
||||
Vulns []*Vuln
|
||||
}
|
||||
|
||||
// Vuln provides information on a detected vulnerability. For call
|
||||
// graph mode, Vuln will also contain the information on how the
|
||||
// vulnerability is reachable in the user call graph.
|
||||
type Vuln struct {
|
||||
// OSV contains information on the detected vulnerability in the shared
|
||||
// vulnerability format.
|
||||
//
|
||||
// OSV, Symbol, and Package identify a vulnerability.
|
||||
//
|
||||
// Note that *osv.Entry may describe multiple symbols from multiple
|
||||
// packages.
|
||||
OSV *osv.Entry
|
||||
|
||||
// Symbol is the name of the detected vulnerable function or method.
|
||||
Symbol string
|
||||
|
||||
// CallSink is the FuncNode corresponding to Symbol.
|
||||
//
|
||||
// When analyzing binaries, Symbol is not reachable, or cfg.ScanLevel
|
||||
// is symbol, CallSink will be unavailable and set to nil.
|
||||
CallSink *FuncNode
|
||||
|
||||
// Package of Symbol.
|
||||
//
|
||||
// When the package of symbol is not imported, Package will be
|
||||
// unavailable and set to nil.
|
||||
Package *packages.Package
|
||||
}
|
||||
|
||||
// A FuncNode describes a function in the call graph.
|
||||
type FuncNode struct {
|
||||
// Name is the name of the function.
|
||||
Name string
|
||||
|
||||
// RecvType is the receiver object type of this function, if any.
|
||||
RecvType string
|
||||
|
||||
// Package is the package the function is part of.
|
||||
Package *packages.Package
|
||||
|
||||
// Position describes the position of the function in the file.
|
||||
Pos *token.Position
|
||||
|
||||
// CallSites is a set of call sites where this function is called.
|
||||
CallSites []*CallSite
|
||||
}
|
||||
|
||||
func (fn *FuncNode) String() string {
|
||||
if fn.RecvType == "" {
|
||||
return fmt.Sprintf("%s.%s", fn.Package.PkgPath, fn.Name)
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", fn.RecvType, fn.Name)
|
||||
}
|
||||
|
||||
// Receiver returns the FuncNode's receiver, with package path removed.
|
||||
// Pointers are preserved if present.
|
||||
func (fn *FuncNode) Receiver() string {
|
||||
return strings.Replace(fn.RecvType, fmt.Sprintf("%s.", fn.Package.PkgPath), "", 1)
|
||||
}
|
||||
|
||||
// A CallSite describes a function call.
|
||||
type CallSite struct {
|
||||
// Parent is the enclosing function where the call is made.
|
||||
Parent *FuncNode
|
||||
|
||||
// Name stands for the name of the function (variable) being called.
|
||||
Name string
|
||||
|
||||
// RecvType is the full path of the receiver object type, if any.
|
||||
RecvType string
|
||||
|
||||
// Position describes the position of the function in the file.
|
||||
Pos *token.Position
|
||||
|
||||
// Resolved indicates if the called function can be statically resolved.
|
||||
Resolved bool
|
||||
}
|
||||
|
||||
// affectingVulns is an internal structure for querying
|
||||
// vulnerabilities that apply to the current program
|
||||
// and platform under consideration.
|
||||
type affectingVulns []*ModVulns
|
||||
|
||||
// ModVulns groups vulnerabilities per module.
|
||||
type ModVulns struct {
|
||||
Module *packages.Module
|
||||
Vulns []*osv.Entry
|
||||
}
|
||||
|
||||
func affectingVulnerabilities(vulns []*ModVulns, os, arch string) affectingVulns {
|
||||
now := time.Now()
|
||||
var filtered affectingVulns
|
||||
for _, mod := range vulns {
|
||||
module := mod.Module
|
||||
modVersion := module.Version
|
||||
if module.Replace != nil {
|
||||
modVersion = module.Replace.Version
|
||||
}
|
||||
// TODO(https://golang.org/issues/49264): if modVersion == "", try vcs?
|
||||
var filteredVulns []*osv.Entry
|
||||
for _, v := range mod.Vulns {
|
||||
// Ignore vulnerabilities that have been withdrawn
|
||||
if v.Withdrawn != nil && v.Withdrawn.Before(now) {
|
||||
continue
|
||||
}
|
||||
|
||||
var filteredAffected []osv.Affected
|
||||
for _, a := range v.Affected {
|
||||
// Vulnerabilities from some databases might contain
|
||||
// information on related but different modules that
|
||||
// were, say, reported in the same CVE. We filter such
|
||||
// information out as it might lead to incorrect results:
|
||||
// Computing a latest fix could consider versions of these
|
||||
// different packages.
|
||||
if a.Module.Path != module.Path {
|
||||
continue
|
||||
}
|
||||
if !affected(modVersion, a) {
|
||||
continue
|
||||
}
|
||||
|
||||
var filteredImports []osv.Package
|
||||
for _, p := range a.EcosystemSpecific.Packages {
|
||||
if matchesPlatform(os, arch, p) {
|
||||
filteredImports = append(filteredImports, p)
|
||||
}
|
||||
}
|
||||
// If we pruned all existing Packages, then the affected is
|
||||
// empty and we can filter it out. Note that Packages can
|
||||
// be empty for vulnerabilities that have no package or
|
||||
// symbol information available.
|
||||
if len(a.EcosystemSpecific.Packages) != 0 && len(filteredImports) == 0 {
|
||||
continue
|
||||
}
|
||||
a.EcosystemSpecific.Packages = filteredImports
|
||||
filteredAffected = append(filteredAffected, a)
|
||||
}
|
||||
if len(filteredAffected) == 0 {
|
||||
continue
|
||||
}
|
||||
// save the non-empty vulnerability with only
|
||||
// affected symbols.
|
||||
newV := *v
|
||||
newV.Affected = filteredAffected
|
||||
filteredVulns = append(filteredVulns, &newV)
|
||||
}
|
||||
|
||||
filtered = append(filtered, &ModVulns{
|
||||
Module: module,
|
||||
Vulns: filteredVulns,
|
||||
})
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// affected checks if modVersion is affected by a:
|
||||
// - it is included in one of the affected version ranges
|
||||
// - and module version is not "" and "(devel)"
|
||||
func affected(modVersion string, a osv.Affected) bool {
|
||||
const devel = "(devel)"
|
||||
if modVersion == "" || modVersion == devel {
|
||||
// Module version of "" means the module version is not available
|
||||
// and devel means it is in development stage. Either way, we don't
|
||||
// know the exact version so we don't want to spam users with
|
||||
// potential false alarms.
|
||||
return false
|
||||
}
|
||||
return semver.Affects(a.Ranges, modVersion)
|
||||
}
|
||||
|
||||
func matchesPlatform(os, arch string, e osv.Package) bool {
|
||||
return matchesPlatformComponent(os, e.GOOS) &&
|
||||
matchesPlatformComponent(arch, e.GOARCH)
|
||||
}
|
||||
|
||||
// matchesPlatformComponent reports whether a GOOS (or GOARCH)
|
||||
// matches a list of GOOS (or GOARCH) values from an osv.EcosystemSpecificImport.
|
||||
func matchesPlatformComponent(s string, ps []string) bool {
|
||||
// An empty input or an empty GOOS or GOARCH list means "matches everything."
|
||||
if s == "" || len(ps) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, p := range ps {
|
||||
if s == p {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// moduleVulns return vulnerabilities for module. If module is unknown,
|
||||
// it figures the module from package importPath. It returns the module
|
||||
// whose path is the longest prefix of importPath.
|
||||
func (aff affectingVulns) moduleVulns(module, importPath string) *ModVulns {
|
||||
moduleKnown := module != "" && module != internal.UnknownModulePath
|
||||
|
||||
isStd := IsStdPackage(importPath)
|
||||
var mostSpecificMod *ModVulns // for the case where !moduleKnown
|
||||
for _, mod := range aff {
|
||||
md := mod
|
||||
if isStd && mod.Module.Path == internal.GoStdModulePath {
|
||||
// Standard library packages do not have an associated module,
|
||||
// so we relate them to the artificial stdlib module.
|
||||
return md
|
||||
}
|
||||
|
||||
if moduleKnown {
|
||||
if mod.Module.Path == module {
|
||||
// If we know exactly which module we need,
|
||||
// return its vulnerabilities.
|
||||
return md
|
||||
}
|
||||
} else if strings.HasPrefix(importPath, md.Module.Path) {
|
||||
// If module is unknown, we try to figure it out from importPath.
|
||||
// We take the module whose path has the longest match to importPath.
|
||||
// TODO: do matching based on path components.
|
||||
if mostSpecificMod == nil || len(mostSpecificMod.Module.Path) < len(md.Module.Path) {
|
||||
mostSpecificMod = md
|
||||
}
|
||||
}
|
||||
}
|
||||
return mostSpecificMod
|
||||
}
|
||||
|
||||
// ForPackage returns the vulnerabilities for the importPath belonging to
|
||||
// module.
|
||||
//
|
||||
// If module is unknown, ForPackage will resolve it as the most specific
|
||||
// prefix of importPath.
|
||||
func (aff affectingVulns) ForPackage(module, importPath string) []*osv.Entry {
|
||||
mod := aff.moduleVulns(module, importPath)
|
||||
if mod == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if mod.Module.Replace != nil {
|
||||
// standard libraries do not have a module nor replace module
|
||||
importPath = fmt.Sprintf("%s%s", mod.Module.Replace.Path, strings.TrimPrefix(importPath, mod.Module.Path))
|
||||
}
|
||||
vulns := mod.Vulns
|
||||
packageVulns := []*osv.Entry{}
|
||||
Vuln:
|
||||
for _, v := range vulns {
|
||||
for _, a := range v.Affected {
|
||||
if len(a.EcosystemSpecific.Packages) == 0 {
|
||||
// no packages means all packages are vulnerable
|
||||
packageVulns = append(packageVulns, v)
|
||||
continue Vuln
|
||||
}
|
||||
|
||||
for _, p := range a.EcosystemSpecific.Packages {
|
||||
if p.Path == importPath {
|
||||
packageVulns = append(packageVulns, v)
|
||||
continue Vuln
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return packageVulns
|
||||
}
|
||||
|
||||
// ForSymbol returns vulnerabilities for symbol in aff.ForPackage(module, importPath).
|
||||
func (aff affectingVulns) ForSymbol(module, importPath, symbol string) []*osv.Entry {
|
||||
vulns := aff.ForPackage(module, importPath)
|
||||
if vulns == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
symbolVulns := []*osv.Entry{}
|
||||
vulnLoop:
|
||||
for _, v := range vulns {
|
||||
for _, a := range v.Affected {
|
||||
if len(a.EcosystemSpecific.Packages) == 0 {
|
||||
// no packages means all symbols of all packages are vulnerable
|
||||
symbolVulns = append(symbolVulns, v)
|
||||
continue vulnLoop
|
||||
}
|
||||
|
||||
for _, p := range a.EcosystemSpecific.Packages {
|
||||
if p.Path != importPath {
|
||||
continue
|
||||
}
|
||||
if len(p.Symbols) > 0 && !contains(p.Symbols, symbol) {
|
||||
continue
|
||||
}
|
||||
symbolVulns = append(symbolVulns, v)
|
||||
continue vulnLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
return symbolVulns
|
||||
}
|
||||
|
||||
func contains(symbols []string, target string) bool {
|
||||
for _, s := range symbols {
|
||||
if s == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
449
vendor/golang.org/x/vuln/internal/vulncheck/witness.go
generated
vendored
Normal file
449
vendor/golang.org/x/vuln/internal/vulncheck/witness.go
generated
vendored
Normal file
@@ -0,0 +1,449 @@
|
||||
// Copyright 2021 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 vulncheck
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
// CallStack is a call stack starting with a client
|
||||
// function or method and ending with a call to a
|
||||
// vulnerable symbol.
|
||||
type CallStack []StackEntry
|
||||
|
||||
// StackEntry is an element of a call stack.
|
||||
type StackEntry struct {
|
||||
// Function whose frame is on the stack.
|
||||
Function *FuncNode
|
||||
|
||||
// Call is the call site inducing the next stack frame.
|
||||
// nil when the frame represents the last frame in the stack.
|
||||
Call *CallSite
|
||||
}
|
||||
|
||||
// sourceCallstacks returns representative call stacks for each
|
||||
// vulnerability in res. The returned call stacks are heuristically
|
||||
// ordered by how seemingly easy is to understand them: shorter
|
||||
// call stacks with less dynamic call sites appear earlier in the
|
||||
// returned slices.
|
||||
//
|
||||
// sourceCallstacks performs a breadth-first search of res.CallGraph
|
||||
// starting at the vulnerable symbol and going up until reaching an entry
|
||||
// function or method in res.CallGraph.Entries. During this search,
|
||||
// each function is visited at most once to avoid potential
|
||||
// exponential explosion. Hence, not all call stacks are analyzed.
|
||||
func sourceCallstacks(res *Result) map[*Vuln]CallStack {
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
)
|
||||
stackPerVuln := make(map[*Vuln]CallStack)
|
||||
for _, vuln := range res.Vulns {
|
||||
vuln := vuln
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
cs := sourceCallstack(vuln, res)
|
||||
mu.Lock()
|
||||
stackPerVuln[vuln] = cs
|
||||
mu.Unlock()
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
updateInitPositions(stackPerVuln)
|
||||
return stackPerVuln
|
||||
}
|
||||
|
||||
// sourceCallstack finds a representative call stack for vuln.
|
||||
// This is a shortest unique call stack with the least
|
||||
// number of dynamic call sites.
|
||||
func sourceCallstack(vuln *Vuln, res *Result) CallStack {
|
||||
vulnSink := vuln.CallSink
|
||||
if vulnSink == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
entries := make(map[*FuncNode]bool)
|
||||
for _, e := range res.EntryFunctions {
|
||||
entries[e] = true
|
||||
}
|
||||
|
||||
seen := make(map[*FuncNode]bool)
|
||||
|
||||
// Do a BFS from the vuln sink to the entry points
|
||||
// and find the representative call stack. This is
|
||||
// the shortest call stack that goes through the
|
||||
// least number of dynamic call sites. We first
|
||||
// collect all candidate call stacks of the shortest
|
||||
// length and then pick the best one accordingly.
|
||||
var candidates []CallStack
|
||||
candDepth := 0
|
||||
queue := list.New()
|
||||
queue.PushBack(&callChain{f: vulnSink})
|
||||
|
||||
// We want to avoid call stacks that go through
|
||||
// other vulnerable symbols of the same package
|
||||
// for the same vulnerability. In other words,
|
||||
// we want unique call stacks.
|
||||
skipSymbols := make(map[*FuncNode]bool)
|
||||
for _, v := range res.Vulns {
|
||||
if v.CallSink != nil && v != vuln &&
|
||||
v.OSV == vuln.OSV && v.Package == vuln.Package {
|
||||
skipSymbols[v.CallSink] = true
|
||||
}
|
||||
}
|
||||
|
||||
for queue.Len() > 0 {
|
||||
front := queue.Front()
|
||||
c := front.Value.(*callChain)
|
||||
queue.Remove(front)
|
||||
|
||||
f := c.f
|
||||
if seen[f] {
|
||||
continue
|
||||
}
|
||||
seen[f] = true
|
||||
|
||||
// Pick a single call site for each function in determinstic order.
|
||||
// A single call site is sufficient as we visit a function only once.
|
||||
for _, cs := range callsites(f.CallSites, seen) {
|
||||
nStack := &callChain{f: cs.Parent, call: cs, child: c}
|
||||
if !skipSymbols[cs.Parent] {
|
||||
queue.PushBack(nStack)
|
||||
}
|
||||
|
||||
if entries[cs.Parent] {
|
||||
ns := nStack.CallStack()
|
||||
if len(candidates) == 0 || len(ns) == candDepth {
|
||||
// The case where we either have not identified
|
||||
// any call stacks or just found one of the same
|
||||
// length as the previous ones.
|
||||
candidates = append(candidates, ns)
|
||||
candDepth = len(ns)
|
||||
} else {
|
||||
// We just found a candidate call stack whose
|
||||
// length is greater than what we previously
|
||||
// found. We can thus safely disregard this
|
||||
// call stack and stop searching since we won't
|
||||
// be able to find any better candidates.
|
||||
queue.Init() // clear the list, effectively exiting the outer loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort candidate call stacks by their number of dynamic call
|
||||
// sites and return the first one.
|
||||
sort.SliceStable(candidates, func(i int, j int) bool {
|
||||
s1, s2 := candidates[i], candidates[j]
|
||||
if w1, w2 := weight(s1), weight(s2); w1 != w2 {
|
||||
return w1 < w2
|
||||
}
|
||||
|
||||
// At this point, the stableness/determinism of
|
||||
// sorting is guaranteed by the determinism of
|
||||
// the underlying call graph and the call stack
|
||||
// search algorithm.
|
||||
return true
|
||||
})
|
||||
if len(candidates) == 0 {
|
||||
return nil
|
||||
}
|
||||
return candidates[0]
|
||||
}
|
||||
|
||||
// callsites picks a call site from sites for each non-visited function.
|
||||
// For each such function, the smallest (posLess) call site is chosen. The
|
||||
// returned slice is sorted by caller functions (funcLess). Assumes callee
|
||||
// of each call site is the same.
|
||||
func callsites(sites []*CallSite, visited map[*FuncNode]bool) []*CallSite {
|
||||
minCs := make(map[*FuncNode]*CallSite)
|
||||
for _, cs := range sites {
|
||||
if visited[cs.Parent] {
|
||||
continue
|
||||
}
|
||||
if csLess(cs, minCs[cs.Parent]) {
|
||||
minCs[cs.Parent] = cs
|
||||
}
|
||||
}
|
||||
|
||||
var fs []*FuncNode
|
||||
for _, cs := range minCs {
|
||||
fs = append(fs, cs.Parent)
|
||||
}
|
||||
sort.SliceStable(fs, func(i, j int) bool { return funcLess(fs[i], fs[j]) })
|
||||
|
||||
var css []*CallSite
|
||||
for _, f := range fs {
|
||||
css = append(css, minCs[f])
|
||||
}
|
||||
return css
|
||||
}
|
||||
|
||||
// callChain models a chain of function calls.
|
||||
type callChain struct {
|
||||
call *CallSite // nil for entry points
|
||||
f *FuncNode
|
||||
child *callChain
|
||||
}
|
||||
|
||||
// CallStack converts callChain to CallStack type.
|
||||
func (c *callChain) CallStack() CallStack {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return append(CallStack{StackEntry{Function: c.f, Call: c.call}}, c.child.CallStack()...)
|
||||
}
|
||||
|
||||
// weight computes an approximate measure of how easy is to understand the call
|
||||
// stack when presented to the client as a witness. The smaller the value, the more
|
||||
// understandable the stack is. Currently defined as the number of unresolved
|
||||
// call sites in the stack.
|
||||
func weight(stack CallStack) int {
|
||||
w := 0
|
||||
for _, e := range stack {
|
||||
if e.Call != nil && !e.Call.Resolved {
|
||||
w += 1
|
||||
}
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// csLess compares two call sites by their locations and, if needed,
|
||||
// their string representation.
|
||||
func csLess(cs1, cs2 *CallSite) bool {
|
||||
if cs2 == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// fast code path
|
||||
if p1, p2 := cs1.Pos, cs2.Pos; p1 != nil && p2 != nil {
|
||||
if posLess(*p1, *p2) {
|
||||
return true
|
||||
}
|
||||
if posLess(*p2, *p1) {
|
||||
return false
|
||||
}
|
||||
// for sanity, should not occur in practice
|
||||
return fmt.Sprintf("%v.%v", cs1.RecvType, cs2.Name) < fmt.Sprintf("%v.%v", cs2.RecvType, cs2.Name)
|
||||
}
|
||||
|
||||
// code path rarely exercised
|
||||
if cs2.Pos == nil {
|
||||
return true
|
||||
}
|
||||
if cs1.Pos == nil {
|
||||
return false
|
||||
}
|
||||
// should very rarely occur in practice
|
||||
return fmt.Sprintf("%v.%v", cs1.RecvType, cs2.Name) < fmt.Sprintf("%v.%v", cs2.RecvType, cs2.Name)
|
||||
}
|
||||
|
||||
// posLess compares two positions by their line and column number,
|
||||
// and filename if needed.
|
||||
func posLess(p1, p2 token.Position) bool {
|
||||
if p1.Line < p2.Line {
|
||||
return true
|
||||
}
|
||||
if p2.Line < p1.Line {
|
||||
return false
|
||||
}
|
||||
|
||||
if p1.Column < p2.Column {
|
||||
return true
|
||||
}
|
||||
if p2.Column < p1.Column {
|
||||
return false
|
||||
}
|
||||
|
||||
return strings.Compare(p1.Filename, p2.Filename) == -1
|
||||
}
|
||||
|
||||
// funcLess compares two function nodes by locations of
|
||||
// corresponding functions and, if needed, their string representation.
|
||||
func funcLess(f1, f2 *FuncNode) bool {
|
||||
if p1, p2 := f1.Pos, f2.Pos; p1 != nil && p2 != nil {
|
||||
if posLess(*p1, *p2) {
|
||||
return true
|
||||
}
|
||||
if posLess(*p2, *p1) {
|
||||
return false
|
||||
}
|
||||
// for sanity, should not occur in practice
|
||||
return f1.String() < f2.String()
|
||||
}
|
||||
|
||||
if f2.Pos == nil {
|
||||
return true
|
||||
}
|
||||
if f1.Pos == nil {
|
||||
return false
|
||||
}
|
||||
// should happen only for inits
|
||||
return f1.String() < f2.String()
|
||||
}
|
||||
|
||||
// updateInitPositions populates non-existing positions of init functions
|
||||
// and their respective calls in callStacks (see #51575).
|
||||
func updateInitPositions(callStacks map[*Vuln]CallStack) {
|
||||
for _, cs := range callStacks {
|
||||
for i := range cs {
|
||||
updateInitPosition(&cs[i])
|
||||
if i != len(cs)-1 {
|
||||
updateInitCallPosition(&cs[i], cs[i+1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updateInitCallPosition updates the position of a call to init in a stack frame, if
|
||||
// one already does not exist:
|
||||
//
|
||||
// P1.init -> P2.init: position of call to P2.init is the position of "import P2"
|
||||
// statement in P1
|
||||
//
|
||||
// P.init -> P.init#d: P.init is an implicit init. We say it calls the explicit
|
||||
// P.init#d at the place of "package P" statement.
|
||||
func updateInitCallPosition(curr *StackEntry, next StackEntry) {
|
||||
call := curr.Call
|
||||
if !isInit(next.Function) || (call.Pos != nil && call.Pos.IsValid()) {
|
||||
// Skip non-init functions and inits whose call site position is available.
|
||||
return
|
||||
}
|
||||
|
||||
var pos token.Position
|
||||
if curr.Function.Name == "init" && curr.Function.Package == next.Function.Package {
|
||||
// We have implicit P.init calling P.init#d. Set the call position to
|
||||
// be at "package P" statement position.
|
||||
pos = packageStatementPos(curr.Function.Package)
|
||||
} else {
|
||||
// Choose the beginning of the import statement as the position.
|
||||
pos = importStatementPos(curr.Function.Package, next.Function.Package.PkgPath)
|
||||
}
|
||||
|
||||
call.Pos = &pos
|
||||
}
|
||||
|
||||
func importStatementPos(pkg *packages.Package, importPath string) token.Position {
|
||||
var importSpec *ast.ImportSpec
|
||||
spec:
|
||||
for _, f := range pkg.Syntax {
|
||||
for _, impSpec := range f.Imports {
|
||||
// Import spec paths have quotation marks.
|
||||
impSpecPath, err := strconv.Unquote(impSpec.Path.Value)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("import specification: package path has no quotation marks: %v", err))
|
||||
}
|
||||
if impSpecPath == importPath {
|
||||
importSpec = impSpec
|
||||
break spec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if importSpec == nil {
|
||||
// for sanity, in case of a wild call graph imprecision
|
||||
return token.Position{}
|
||||
}
|
||||
|
||||
// Choose the beginning of the import statement as the position.
|
||||
return pkg.Fset.Position(importSpec.Pos())
|
||||
}
|
||||
|
||||
func packageStatementPos(pkg *packages.Package) token.Position {
|
||||
if len(pkg.Syntax) == 0 {
|
||||
return token.Position{}
|
||||
}
|
||||
// Choose beginning of the package statement as the position. Pick
|
||||
// the first file since it is as good as any.
|
||||
return pkg.Fset.Position(pkg.Syntax[0].Package)
|
||||
}
|
||||
|
||||
// updateInitPosition updates the position of P.init function in a stack frame if one
|
||||
// is not available. The new position is the position of the "package P" statement.
|
||||
func updateInitPosition(se *StackEntry) {
|
||||
fun := se.Function
|
||||
if !isInit(fun) || (fun.Pos != nil && fun.Pos.IsValid()) {
|
||||
// Skip non-init functions and inits whose position is available.
|
||||
return
|
||||
}
|
||||
|
||||
pos := packageStatementPos(fun.Package)
|
||||
fun.Pos = &pos
|
||||
}
|
||||
|
||||
func isInit(f *FuncNode) bool {
|
||||
// A source init function, or anonymous functions used in inits, will
|
||||
// be named "init#x" by vulncheck (more precisely, ssa), where x is a
|
||||
// positive integer. Implicit inits are named simply "init".
|
||||
return f.Name == "init" || strings.HasPrefix(f.Name, "init#")
|
||||
}
|
||||
|
||||
// binaryCallstacks computes representative call stacks for binary results.
|
||||
func binaryCallstacks(vr *Result) map[*Vuln]CallStack {
|
||||
callstacks := map[*Vuln]CallStack{}
|
||||
for _, vv := range uniqueVulns(vr.Vulns) {
|
||||
f := &FuncNode{Package: vv.Package, Name: vv.Symbol}
|
||||
parts := strings.Split(vv.Symbol, ".")
|
||||
if len(parts) != 1 {
|
||||
f.RecvType = parts[0]
|
||||
f.Name = parts[1]
|
||||
}
|
||||
callstacks[vv] = CallStack{StackEntry{Function: f}}
|
||||
}
|
||||
return callstacks
|
||||
}
|
||||
|
||||
// uniqueVulns does for binary mode what sourceCallstacks does for source mode.
|
||||
// It tries not to report redundant symbols. Since there are no call stacks in
|
||||
// binary mode, the following approximate approach is used. Do not report unexported
|
||||
// symbols for a <vulnID, pkg, module> triple if there are some exported symbols.
|
||||
// Otherwise, report all unexported symbols to avoid not reporting anything.
|
||||
func uniqueVulns(vulns []*Vuln) []*Vuln {
|
||||
type key struct {
|
||||
id string
|
||||
pkg string
|
||||
mod string
|
||||
}
|
||||
hasExported := make(map[key]bool)
|
||||
for _, v := range vulns {
|
||||
if isExported(v.Symbol) {
|
||||
k := key{id: v.OSV.ID, pkg: v.Package.PkgPath, mod: v.Package.Module.Path}
|
||||
hasExported[k] = true
|
||||
}
|
||||
}
|
||||
|
||||
var uniques []*Vuln
|
||||
for _, v := range vulns {
|
||||
k := key{id: v.OSV.ID, pkg: v.Package.PkgPath, mod: v.Package.Module.Path}
|
||||
if isExported(v.Symbol) || !hasExported[k] {
|
||||
uniques = append(uniques, v)
|
||||
}
|
||||
}
|
||||
return uniques
|
||||
}
|
||||
|
||||
// isExported checks if the symbol is exported. Assumes that the
|
||||
// symbol is of the form "identifier", "identifier1.identifier2",
|
||||
// or "identifier.".
|
||||
func isExported(symbol string) bool {
|
||||
parts := strings.Split(symbol, ".")
|
||||
last := parts[len(parts)-1]
|
||||
if last == "" { // case for "identifier."
|
||||
return false
|
||||
}
|
||||
return unicode.IsUpper(rune(last[0]))
|
||||
}
|
||||
Reference in New Issue
Block a user