src

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

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 }