vulndb/internal/audit: improve support for init functions
For top-level main packages, init functions of those packages as well
as init functions of transitive dependencies should be considered entry
points. This CL ensures this is done correctly.
Change-Id: Ic282ee53630044854eb8d12307436bc462c9f7ba
Reviewed-on: https://go-review.googlesource.com/c/exp/+/348229
Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Tim King <taking@google.com>
Trust: Zvonimir Pavlinovic <zpavlinovic@google.com>
diff --git a/vulndb/internal/audit/detect_callgraph_test.go b/vulndb/internal/audit/detect_callgraph_test.go
index 4813770..4753d1f 100644
--- a/vulndb/internal/audit/detect_callgraph_test.go
+++ b/vulndb/internal/audit/detect_callgraph_test.go
@@ -8,6 +8,10 @@
"go/token"
"reflect"
"testing"
+
+ "golang.org/x/tools/go/packages"
+ "golang.org/x/tools/go/packages/packagestest"
+ "golang.org/x/vulndb/osv"
)
func TestSymbolVulnDetectionVTA(t *testing.T) {
@@ -80,3 +84,76 @@
}
}
}
+
+func TestInitReachability(t *testing.T) {
+ e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{
+ {
+ Name: "golang.org/inittest",
+ Files: map[string]interface{}{"main.go": `
+ package main
+
+ import "example.com/vuln"
+
+ func main() {
+ vuln.Foo() // benign
+ }
+ `},
+ },
+ {
+ Name: "example.com@v1.1.1",
+ Files: map[string]interface{}{"vuln/vuln.go": `
+ package vuln
+
+ func init() {
+ Bad() // bad
+ }
+
+ func Foo() {}
+ func Bad() {}
+ `},
+ },
+ })
+ defer e.Cleanup()
+
+ _, pkgs, _, err := loadAndBuildPackages(e, "/inittest/main.go")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ modVulns := ModuleVulnerabilities{
+ {
+ mod: &packages.Module{Path: "example.com", Version: "v1.1.1"},
+ vulns: []*osv.Entry{
+ {
+ ID: "V3",
+ Affected: []osv.Affected{{
+ Package: osv.Package{Name: "example.com/vuln"},
+ Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "1.1.2"}}}},
+ EcosystemSpecific: osv.EcosystemSpecific{Symbols: []string{"Bad"}},
+ }},
+ },
+ },
+ },
+ }
+ results := VulnerableSymbols(pkgs, modVulns)
+
+ if results.SearchMode != CallGraphSearch {
+ t.Errorf("want call graph search mode; got %v", results.SearchMode)
+ }
+
+ want := []Finding{
+ {
+ Symbol: "example.com/vuln.Bad",
+ Trace: []TraceElem{
+ {Description: "command-line-arguments.init(...)", Position: &token.Position{}},
+ {Description: "example.com/vuln.init(...)", Position: &token.Position{}},
+ {Description: "example.com/vuln.init#1(...)", Position: &token.Position{}}},
+ Type: FunctionType,
+ Position: &token.Position{Line: 5, Filename: "vuln.go"},
+ weight: 0,
+ },
+ }
+ if got := projectFindings(results.VulnFindings["V3"]); !reflect.DeepEqual(want, got) {
+ t.Errorf("want %v findings (projected); got %v", want, got)
+ }
+}
diff --git a/vulndb/internal/audit/utils.go b/vulndb/internal/audit/utils.go
index c7395a7..8fe5609 100644
--- a/vulndb/internal/audit/utils.go
+++ b/vulndb/internal/audit/utils.go
@@ -51,8 +51,9 @@
for _, edge := range node.Out {
callee := edge.Callee.Func
- // Skip synthetic functions wrapped around source functions.
- if edge.Site == call && callee.Synthetic == "" {
+ // Some callgraph analyses, such as CHA, might return synthetic (interface)
+ // methods as well as the concrete methods. Skip such synthetic functions.
+ if edge.Site == call {
matches = append(matches, callee)
}
}