internal/result: move structs to separate package
Move structs for the govulncheck JSON output to a separate package.
Change-Id: Ifb886850a6615c8a20373b00e2065f7d4770bdb2
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/474479
Run-TryBot: Julie Qiu <julieqiu@google.com>
Reviewed-by: Julie Qiu <julieqiu@google.com>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/cmd/govulncheck/integration/k8s/k8s.go b/cmd/govulncheck/integration/k8s/k8s.go
index c19db34..e1674e9 100644
--- a/cmd/govulncheck/integration/k8s/k8s.go
+++ b/cmd/govulncheck/integration/k8s/k8s.go
@@ -10,7 +10,7 @@
"os"
"github.com/google/go-cmp/cmp"
- "golang.org/x/vuln/internal/govulncheck"
+ "golang.org/x/vuln/internal/result"
)
const usage = `test helper for examining the output of running govulncheck on k8s@v1.15.11.
@@ -29,7 +29,7 @@
log.Fatal("Failed to read:", out)
}
- var r govulncheck.Result
+ var r result.Result
if err := json.Unmarshal(outJson, &r); err != nil {
log.Fatal("Failed to load json into internal/govulncheck.Result:", err)
}
diff --git a/cmd/govulncheck/integration/stackrox-scanner/scanner.go b/cmd/govulncheck/integration/stackrox-scanner/scanner.go
index 4d46955..bfd5058 100644
--- a/cmd/govulncheck/integration/stackrox-scanner/scanner.go
+++ b/cmd/govulncheck/integration/stackrox-scanner/scanner.go
@@ -10,7 +10,7 @@
"os"
"github.com/google/go-cmp/cmp"
- "golang.org/x/vuln/internal/govulncheck"
+ "golang.org/x/vuln/internal/result"
)
const usage = `test helper for examining the output of running govulncheck on
@@ -30,7 +30,7 @@
log.Fatal("Failed to read:", out)
}
- var r govulncheck.Result
+ var r result.Result
if err := json.Unmarshal(outJson, &r); err != nil {
log.Fatal("Failed to load json into internal/govulncheck.Result:", err)
}
diff --git a/exp/govulncheck/govulncheck.go b/exp/govulncheck/govulncheck.go
index d09b0d0..2b4fb8c 100644
--- a/exp/govulncheck/govulncheck.go
+++ b/exp/govulncheck/govulncheck.go
@@ -5,7 +5,10 @@
// Package govulncheck provides an experimental govulncheck API.
package govulncheck
-import "golang.org/x/vuln/internal/govulncheck"
+import (
+ "golang.org/x/vuln/internal/govulncheck"
+ "golang.org/x/vuln/internal/result"
+)
var (
// Source reports vulnerabilities that affect the analyzed packages.
@@ -20,22 +23,22 @@
Config = govulncheck.Config
// Result is the result of executing Source.
- Result = govulncheck.Result
+ Result = result.Result
// Vuln represents a single OSV entry.
- Vuln = govulncheck.Vuln
+ Vuln = result.Vuln
// Module represents a specific vulnerability relevant to a
// single module or package.
- Module = govulncheck.Module
+ Module = result.Module
// Package is a Go package with known vulnerable symbols.
- Package = govulncheck.Package
+ Package = result.Package
// CallStacks contains a representative call stack for each
// vulnerable symbol that is called.
- CallStack = govulncheck.CallStack
+ CallStack = result.CallStack
// StackFrame represents a call stack entry.
- StackFrame = govulncheck.StackFrame
+ StackFrame = result.StackFrame
)
diff --git a/internal/govulncheck/callstacks.go b/internal/govulncheck/callstacks.go
index 85ec630..df861d9 100644
--- a/internal/govulncheck/callstacks.go
+++ b/internal/govulncheck/callstacks.go
@@ -12,6 +12,7 @@
"strings"
"golang.org/x/vuln/internal"
+ "golang.org/x/vuln/internal/result"
"golang.org/x/vuln/vulncheck"
)
@@ -129,7 +130,7 @@
// "F calls W, which eventually calls V"
//
// If it can't find any of these functions, summarizeCallStack returns the empty string.
-func summarizeCallStack(cs CallStack, topPkgs map[string]bool, vulnPkg string) string {
+func summarizeCallStack(cs result.CallStack, topPkgs map[string]bool, vulnPkg string) string {
iTop, iTopEnd, topFunc, topEndFunc := summarizeTop(cs.Frames, topPkgs)
if iTop < 0 {
return ""
@@ -176,8 +177,8 @@
//
// [p.V p.W q.Q ...] -> (1, 1, p.W, p.W)
// [p.V p.W p.Z$1 q.Q ...] -> (1, 2, p.W, p.Z)
-func summarizeTop(frames []*StackFrame, topPkgs map[string]bool) (iTop, iTopEnd int, topFunc, topEndFunc string) {
- iTopEnd = lowest(frames, func(e *StackFrame) bool {
+func summarizeTop(frames []*result.StackFrame, topPkgs map[string]bool) (iTop, iTopEnd int, topFunc, topEndFunc string) {
+ iTopEnd = lowest(frames, func(e *result.StackFrame) bool {
return topPkgs[e.PkgPath]
})
if iTopEnd < 0 {
@@ -193,7 +194,7 @@
topEndFunc = creatorName(topEndFunc)
- iTop = lowest(frames, func(e *StackFrame) bool {
+ iTop = lowest(frames, func(e *result.StackFrame) bool {
return topPkgs[e.PkgPath] && !isAnonymousFunction(e.FuncName)
})
if iTop < 0 {
@@ -215,8 +216,8 @@
//
// [x x q.Q v.V v.W] -> (3, v.V, v.V)
// [x x q.Q v.V$1 v.W] -> (3, v.V, v.W)
-func summarizeVuln(frames []*StackFrame, iTop int, vulnPkg string) (iVulnStart int, vulnStartFunc, vulnFunc string) {
- iVulnStart = highest(frames[iTop+1:], func(e *StackFrame) bool {
+func summarizeVuln(frames []*result.StackFrame, iTop int, vulnPkg string) (iVulnStart int, vulnStartFunc, vulnFunc string) {
+ iVulnStart = highest(frames[iTop+1:], func(e *result.StackFrame) bool {
return e.PkgPath == vulnPkg
})
if iVulnStart < 0 {
@@ -232,7 +233,7 @@
vulnStartFunc = creatorName(vulnStartFunc)
- iVuln := highest(frames[iVulnStart:], func(e *StackFrame) bool {
+ iVuln := highest(frames[iVulnStart:], func(e *result.StackFrame) bool {
return e.PkgPath == vulnPkg && !isAnonymousFunction(e.FuncName)
})
if iVuln < 0 {
diff --git a/internal/govulncheck/callstacks_test.go b/internal/govulncheck/callstacks_test.go
index 2ff2e6e..dc3c869 100644
--- a/internal/govulncheck/callstacks_test.go
+++ b/internal/govulncheck/callstacks_test.go
@@ -14,6 +14,7 @@
"github.com/google/go-cmp/cmp"
"golang.org/x/tools/go/packages/packagestest"
+ "golang.org/x/vuln/internal/result"
"golang.org/x/vuln/internal/test"
"golang.org/x/vuln/osv"
"golang.org/x/vuln/vulncheck"
@@ -115,11 +116,11 @@
}
}
-func stringToCallStack(s string) CallStack {
- var cs CallStack
+func stringToCallStack(s string) result.CallStack {
+ var cs result.CallStack
for _, e := range strings.Fields(s) {
parts := strings.Split(e, ".")
- cs.Frames = append(cs.Frames, &StackFrame{
+ cs.Frames = append(cs.Frames, &result.StackFrame{
PkgPath: parts[0],
FuncName: parts[1],
})
diff --git a/internal/govulncheck/json.go b/internal/govulncheck/json.go
index 3e76548..7eec6a7 100644
--- a/internal/govulncheck/json.go
+++ b/internal/govulncheck/json.go
@@ -12,6 +12,7 @@
"io"
"golang.org/x/vuln/client"
+ "golang.org/x/vuln/internal/result"
)
type jsonOutput struct {
@@ -20,7 +21,7 @@
func (o *jsonOutput) intro(ctx context.Context, dbClient client.Client, dbs []string, source bool) {}
-func (o *jsonOutput) result(r *Result, verbose, source bool) error {
+func (o *jsonOutput) result(r *result.Result, verbose, source bool) error {
b, err := json.MarshalIndent(r, "", "\t")
if err != nil {
return err
diff --git a/internal/govulncheck/print_test.go b/internal/govulncheck/print_test.go
index 97ad1b5..7c7d9b5 100644
--- a/internal/govulncheck/print_test.go
+++ b/internal/govulncheck/print_test.go
@@ -10,6 +10,7 @@
"github.com/google/go-cmp/cmp"
"golang.org/x/vuln/internal"
+ "golang.org/x/vuln/internal/result"
"golang.org/x/vuln/osv"
)
@@ -126,10 +127,10 @@
}}}
func TestPrintTextNoVulns(t *testing.T) {
- r := &Result{Vulns: []*Vuln{
+ r := &result.Result{Vulns: []*result.Vuln{
{
OSV: testVuln1,
- Modules: []*Module{
+ Modules: []*result.Module{
{
Path: "golang.org/vmod",
FoundVersion: "v0.0.1",
@@ -166,17 +167,17 @@
}
func TestPrintTextSource(t *testing.T) {
- r := &Result{Vulns: []*Vuln{
+ r := &result.Result{Vulns: []*result.Vuln{
{
OSV: testVuln1,
- Modules: []*Module{
+ Modules: []*result.Module{
{
Path: "golang.org/vmod",
FoundVersion: "v0.0.1",
FixedVersion: "v0.1.3",
- Packages: []*Package{
+ Packages: []*result.Package{
{
- CallStacks: []CallStack{{Summary: "main calls vmod.Vuln"}},
+ CallStacks: []result.CallStack{{Summary: "main calls vmod.Vuln"}},
},
},
},
@@ -184,11 +185,11 @@
},
{
OSV: testVuln2,
- Modules: []*Module{
+ Modules: []*result.Module{
{
Path: internal.GoStdModulePath,
FoundVersion: "v0.0.1",
- Packages: []*Package{
+ Packages: []*result.Package{
{
Path: "net/http",
},
@@ -236,10 +237,10 @@
}
func TestPrintTextBinary(t *testing.T) {
- r := &Result{Vulns: []*Vuln{
+ r := &result.Result{Vulns: []*result.Vuln{
{
OSV: testVuln1,
- Modules: []*Module{
+ Modules: []*result.Module{
{
Path: "golang.org/vmod",
FoundVersion: "v0.0.1",
@@ -251,11 +252,11 @@
},
{
OSV: testVuln2,
- Modules: []*Module{
+ Modules: []*result.Module{
{
Path: internal.GoStdModulePath,
FoundVersion: "v0.0.1",
- Packages: []*Package{
+ Packages: []*result.Package{
{
Path: "net/http",
},
@@ -296,17 +297,17 @@
}
func TestPrintTextMultiModuleAndStacks(t *testing.T) {
- r := &Result{Vulns: []*Vuln{
+ r := &result.Result{Vulns: []*result.Vuln{
{
OSV: testVuln1,
- Modules: []*Module{
+ Modules: []*result.Module{
{
Path: "golang.org/vmod",
FoundVersion: "v0.0.1",
FixedVersion: "v0.1.3",
- Packages: []*Package{
+ Packages: []*result.Package{
{
- CallStacks: []CallStack{{Summary: "main calls vmod.Vuln"}, {Summary: "main calls vmod.VulnFoo"}},
+ CallStacks: []result.CallStack{{Summary: "main calls vmod.Vuln"}, {Summary: "main calls vmod.VulnFoo"}},
},
},
},
@@ -314,12 +315,12 @@
Path: "golang.org/vmod1",
FoundVersion: "v0.0.3",
FixedVersion: "v0.0.4",
- Packages: []*Package{
+ Packages: []*result.Package{
{
- CallStacks: []CallStack{{Summary: "Foo calls vmod1.Vuln"}},
+ CallStacks: []result.CallStack{{Summary: "Foo calls vmod1.Vuln"}},
},
{
- CallStacks: []CallStack{{Summary: "Bar calls vmod1.VulnFoo"}},
+ CallStacks: []result.CallStack{{Summary: "Bar calls vmod1.VulnFoo"}},
},
},
},
diff --git a/internal/govulncheck/result.go b/internal/govulncheck/result.go
index 9110105..5a33410 100644
--- a/internal/govulncheck/result.go
+++ b/internal/govulncheck/result.go
@@ -6,13 +6,8 @@
package govulncheck
import (
- "fmt"
- "go/token"
- "strings"
-
"golang.org/x/tools/go/packages"
"golang.org/x/vuln/client"
- "golang.org/x/vuln/osv"
)
// LoadMode is the level of information needed for each package
@@ -33,144 +28,3 @@
// By default, GoVersion is the go command version found from the PATH.
GoVersion string
}
-
-// Result is the result of executing Source or Binary.
-type Result struct {
- // Vulns contains all vulnerabilities that are called or imported by
- // the analyzed module.
- Vulns []*Vuln
-}
-
-// Vuln represents a single OSV entry.
-type Vuln struct {
- // OSV contains all data from the OSV entry for this vulnerability.
- OSV *osv.Entry
-
- // Modules contains all of the modules in the OSV entry where a
- // vulnerable package is imported by the target source code or binary.
- //
- // For example, a module M with two packages M/p1 and M/p2, where only p1
- // is vulnerable, will appear in this list if and only if p1 is imported by
- // the target source code or binary.
- Modules []*Module
-}
-
-// IsCalled reports whether the vulnerability is called, therefore
-// affecting the target source code or binary.
-func (v *Vuln) IsCalled() bool {
- for _, m := range v.Modules {
- for _, p := range m.Packages {
- if len(p.CallStacks) > 0 {
- return true
- }
- }
- }
- return false
-}
-
-// Module represents a specific vulnerability relevant to a single module.
-type Module struct {
- // Path is the module path of the module containing the vulnerability.
- //
- // Importable packages in the standard library will have the path "stdlib".
- Path string
-
- // FoundVersion is the module version where the vulnerability was found.
- FoundVersion string
-
- // FixedVersion is the module version where the vulnerability was
- // fixed. If there are multiple fixed versions in the OSV report, this will
- // be the latest fixed version.
- //
- // This is empty if a fix is not available.
- FixedVersion string
-
- // Packages contains all the vulnerable packages in OSV entry that are
- // imported by the target source code or binary.
- //
- // For example, given a module M with two packages M/p1 and M/p2, where
- // both p1 and p2 are vulnerable, p1 and p2 will each only appear in this
- // list they are individually imported by the target source code or binary.
- Packages []*Package
-}
-
-// Package is a Go package with known vulnerable symbols.
-type Package struct {
- // Path is the import path of the package containing the vulnerability.
- Path string
-
- // CallStacks contains a representative call stack for each
- // vulnerable symbol that is called.
- //
- // For vulnerabilities found from binary analysis, only CallStack.Symbol
- // will be provided.
- //
- // For non-affecting vulnerabilities reported from the source mode
- // analysis, this will be empty.
- CallStacks []CallStack
-}
-
-// CallStacks contains a representative call stack for a vulnerable
-// symbol.
-type CallStack struct {
- // Symbol is the name of the detected vulnerable function
- // or method.
- //
- // This follows the naming convention in the OSV report.
- Symbol string
-
- // Summary is a one-line description of the callstack, used by the
- // default govulncheck mode.
- //
- // Example: module3.main calls github.com/shiyanhui/dht.DHT.Run
- Summary string
-
- // Frames contains an entry for each stack in the call stack.
- //
- // Frames are sorted starting from the entry point to the
- // imported vulnerable symbol. The last frame in Frames should match
- // Symbol.
- Frames []*StackFrame
-}
-
-// StackFrame represents a call stack entry.
-type StackFrame struct {
- // PackagePath is the import path.
- PkgPath string
-
- // FuncName is the function name.
- FuncName string
-
- // RecvType is the fully qualified receiver type,
- // if the called symbol is a method.
- //
- // The client can create the final symbol name by
- // prepending RecvType to FuncName.
- RecvType string
-
- // Position describes an arbitrary source position
- // including the file, line, and column location.
- // A Position is valid if the line number is > 0.
- Position token.Position
-}
-
-// Name returns the full qualified function name from sf,
-// adjusted to remove pointer annotations.
-func (sf *StackFrame) Name() string {
- var n string
- if sf.RecvType == "" {
- n = fmt.Sprintf("%s.%s", sf.PkgPath, sf.FuncName)
- } else {
- n = fmt.Sprintf("%s.%s", sf.RecvType, sf.FuncName)
- }
- return strings.TrimPrefix(n, "*")
-}
-
-// Pos returns the position of the call in sf as string.
-// If position is not available, return "".
-func (sf *StackFrame) Pos() string {
- if sf.Position.IsValid() {
- return sf.Position.String()
- }
- return ""
-}
diff --git a/internal/govulncheck/result_test.go b/internal/govulncheck/result_test.go
index 081da3d..d0d52c4 100644
--- a/internal/govulncheck/result_test.go
+++ b/internal/govulncheck/result_test.go
@@ -7,16 +7,18 @@
import (
"go/token"
"testing"
+
+ "golang.org/x/vuln/internal/result"
)
func TestStackFrame(t *testing.T) {
for _, test := range []struct {
- sf *StackFrame
+ sf *result.StackFrame
wantFunc string
wantPos string
}{
{
- &StackFrame{
+ &result.StackFrame{
PkgPath: "golang.org/x/vuln/vulncheck",
FuncName: "Foo",
Position: token.Position{Filename: "some/path/file.go", Line: 12},
@@ -25,7 +27,7 @@
"some/path/file.go:12",
},
{
- &StackFrame{
+ &result.StackFrame{
PkgPath: "golang.org/x/vuln/vulncheck",
RecvType: "golang.org/x/vuln/vulncheck.Bar",
FuncName: "Foo",
@@ -49,17 +51,17 @@
// p is both the module and package path, and
// s is the called symbol. If s is "", then
// there is no called symbol.
- vuln := func(syms ...[2]string) *Vuln {
- v := &Vuln{}
+ vuln := func(syms ...[2]string) *result.Vuln {
+ v := &result.Vuln{}
for _, sym := range syms {
- p := &Package{Path: sym[0]}
- v.Modules = append(v.Modules, &Module{
+ p := &result.Package{Path: sym[0]}
+ v.Modules = append(v.Modules, &result.Module{
Path: sym[0],
- Packages: []*Package{p},
+ Packages: []*result.Package{p},
})
if symbol := sym[1]; symbol != "" {
- cs := CallStack{Symbol: symbol}
- p.CallStacks = []CallStack{cs}
+ cs := result.CallStack{Symbol: symbol}
+ p.CallStacks = []result.CallStack{cs}
}
}
return v
@@ -67,7 +69,7 @@
for _, test := range []struct {
desc string
- v *Vuln
+ v *result.Vuln
want bool
}{
{"called - single module", vuln([2]string{"golang.org/p1", "Foo"}), true},
diff --git a/internal/govulncheck/run.go b/internal/govulncheck/run.go
index bfdfaf1..a02052f 100644
--- a/internal/govulncheck/run.go
+++ b/internal/govulncheck/run.go
@@ -11,6 +11,7 @@
"io"
"sort"
+ "golang.org/x/vuln/internal/result"
"golang.org/x/vuln/osv"
"golang.org/x/vuln/vulncheck"
)
@@ -23,7 +24,7 @@
//
// This function is used for source code analysis by cmd/govulncheck and
// exp/govulncheck.
-func Source(ctx context.Context, cfg *Config, pkgs []*vulncheck.Package) (*Result, error) {
+func Source(ctx context.Context, cfg *Config, pkgs []*vulncheck.Package) (*result.Result, error) {
vcfg := &vulncheck.Config{
Client: cfg.Client,
SourceGoVersion: cfg.GoVersion,
@@ -38,7 +39,7 @@
// Binary detects presence of vulnerable symbols in exe.
//
// This function is used for binary analysis by cmd/govulncheck.
-func Binary(ctx context.Context, cfg *Config, exe io.ReaderAt) (*Result, error) {
+func Binary(ctx context.Context, cfg *Config, exe io.ReaderAt) (*result.Result, error) {
vcfg := &vulncheck.Config{
Client: cfg.Client,
}
@@ -49,7 +50,7 @@
return createBinaryResult(vr), nil
}
-func createSourceResult(vr *vulncheck.Result, pkgs []*vulncheck.Package) *Result {
+func createSourceResult(vr *vulncheck.Result, pkgs []*vulncheck.Package) *result.Result {
topPkgs := map[string]bool{}
for _, p := range pkgs {
topPkgs[p.PkgPath] = true
@@ -75,27 +76,27 @@
// Create Result where each vulncheck.Vuln{OSV, ModPath, PkgPath} becomes
// a separate Vuln{OSV, Modules{Packages{PkgPath}}} entry. We merge the
// results later.
- r := &Result{}
+ r := &result.Result{}
for _, vv := range vr.Vulns {
- p := &Package{Path: vv.PkgPath}
- m := &Module{
+ p := &result.Package{Path: vv.PkgPath}
+ m := &result.Module{
Path: vv.ModPath,
FoundVersion: foundVersion(vv.ModPath, modVersions),
FixedVersion: fixedVersion(vv.ModPath, vv.OSV.Affected),
- Packages: []*Package{p},
+ Packages: []*result.Package{p},
}
- v := &Vuln{OSV: vv.OSV, Modules: []*Module{m}}
+ v := &result.Vuln{OSV: vv.OSV, Modules: []*result.Module{m}}
if vv.CallSink != 0 {
k := key{id: vv.OSV.ID, pkg: vv.PkgPath, mod: vv.ModPath}
vcs := uniqueCallStack(vv, callStacks[vv], vulnsPerPkg[k], vr)
if vcs != nil {
- cs := CallStack{
+ cs := result.CallStack{
Frames: stackFramesfromEntries(vcs),
Symbol: vv.Symbol,
}
cs.Summary = summarizeCallStack(cs, topPkgs, p.Path)
- p.CallStacks = []CallStack{cs}
+ p.CallStacks = []result.CallStack{cs}
}
}
r.Vulns = append(r.Vulns, v)
@@ -106,23 +107,23 @@
return r
}
-func createBinaryResult(vr *vulncheck.Result) *Result {
+func createBinaryResult(vr *vulncheck.Result) *result.Result {
modVersions := moduleVersionMap(vr.Modules)
// Create Result where each vulncheck.Vuln{OSV, ModPath, PkgPath} becomes
// a separate Vuln{OSV, Modules{Packages{PkgPath}}} entry. We merge the
// results later.
- r := &Result{}
+ r := &result.Result{}
for _, vv := range vr.Vulns {
- p := &Package{Path: vv.PkgPath}
+ p := &result.Package{Path: vv.PkgPath}
// in binary mode, call stacks contain just the symbol data
- p.CallStacks = []CallStack{{Symbol: vv.Symbol}}
- m := &Module{
+ p.CallStacks = []result.CallStack{{Symbol: vv.Symbol}}
+ m := &result.Module{
Path: vv.ModPath,
FoundVersion: foundVersion(vv.ModPath, modVersions),
FixedVersion: fixedVersion(vv.ModPath, vv.OSV.Affected),
- Packages: []*Package{p},
+ Packages: []*result.Package{p},
}
- v := &Vuln{OSV: vv.OSV, Modules: []*Module{m}}
+ v := &result.Vuln{OSV: vv.OSV, Modules: []*result.Module{m}}
r.Vulns = append(r.Vulns, v)
}
@@ -136,36 +137,36 @@
// For instance, Vulns with the same OSV field are
// merged into a single one. The same applies for
// Modules of a Vuln, and Packages of a Module.
-func merge(r *Result) *Result {
- nr := &Result{}
+func merge(r *result.Result) *result.Result {
+ nr := &result.Result{}
// merge vulns by their ID. Note that there can
// be several OSVs with the same ID but different
// pointer values
osvs := make(map[string]*osv.Entry)
- vs := make(map[string][]*Module)
+ vs := make(map[string][]*result.Module)
for _, v := range r.Vulns {
osvs[v.OSV.ID] = v.OSV
vs[v.OSV.ID] = append(vs[v.OSV.ID], v.Modules...)
}
for id, mods := range vs {
- v := &Vuln{OSV: osvs[id], Modules: mods}
+ v := &result.Vuln{OSV: osvs[id], Modules: mods}
nr.Vulns = append(nr.Vulns, v)
}
// merge modules
for _, v := range nr.Vulns {
- ms := make(map[string][]*Module)
+ ms := make(map[string][]*result.Module)
for _, m := range v.Modules {
ms[m.Path] = append(ms[m.Path], m)
}
- var nms []*Module
+ var nms []*result.Module
for mpath, mods := range ms {
// modules with the same path must have
// same found and fixed versions
validateModuleVersions(mods)
- nm := &Module{
+ nm := &result.Module{
Path: mpath,
FixedVersion: mods[0].FixedVersion,
FoundVersion: mods[0].FoundVersion,
@@ -181,14 +182,14 @@
// merge packages
for _, v := range nr.Vulns {
for _, m := range v.Modules {
- ps := make(map[string][]*Package)
+ ps := make(map[string][]*result.Package)
for _, p := range m.Packages {
ps[p.Path] = append(ps[p.Path], p)
}
- var nps []*Package
+ var nps []*result.Package
for ppath, pkgs := range ps {
- np := &Package{Path: ppath}
+ np := &result.Package{Path: ppath}
for _, p := range pkgs {
np.CallStacks = append(np.CallStacks, p.CallStacks...)
}
@@ -202,7 +203,7 @@
// validateModuleVersions checks that all modules have
// the same found and fixed version. If not, panics.
-func validateModuleVersions(modules []*Module) {
+func validateModuleVersions(modules []*result.Module) {
var found, fixed string
for i, m := range modules {
if i == 0 {
@@ -217,7 +218,7 @@
}
// sortResults sorts Vulns, Modules, and Packages of r.
-func sortResult(r *Result) {
+func sortResult(r *result.Result) {
sort.Slice(r.Vulns, func(i, j int) bool {
return r.Vulns[i].OSV.ID > r.Vulns[j].OSV.ID
})
@@ -236,10 +237,10 @@
// stackFramesFromEntries creates a sequence of stack
// frames from vcs. Position of a StackFrame is the
// call position of the corresponding stack entry.
-func stackFramesfromEntries(vcs vulncheck.CallStack) []*StackFrame {
- var frames []*StackFrame
+func stackFramesfromEntries(vcs vulncheck.CallStack) []*result.StackFrame {
+ var frames []*result.StackFrame
for _, e := range vcs {
- fr := &StackFrame{
+ fr := &result.StackFrame{
FuncName: e.Function.Name,
PkgPath: e.Function.PkgPath,
RecvType: e.Function.RecvType,
diff --git a/internal/govulncheck/scan.go b/internal/govulncheck/scan.go
index 3e531d1..b8206f0 100644
--- a/internal/govulncheck/scan.go
+++ b/internal/govulncheck/scan.go
@@ -17,6 +17,7 @@
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/packages"
"golang.org/x/vuln/client"
+ "golang.org/x/vuln/internal/result"
"golang.org/x/vuln/vulncheck"
)
@@ -126,7 +127,7 @@
// config GoVersion is "", which means use current
// Go version at path.
cfg := &Config{Client: dbClient}
- var res *Result
+ var res *result.Result
if c.sourceAnalysis {
var pkgs []*vulncheck.Package
pkgs, err = loadPackages(c, dir)
diff --git a/internal/govulncheck/text.go b/internal/govulncheck/text.go
index 2f3e5cc..b454987 100644
--- a/internal/govulncheck/text.go
+++ b/internal/govulncheck/text.go
@@ -16,6 +16,7 @@
"golang.org/x/vuln/client"
"golang.org/x/vuln/internal"
+ "golang.org/x/vuln/internal/result"
"golang.org/x/vuln/osv"
"golang.org/x/vuln/vulncheck"
)
@@ -35,7 +36,7 @@
intro(ctx context.Context, dbClient client.Client, dbs []string, source bool)
// result communicates the result of running govulncheck to the user.
- result(r *Result, verbose, source bool) error
+ result(r *result.Result, verbose, source bool) error
// progress communicates a progress update to the user.
progress(msg string)
@@ -81,7 +82,7 @@
tmpl.Execute(o.to, i)
}
-func (o *readableOutput) result(r *Result, verbose, source bool) error {
+func (o *readableOutput) result(r *result.Result, verbose, source bool) error {
lineWidth := 80 - labelWidth
funcMap := template.FuncMap{
// used in template for counting vulnerabilities
@@ -133,7 +134,7 @@
// createTmplResult transforms Result r into a
// template structure for printing.
-func createTmplResult(r *Result, verbose, source bool) tmplResult {
+func createTmplResult(r *result.Result, verbose, source bool) tmplResult {
// unaffected are (imported) OSVs none of
// which vulnerabilities are called.
var unaffected []tmplVulnInfo
@@ -175,7 +176,7 @@
// createTmplVulnInfo creates a template vuln info for
// a vulnerability that is called by source code or
// present in the binary.
-func createTmplVulnInfo(v *Vuln, verbose, source bool) tmplVulnInfo {
+func createTmplVulnInfo(v *result.Vuln, verbose, source bool) tmplVulnInfo {
vInfo := tmplVulnInfo{
ID: v.OSV.ID,
Details: v.OSV.Details,
@@ -183,7 +184,7 @@
// stacks returns call stack info of p as a
// string depending on verbose and source mode.
- stacks := func(p *Package) string {
+ stacks := func(p *result.Package) string {
if !source {
return ""
}
@@ -248,7 +249,7 @@
return vInfo
}
-func defaultCallStacks(css []CallStack) string {
+func defaultCallStacks(css []result.CallStack) string {
var summaries []string
for _, cs := range css {
summaries = append(summaries, cs.Summary)
@@ -268,7 +269,7 @@
return b.String()
}
-func verboseCallStacks(css []CallStack) string {
+func verboseCallStacks(css []result.CallStack) string {
// Display one full call stack for each vuln.
i := 1
var b strings.Builder
diff --git a/internal/govulncheck/util.go b/internal/govulncheck/util.go
index 25a10c6..916004e 100644
--- a/internal/govulncheck/util.go
+++ b/internal/govulncheck/util.go
@@ -9,6 +9,7 @@
"golang.org/x/mod/semver"
"golang.org/x/vuln/internal"
+ "golang.org/x/vuln/internal/result"
isem "golang.org/x/vuln/internal/semver"
"golang.org/x/vuln/osv"
"golang.org/x/vuln/vulncheck"
@@ -64,7 +65,7 @@
// highest returns the highest (one with the smallest index) entry in the call
// stack for which f returns true.
-func highest(cs []*StackFrame, f func(e *StackFrame) bool) int {
+func highest(cs []*result.StackFrame, f func(e *result.StackFrame) bool) int {
for i := 0; i < len(cs); i++ {
if f(cs[i]) {
return i
@@ -75,7 +76,7 @@
// lowest returns the lowest (one with the largest index) entry in the call
// stack for which f returns true.
-func lowest(cs []*StackFrame, f func(e *StackFrame) bool) int {
+func lowest(cs []*result.StackFrame, f func(e *result.StackFrame) bool) int {
for i := len(cs) - 1; i >= 0; i-- {
if f(cs[i]) {
return i
diff --git a/internal/result/result.go b/internal/result/result.go
new file mode 100644
index 0000000..16eceea
--- /dev/null
+++ b/internal/result/result.go
@@ -0,0 +1,155 @@
+// 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 result contains the JSON output structs for govulncheck.
+package result
+
+import (
+ "fmt"
+ "go/token"
+ "strings"
+
+ "golang.org/x/vuln/osv"
+)
+
+// Result is the result of executing Source or Binary.
+type Result struct {
+ // Vulns contains all vulnerabilities that are called or imported by
+ // the analyzed module.
+ Vulns []*Vuln
+}
+
+// Vuln represents a single OSV entry.
+type Vuln struct {
+ // OSV contains all data from the OSV entry for this vulnerability.
+ OSV *osv.Entry
+
+ // Modules contains all of the modules in the OSV entry where a
+ // vulnerable package is imported by the target source code or binary.
+ //
+ // For example, a module M with two packages M/p1 and M/p2, where only p1
+ // is vulnerable, will appear in this list if and only if p1 is imported by
+ // the target source code or binary.
+ Modules []*Module
+}
+
+// IsCalled reports whether the vulnerability is called, therefore
+// affecting the target source code or binary.
+func (v *Vuln) IsCalled() bool {
+ for _, m := range v.Modules {
+ for _, p := range m.Packages {
+ if len(p.CallStacks) > 0 {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// Module represents a specific vulnerability relevant to a single module.
+type Module struct {
+ // Path is the module path of the module containing the vulnerability.
+ //
+ // Importable packages in the standard library will have the path "stdlib".
+ Path string
+
+ // FoundVersion is the module version where the vulnerability was found.
+ FoundVersion string
+
+ // FixedVersion is the module version where the vulnerability was
+ // fixed. If there are multiple fixed versions in the OSV report, this will
+ // be the latest fixed version.
+ //
+ // This is empty if a fix is not available.
+ FixedVersion string
+
+ // Packages contains all the vulnerable packages in OSV entry that are
+ // imported by the target source code or binary.
+ //
+ // For example, given a module M with two packages M/p1 and M/p2, where
+ // both p1 and p2 are vulnerable, p1 and p2 will each only appear in this
+ // list they are individually imported by the target source code or binary.
+ Packages []*Package
+}
+
+// Package is a Go package with known vulnerable symbols.
+type Package struct {
+ // Path is the import path of the package containing the vulnerability.
+ Path string
+
+ // CallStacks contains a representative call stack for each
+ // vulnerable symbol that is called.
+ //
+ // For vulnerabilities found from binary analysis, only CallStack.Symbol
+ // will be provided.
+ //
+ // For non-affecting vulnerabilities reported from the source mode
+ // analysis, this will be empty.
+ CallStacks []CallStack
+}
+
+// CallStacks contains a representative call stack for a vulnerable
+// symbol.
+type CallStack struct {
+ // Symbol is the name of the detected vulnerable function
+ // or method.
+ //
+ // This follows the naming convention in the OSV report.
+ Symbol string
+
+ // Summary is a one-line description of the callstack, used by the
+ // default govulncheck mode.
+ //
+ // Example: module3.main calls github.com/shiyanhui/dht.DHT.Run
+ Summary string
+
+ // Frames contains an entry for each stack in the call stack.
+ //
+ // Frames are sorted starting from the entry point to the
+ // imported vulnerable symbol. The last frame in Frames should match
+ // Symbol.
+ Frames []*StackFrame
+}
+
+// StackFrame represents a call stack entry.
+type StackFrame struct {
+ // PackagePath is the import path.
+ PkgPath string
+
+ // FuncName is the function name.
+ FuncName string
+
+ // RecvType is the fully qualified receiver type,
+ // if the called symbol is a method.
+ //
+ // The client can create the final symbol name by
+ // prepending RecvType to FuncName.
+ RecvType string
+
+ // Position describes an arbitrary source position
+ // including the file, line, and column location.
+ // A Position is valid if the line number is > 0.
+ Position token.Position
+}
+
+// Name returns the full qualified function name from sf,
+// adjusted to remove pointer annotations.
+func (sf *StackFrame) Name() string {
+ var n string
+ if sf.RecvType == "" {
+ n = fmt.Sprintf("%s.%s", sf.PkgPath, sf.FuncName)
+ } else {
+ n = fmt.Sprintf("%s.%s", sf.RecvType, sf.FuncName)
+ }
+ return strings.TrimPrefix(n, "*")
+}
+
+// Pos returns the position of the call in sf as string.
+// If position is not available, return "".
+func (sf *StackFrame) Pos() string {
+ if sf.Position.IsValid() {
+ return sf.Position.String()
+ }
+ return ""
+}