| // 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 symbols |
| |
| import ( |
| "context" |
| "fmt" |
| "go/token" |
| |
| "golang.org/x/tools/go/callgraph" |
| "golang.org/x/tools/go/packages" |
| "golang.org/x/tools/go/ssa" |
| "golang.org/x/vulndb/internal/report" |
| ) |
| |
| // vulnEntries returns entries of pkgs call graph that lead to |
| // vulnerable symbols in m. |
| // |
| // It assumes that the modules in m present in pkgs, if any, |
| // are at a version deemed vulnerable by m. |
| func vulnEntries(pkgs []*packages.Package, m *report.Module) ([]*ssa.Function, error) { |
| ctx := context.Background() |
| |
| // The following code block is copied from |
| // golang.org/x/vuln/internal/vulncheck/source.go:Source. |
| var fset *token.FileSet |
| for _, p := range pkgs { |
| if fset == nil { |
| fset = p.Fset |
| } else { |
| if fset != p.Fset { |
| return nil, fmt.Errorf("[]*Package must have created with the same FileSet") |
| } |
| } |
| } |
| prog, ssaPkgs := buildSSA(pkgs, fset) |
| entries := entryPoints(ssaPkgs) |
| cg, err := callGraph(ctx, prog, entries) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Identify vulnerable functions/methods in the call graph and |
| // compute the backwards reachable entries. |
| entryNodes := vulnReachingEntries(vulnFuncs(cg, m), entries) |
| var vres []*ssa.Function |
| for _, n := range entryNodes { |
| vres = append(vres, n.Func) |
| } |
| return vres, nil |
| } |
| |
| // vulnFuncs returns functions/methods of cg deemed vulnerable by m. |
| // |
| // It mimics golang.org/x/vuln/internal/vulncheck/source.go:vulnFuncs. |
| func vulnFuncs(cg *callgraph.Graph, m *report.Module) []*callgraph.Node { |
| // Create a set of vulnerable symbols easy to query. |
| type vulnSym struct { |
| pkg string |
| sym string |
| } |
| vulnSyms := make(map[vulnSym]bool) |
| for _, p := range m.Packages { |
| for _, s := range p.Symbols { |
| vulnSyms[vulnSym{p.Package, s}] = true |
| } |
| for _, s := range p.DerivedSymbols { // for sanity |
| vulnSyms[vulnSym{p.Package, s}] = true |
| } |
| } |
| |
| var vfs []*callgraph.Node |
| for f, n := range cg.Nodes { |
| if vulnSyms[vulnSym{pkgPath(f), dbFuncName(f)}] { |
| vfs = append(vfs, n) |
| } |
| } |
| return vfs |
| } |
| |
| // vulnReachingEntries returns call graph nodes corresponding to allEntries |
| // that are backwards reachable from sinks. |
| func vulnReachingEntries(sinks []*callgraph.Node, allEntries []*ssa.Function) []*callgraph.Node { |
| allEs := make(map[*ssa.Function]bool) |
| for _, e := range allEntries { |
| allEs[e] = true |
| } |
| |
| var vres []*callgraph.Node |
| // The following code block mimics the body of |
| // golang.org/x/vuln/internal/vulncheck/source.go:callGraphSlice. |
| visited := make(map[*callgraph.Node]bool) |
| var visit func(*callgraph.Node) |
| visit = func(n *callgraph.Node) { |
| if visited[n] { |
| return |
| } |
| visited[n] = true |
| |
| if allEs[n.Func] { |
| vres = append(vres, n) |
| } |
| |
| for _, edge := range n.In { |
| visit(edge.Caller) |
| } |
| } |
| |
| for _, s := range sinks { |
| visit(s) |
| } |
| return vres |
| } |