src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

utils.go (10010B)


      1 // Copyright 2021 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package vulncheck
      6 
      7 import (
      8 	"bytes"
      9 	"context"
     10 	"go/token"
     11 	"go/types"
     12 	"sort"
     13 	"strings"
     14 
     15 	"golang.org/x/tools/go/callgraph"
     16 	"golang.org/x/tools/go/callgraph/cha"
     17 	"golang.org/x/tools/go/callgraph/vta"
     18 	"golang.org/x/tools/go/packages"
     19 	"golang.org/x/tools/go/types/typeutil"
     20 	"golang.org/x/vuln/internal"
     21 	"golang.org/x/vuln/internal/osv"
     22 	"golang.org/x/vuln/internal/semver"
     23 
     24 	"golang.org/x/tools/go/ssa"
     25 )
     26 
     27 // buildSSA creates an ssa representation for pkgs. Returns
     28 // the ssa program encapsulating the packages and top level
     29 // ssa packages corresponding to pkgs.
     30 func buildSSA(pkgs []*packages.Package, fset *token.FileSet) (*ssa.Program, []*ssa.Package) {
     31 	prog := ssa.NewProgram(fset, ssa.InstantiateGenerics)
     32 
     33 	imports := make(map[*packages.Package]*ssa.Package)
     34 	var createImports func(map[string]*packages.Package)
     35 	createImports = func(pkgs map[string]*packages.Package) {
     36 		for _, p := range pkgs {
     37 			if _, ok := imports[p]; !ok {
     38 				i := prog.CreatePackage(p.Types, p.Syntax, p.TypesInfo, true)
     39 				imports[p] = i
     40 				createImports(p.Imports)
     41 			}
     42 		}
     43 	}
     44 
     45 	for _, tp := range pkgs {
     46 		createImports(tp.Imports)
     47 	}
     48 
     49 	var ssaPkgs []*ssa.Package
     50 	for _, tp := range pkgs {
     51 		if sp, ok := imports[tp]; ok {
     52 			ssaPkgs = append(ssaPkgs, sp)
     53 		} else {
     54 			sp := prog.CreatePackage(tp.Types, tp.Syntax, tp.TypesInfo, false)
     55 			ssaPkgs = append(ssaPkgs, sp)
     56 		}
     57 	}
     58 	prog.Build()
     59 	return prog, ssaPkgs
     60 }
     61 
     62 // callGraph builds a call graph of prog based on VTA analysis.
     63 func callGraph(ctx context.Context, prog *ssa.Program, entries []*ssa.Function) (*callgraph.Graph, error) {
     64 	entrySlice := make(map[*ssa.Function]bool)
     65 	for _, e := range entries {
     66 		entrySlice[e] = true
     67 	}
     68 
     69 	if err := ctx.Err(); err != nil { // cancelled?
     70 		return nil, err
     71 	}
     72 	initial := cha.CallGraph(prog)
     73 
     74 	fslice := forwardSlice(entrySlice, initial)
     75 	if err := ctx.Err(); err != nil { // cancelled?
     76 		return nil, err
     77 	}
     78 	vtaCg := vta.CallGraph(fslice, initial)
     79 
     80 	// Repeat the process once more, this time using
     81 	// the produced VTA call graph as the base graph.
     82 	fslice = forwardSlice(entrySlice, vtaCg)
     83 	if err := ctx.Err(); err != nil { // cancelled?
     84 		return nil, err
     85 	}
     86 	cg := vta.CallGraph(fslice, vtaCg)
     87 	cg.DeleteSyntheticNodes()
     88 	return cg, nil
     89 }
     90 
     91 // dbTypeFormat formats the name of t according how types
     92 // are encoded in vulnerability database:
     93 //   - pointer designation * is skipped
     94 //   - full path prefix is skipped as well
     95 func dbTypeFormat(t types.Type) string {
     96 	switch tt := types.Unalias(t).(type) {
     97 	case *types.Pointer:
     98 		return dbTypeFormat(tt.Elem())
     99 	case *types.Named:
    100 		return tt.Obj().Name()
    101 	default:
    102 		return types.TypeString(t, func(p *types.Package) string { return "" })
    103 	}
    104 }
    105 
    106 // dbFuncName computes a function name consistent with the namings used in vulnerability
    107 // databases. Effectively, a qualified name of a function local to its enclosing package.
    108 // If a receiver is a pointer, this information is not encoded in the resulting name. If
    109 // a function has type argument/parameter, this information is omitted. The name of
    110 // anonymous functions is simply "". The function names are unique subject to the enclosing
    111 // package, but not globally.
    112 //
    113 // Examples:
    114 //
    115 //	func (a A) foo (...) {...}  -> A.foo
    116 //	func foo(...) {...}         -> foo
    117 //	func (b *B) bar (...) {...} -> B.bar
    118 //	func (c C[T]) do(...) {...} -> C.do
    119 func dbFuncName(f *ssa.Function) string {
    120 	selectBound := func(f *ssa.Function) types.Type {
    121 		// If f is a "bound" function introduced by ssa for a given type, return the type.
    122 		// When "f" is a "bound" function, it will have 1 free variable of that type within
    123 		// the function. This is subject to change when ssa changes.
    124 		if len(f.FreeVars) == 1 && strings.HasPrefix(f.Synthetic, "bound ") {
    125 			return f.FreeVars[0].Type()
    126 		}
    127 		return nil
    128 	}
    129 	selectThunk := func(f *ssa.Function) types.Type {
    130 		// If f is a "thunk" function introduced by ssa for a given type, return the type.
    131 		// When "f" is a "thunk" function, the first parameter will have that type within
    132 		// the function. This is subject to change when ssa changes.
    133 		params := f.Signature.Params() // params.Len() == 1 then params != nil.
    134 		if strings.HasPrefix(f.Synthetic, "thunk ") && params.Len() >= 1 {
    135 			if first := params.At(0); first != nil {
    136 				return first.Type()
    137 			}
    138 		}
    139 		return nil
    140 	}
    141 	var qprefix string
    142 	if recv := f.Signature.Recv(); recv != nil {
    143 		qprefix = dbTypeFormat(recv.Type())
    144 	} else if btype := selectBound(f); btype != nil {
    145 		qprefix = dbTypeFormat(btype)
    146 	} else if ttype := selectThunk(f); ttype != nil {
    147 		qprefix = dbTypeFormat(ttype)
    148 	}
    149 
    150 	if qprefix == "" {
    151 		return funcName(f)
    152 	}
    153 	return qprefix + "." + funcName(f)
    154 }
    155 
    156 // funcName returns the name of the ssa function f.
    157 // It is f.Name() without additional type argument
    158 // information in case of generics.
    159 func funcName(f *ssa.Function) string {
    160 	n, _, _ := strings.Cut(f.Name(), "[")
    161 	return n
    162 }
    163 
    164 // memberFuncs returns functions associated with the `member`:
    165 // 1) `member` itself if `member` is a function
    166 // 2) `member` methods if `member` is a type
    167 // 3) empty list otherwise
    168 func memberFuncs(member ssa.Member, prog *ssa.Program) []*ssa.Function {
    169 	switch t := member.(type) {
    170 	case *ssa.Type:
    171 		methods := typeutil.IntuitiveMethodSet(t.Type(), &prog.MethodSets)
    172 		var funcs []*ssa.Function
    173 		for _, m := range methods {
    174 			if f := prog.MethodValue(m); f != nil {
    175 				funcs = append(funcs, f)
    176 			}
    177 		}
    178 		return funcs
    179 	case *ssa.Function:
    180 		return []*ssa.Function{t}
    181 	default:
    182 		return nil
    183 	}
    184 }
    185 
    186 // funcPosition gives the position of `f`. Returns empty token.Position
    187 // if no file information on `f` is available.
    188 func funcPosition(f *ssa.Function) *token.Position {
    189 	pos := f.Prog.Fset.Position(f.Pos())
    190 	return &pos
    191 }
    192 
    193 // instrPosition gives the position of `instr`. Returns empty token.Position
    194 // if no file information on `instr` is available.
    195 func instrPosition(instr ssa.Instruction) *token.Position {
    196 	pos := instr.Parent().Prog.Fset.Position(instr.Pos())
    197 	return &pos
    198 }
    199 
    200 func resolved(call ssa.CallInstruction) bool {
    201 	if call == nil {
    202 		return true
    203 	}
    204 	return call.Common().StaticCallee() != nil
    205 }
    206 
    207 func callRecvType(call ssa.CallInstruction) string {
    208 	if !call.Common().IsInvoke() {
    209 		return ""
    210 	}
    211 	buf := new(bytes.Buffer)
    212 	types.WriteType(buf, call.Common().Value.Type(), nil)
    213 	return buf.String()
    214 }
    215 
    216 func funcRecvType(f *ssa.Function) string {
    217 	v := f.Signature.Recv()
    218 	if v == nil {
    219 		return ""
    220 	}
    221 	buf := new(bytes.Buffer)
    222 	types.WriteType(buf, v.Type(), nil)
    223 	return buf.String()
    224 }
    225 
    226 func FixedVersion(modulePath, version string, affected []osv.Affected) string {
    227 	fixed := earliestValidFix(modulePath, version, affected)
    228 	// Add "v" prefix if one does not exist. moduleVersionString
    229 	// will later on replace it with "go" if needed.
    230 	if fixed != "" && !strings.HasPrefix(fixed, "v") {
    231 		fixed = "v" + fixed
    232 	}
    233 	return fixed
    234 }
    235 
    236 // earliestValidFix returns the earliest fix for version of modulePath that
    237 // itself is not vulnerable in affected.
    238 //
    239 // Suppose we have a version "v1.0.0" and we use {...} to denote different
    240 // affected regions. Assume for simplicity that all affected apply to the
    241 // same input modulePath.
    242 //
    243 //	{[v0.1.0, v0.1.9), [v1.0.0, v2.0.0)} -> v2.0.0
    244 //	{[v1.0.0, v1.5.0), [v2.0.0, v2.1.0}, {[v1.4.0, v1.6.0)} -> v2.1.0
    245 func earliestValidFix(modulePath, version string, affected []osv.Affected) string {
    246 	var moduleAffected []osv.Affected
    247 	for _, a := range affected {
    248 		if a.Module.Path == modulePath {
    249 			moduleAffected = append(moduleAffected, a)
    250 		}
    251 	}
    252 
    253 	vFixes := validFixes(version, moduleAffected)
    254 	for _, fix := range vFixes {
    255 		if !fixNegated(fix, moduleAffected) {
    256 			return fix
    257 		}
    258 	}
    259 	return ""
    260 
    261 }
    262 
    263 // validFixes computes all fixes for version in affected and
    264 // returns them sorted increasingly. Assumes that all affected
    265 // apply to the same module.
    266 func validFixes(version string, affected []osv.Affected) []string {
    267 	var fixes []string
    268 	for _, a := range affected {
    269 		for _, r := range a.Ranges {
    270 			if r.Type != osv.RangeTypeSemver {
    271 				continue
    272 			}
    273 			for _, e := range r.Events {
    274 				fix := e.Fixed
    275 				if fix != "" && semver.Less(version, fix) {
    276 					fixes = append(fixes, fix)
    277 				}
    278 			}
    279 		}
    280 	}
    281 	sort.SliceStable(fixes, func(i, j int) bool { return semver.Less(fixes[i], fixes[j]) })
    282 	return fixes
    283 }
    284 
    285 // fixNegated checks if fix is negated to by a re-introduction
    286 // of a vulnerability in affected. Assumes that all affected apply
    287 // to the same module.
    288 func fixNegated(fix string, affected []osv.Affected) bool {
    289 	for _, a := range affected {
    290 		for _, r := range a.Ranges {
    291 			if semver.ContainsSemver(r, fix) {
    292 				return true
    293 			}
    294 		}
    295 	}
    296 	return false
    297 }
    298 
    299 func modPath(mod *packages.Module) string {
    300 	if mod.Replace != nil {
    301 		return mod.Replace.Path
    302 	}
    303 	return mod.Path
    304 }
    305 
    306 func modVersion(mod *packages.Module) string {
    307 	if mod.Replace != nil {
    308 		return mod.Replace.Version
    309 	}
    310 	return mod.Version
    311 }
    312 
    313 // pkgPath returns the path of the f's enclosing package, if any.
    314 // Otherwise, returns internal.UnknownPackagePath.
    315 func pkgPath(f *ssa.Function) string {
    316 	g := f
    317 	if f.Origin() != nil {
    318 		// Instantiations of generics do not have
    319 		// an associated package. We hence look up
    320 		// the original function for the package.
    321 		g = f.Origin()
    322 	}
    323 	if g.Package() != nil && g.Package().Pkg != nil {
    324 		return g.Package().Pkg.Path()
    325 	}
    326 	return internal.UnknownPackagePath
    327 }
    328 
    329 func pkgModPath(pkg *packages.Package) string {
    330 	if pkg != nil && pkg.Module != nil {
    331 		return pkg.Module.Path
    332 	}
    333 	return internal.UnknownModulePath
    334 }
    335 
    336 func IsStdPackage(pkg string) bool {
    337 	if pkg == "" || pkg == internal.UnknownPackagePath {
    338 		return false
    339 	}
    340 	// std packages do not have a "." in their path. For instance, see
    341 	// Contains in pkgsite/+/refs/heads/master/internal/stdlbib/stdlib.go.
    342 	if i := strings.IndexByte(pkg, '/'); i != -1 {
    343 		pkg = pkg[:i]
    344 	}
    345 	return !strings.Contains(pkg, ".")
    346 }