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 }