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