blob: bceecd4b7fbfb5c04feb78a736b3e16d8875ee19 [file] [log] [blame]
// 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 (
"sort"
"golang.org/x/tools/go/packages"
"golang.org/x/vuln/internal"
"golang.org/x/vuln/internal/govulncheck"
"golang.org/x/vuln/internal/osv"
)
// 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, osv.Affected)},
}); err != nil {
return err
}
}
}
return nil
}
// emitPackageFinding emits package-level findings fod vulnerabilities in vulns.
//
// It does not emit imported symbols. Only the package information is emitted.
func emitPackageFindings(handler govulncheck.Handler, vulns []*Vuln) error {
emitted := make(map[Vuln]bool)
for _, vuln := range vulns {
v := Vuln{
Package: vuln.Package,
OSV: vuln.OSV,
}
if emitted[v] {
// do not emit the same finding all over again
continue
}
emitted[v] = true
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)
}
sort.SliceStable(vulns, func(i, j int) bool {
return vulns[i].Symbol < vulns[j].Symbol
})
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()
if e.Call == nil || e.Call.Pos == nil {
fr.Position = nil
} else {
fr.Position = &govulncheck.Position{
Filename: e.Call.Pos.Filename,
Offset: e.Call.Pos.Offset,
Line: e.Call.Pos.Line,
Column: e.Call.Pos.Column,
}
}
frames = append(frames, fr)
}
return frames
}
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, affected []osv.Affected) *govulncheck.Frame {
fr := &govulncheck.Frame{
Module: mod.Path,
Version: mod.Version,
}
if mod.Path == internal.GoStdModulePath {
for _, a := range affected {
if a.Module.Path != mod.Path {
continue
}
fr.Package = a.EcosystemSpecific.Packages[0].Path
}
}
if mod.Replace != nil {
fr.Module = mod.Replace.Path
fr.Version = mod.Replace.Version
}
return fr
}
func emitBinaryResult(handler govulncheck.Handler, vr *Result, callstacks map[*Vuln]CallStack) error {
// first deal with all the affected vulnerabilities
emitted := map[string]bool{}
for _, vv := range vr.Vulns {
fixed := FixedVersion(modPath(vv.Package.Module), modVersion(vv.Package.Module), vv.OSV.Affected)
stack := callstacks[vv]
if stack == nil {
continue
}
emitted[vv.OSV.ID] = true
if err := handler.Finding(&govulncheck.Finding{
OSV: vv.OSV.ID,
FixedVersion: fixed,
Trace: tracefromEntries(stack),
}); err != nil {
return err
}
}
for _, vv := range vr.Vulns {
if emitted[vv.OSV.ID] {
continue
}
stacks := callstacks[vv]
if len(stacks) != 0 {
continue
}
emitted[vv.OSV.ID] = true
if err := handler.Finding(&govulncheck.Finding{
OSV: vv.OSV.ID,
FixedVersion: FixedVersion(modPath(vv.Package.Module), modVersion(vv.Package.Module), vv.OSV.Affected),
Trace: []*govulncheck.Frame{frameFromPackage(vv.Package)},
}); err != nil {
return err
}
}
return nil
}