emit.go (5312B)
1 // Copyright 2023 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 "go/token" 9 "os" 10 "path/filepath" 11 "strings" 12 13 "golang.org/x/tools/go/packages" 14 "golang.org/x/vuln/internal/govulncheck" 15 ) 16 17 // emitOSVs emits all OSV vuln entries in modVulns to handler. 18 func emitOSVs(handler govulncheck.Handler, modVulns []*ModVulns) error { 19 for _, mv := range modVulns { 20 for _, v := range mv.Vulns { 21 if err := handler.OSV(v); err != nil { 22 return err 23 } 24 } 25 } 26 return nil 27 } 28 29 // emitModuleFindings emits module-level findings for vulnerabilities in modVulns. 30 func emitModuleFindings(handler govulncheck.Handler, affVulns affectingVulns) error { 31 for _, vuln := range affVulns { 32 for _, osv := range vuln.Vulns { 33 if err := handler.Finding(&govulncheck.Finding{ 34 OSV: osv.ID, 35 FixedVersion: FixedVersion(modPath(vuln.Module), modVersion(vuln.Module), osv.Affected), 36 Trace: []*govulncheck.Frame{frameFromModule(vuln.Module)}, 37 }); err != nil { 38 return err 39 } 40 } 41 } 42 return nil 43 } 44 45 // emitPackageFinding emits package-level findings fod vulnerabilities in vulns. 46 func emitPackageFindings(handler govulncheck.Handler, vulns []*Vuln) error { 47 for _, v := range vulns { 48 if err := handler.Finding(&govulncheck.Finding{ 49 OSV: v.OSV.ID, 50 FixedVersion: FixedVersion(modPath(v.Package.Module), modVersion(v.Package.Module), v.OSV.Affected), 51 Trace: []*govulncheck.Frame{frameFromPackage(v.Package)}, 52 }); err != nil { 53 return err 54 } 55 } 56 return nil 57 } 58 59 // emitCallFindings emits call-level findings for vulnerabilities 60 // that have a call stack in callstacks. 61 func emitCallFindings(handler govulncheck.Handler, callstacks map[*Vuln]CallStack) error { 62 var vulns []*Vuln 63 for v := range callstacks { 64 vulns = append(vulns, v) 65 } 66 67 for _, vuln := range vulns { 68 stack := callstacks[vuln] 69 if stack == nil { 70 continue 71 } 72 fixed := FixedVersion(modPath(vuln.Package.Module), modVersion(vuln.Package.Module), vuln.OSV.Affected) 73 if err := handler.Finding(&govulncheck.Finding{ 74 OSV: vuln.OSV.ID, 75 FixedVersion: fixed, 76 Trace: traceFromEntries(stack), 77 }); err != nil { 78 return err 79 } 80 } 81 return nil 82 } 83 84 // traceFromEntries creates a sequence of 85 // frames from vcs. Position of a Frame is the 86 // call position of the corresponding stack entry. 87 func traceFromEntries(vcs CallStack) []*govulncheck.Frame { 88 var frames []*govulncheck.Frame 89 for i := len(vcs) - 1; i >= 0; i-- { 90 e := vcs[i] 91 fr := frameFromPackage(e.Function.Package) 92 fr.Function = e.Function.Name 93 fr.Receiver = e.Function.Receiver() 94 isSink := i == (len(vcs) - 1) 95 fr.Position = posFromStackEntry(e, isSink) 96 frames = append(frames, fr) 97 } 98 return frames 99 } 100 101 func posFromStackEntry(e StackEntry, sink bool) *govulncheck.Position { 102 var p *token.Position 103 var f *FuncNode 104 if sink && e.Function != nil && e.Function.Pos != nil { 105 // For sinks, i.e., vulns we take the position 106 // of the symbol. 107 p = e.Function.Pos 108 f = e.Function 109 } else if e.Call != nil && e.Call.Pos != nil { 110 // Otherwise, we take the position of 111 // the call statement. 112 p = e.Call.Pos 113 f = e.Call.Parent 114 } 115 116 if p == nil { 117 return nil 118 } 119 return &govulncheck.Position{ 120 Filename: pathRelativeToMod(p.Filename, f), 121 Offset: p.Offset, 122 Line: p.Line, 123 Column: p.Column, 124 } 125 } 126 127 // pathRelativeToMod computes a version of path 128 // relative to the module of f. If it does not 129 // have all the necessary information, returns 130 // an empty string. 131 // 132 // The returned paths always use slash as separator 133 // so they can work across different platforms. 134 func pathRelativeToMod(path string, f *FuncNode) string { 135 if path == "" || f == nil || f.Package == nil { // sanity 136 return "" 137 } 138 139 mod := f.Package.Module 140 if mod.Replace != nil { 141 mod = mod.Replace // for replace directive 142 } 143 144 modDir := modDirWithVendor(mod.Dir, path, mod.Path) 145 p, err := filepath.Rel(modDir, path) 146 if err != nil { 147 return "" 148 } 149 // make sure paths are portable. 150 return filepath.ToSlash(p) 151 } 152 153 // modDirWithVendor returns modDir if modDir is not empty. 154 // Otherwise, the module might be located in the vendor 155 // directory. This function attempts to reconstruct the 156 // vendored module directory from path and module. It 157 // returns an empty string if reconstruction fails. 158 func modDirWithVendor(modDir, path, module string) string { 159 if modDir != "" { 160 return modDir 161 } 162 163 sep := string(os.PathSeparator) 164 vendor := sep + "vendor" + sep 165 vendorIndex := strings.Index(path, vendor) 166 if vendorIndex == -1 { 167 return "" 168 } 169 return filepath.Join(path[:vendorIndex], "vendor", filepath.FromSlash(module)) 170 } 171 172 func frameFromPackage(pkg *packages.Package) *govulncheck.Frame { 173 fr := &govulncheck.Frame{} 174 if pkg != nil { 175 fr.Module = pkg.Module.Path 176 fr.Version = pkg.Module.Version 177 fr.Package = pkg.PkgPath 178 } 179 if pkg.Module.Replace != nil { 180 fr.Module = pkg.Module.Replace.Path 181 fr.Version = pkg.Module.Replace.Version 182 } 183 return fr 184 } 185 186 func frameFromModule(mod *packages.Module) *govulncheck.Frame { 187 fr := &govulncheck.Frame{ 188 Module: mod.Path, 189 Version: mod.Version, 190 } 191 192 if mod.Replace != nil { 193 fr.Module = mod.Replace.Path 194 fr.Version = mod.Replace.Version 195 } 196 197 return fr 198 }