File: binary.go

package info (click to toggle)
golang-golang-x-vuln 1.0.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,400 kB
  • sloc: sh: 161; asm: 40; makefile: 7
file content (168 lines) | stat: -rw-r--r-- 5,464 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Copyright 2021 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.

//go:build go1.18
// +build go1.18

package vulncheck

import (
	"context"
	"fmt"
	"sort"

	"golang.org/x/tools/go/packages"
	"golang.org/x/vuln/internal"
	"golang.org/x/vuln/internal/buildinfo"
	"golang.org/x/vuln/internal/client"
	"golang.org/x/vuln/internal/govulncheck"
)

// Bin is an abstraction of Go binary containing
// minimal information needed by govulncheck.
type Bin struct {
	Modules    []*packages.Module `json:"modules,omitempty"`
	PkgSymbols []buildinfo.Symbol `json:"pkgSymbols,omitempty"`
	GoVersion  string             `json:"goVersion,omitempty"`
	GOOS       string             `json:"goos,omitempty"`
	GOARCH     string             `json:"goarch,omitempty"`
}

// Binary detects presence of vulnerable symbols in bin and
// emits findings to handler.
func Binary(ctx context.Context, handler govulncheck.Handler, bin *Bin, cfg *govulncheck.Config, client *client.Client) error {
	vr, err := binary(ctx, handler, bin, cfg, client)
	if err != nil {
		return err
	}
	if cfg.ScanLevel.WantSymbols() {
		return emitCallFindings(handler, binaryCallstacks(vr))
	}
	return nil
}

// binary detects presence of vulnerable symbols in bin.
// It does not compute call graphs so the corresponding
// info in Result will be empty.
func binary(ctx context.Context, handler govulncheck.Handler, bin *Bin, cfg *govulncheck.Config, client *client.Client) (*Result, error) {
	graph := NewPackageGraph(bin.GoVersion)
	graph.AddModules(bin.Modules...)
	mods := append(bin.Modules, graph.GetModule(internal.GoStdModulePath))

	mv, err := FetchVulnerabilities(ctx, client, mods)
	if err != nil {
		return nil, err
	}

	// Emit OSV entries immediately in their raw unfiltered form.
	if err := emitOSVs(handler, mv); err != nil {
		return nil, err
	}

	if bin.GOOS == "" || bin.GOARCH == "" {
		fmt.Printf("warning: failed to extract build system specification GOOS: %s GOARCH: %s\n", bin.GOOS, bin.GOARCH)
	}
	affVulns := affectingVulnerabilities(mv, bin.GOOS, bin.GOARCH)
	if err := emitModuleFindings(handler, affVulns); err != nil {
		return nil, err
	}

	if !cfg.ScanLevel.WantPackages() || len(affVulns) == 0 {
		return &Result{}, nil
	}

	// Group symbols per package to avoid querying affVulns all over again.
	var pkgSymbols map[string][]string
	if len(bin.PkgSymbols) == 0 {
		// The binary exe is stripped. We currently cannot detect inlined
		// symbols for stripped binaries (see #57764), so we report
		// vulnerabilities at the go.mod-level precision.
		pkgSymbols = allKnownVulnerableSymbols(affVulns)
	} else {
		pkgSymbols = make(map[string][]string)
		for _, sym := range bin.PkgSymbols {
			pkgSymbols[sym.Pkg] = append(pkgSymbols[sym.Pkg], sym.Name)
		}
	}

	impVulns := binImportedVulnPackages(graph, pkgSymbols, affVulns)
	// Emit information on imported vulnerable packages now to
	// mimic behavior of source.
	if err := emitPackageFindings(handler, impVulns); err != nil {
		return nil, err
	}

	// Return result immediately if not in symbol mode to mimic the
	// behavior of source.
	if !cfg.ScanLevel.WantSymbols() || len(impVulns) == 0 {
		return &Result{Vulns: impVulns}, nil
	}

	symVulns := binVulnSymbols(graph, pkgSymbols, affVulns)
	return &Result{Vulns: symVulns}, nil
}

func binImportedVulnPackages(graph *PackageGraph, pkgSymbols map[string][]string, affVulns affectingVulns) []*Vuln {
	var vulns []*Vuln
	for pkg := range pkgSymbols {
		for _, osv := range affVulns.ForPackage(pkg) {
			vuln := &Vuln{
				OSV:     osv,
				Package: graph.GetPackage(pkg),
			}
			vulns = append(vulns, vuln)
		}
	}
	return vulns
}

func binVulnSymbols(graph *PackageGraph, pkgSymbols map[string][]string, affVulns affectingVulns) []*Vuln {
	var vulns []*Vuln
	for pkg, symbols := range pkgSymbols {
		// sort symbols for deterministic results
		sort.SliceStable(symbols, func(i, j int) bool { return symbols[i] < symbols[j] })
		for _, symbol := range symbols {
			for _, osv := range affVulns.ForSymbol(pkg, symbol) {
				vuln := &Vuln{
					OSV:     osv,
					Symbol:  symbol,
					Package: graph.GetPackage(pkg),
				}
				vulns = append(vulns, vuln)
			}
		}
	}
	return vulns
}

// allKnownVulnerableSymbols returns all known vulnerable symbols for packages in graph.
// If all symbols of a package are vulnerable, that is modeled as a wild car symbol "<pkg-path>/*".
func allKnownVulnerableSymbols(affVulns affectingVulns) map[string][]string {
	pkgSymbols := make(map[string][]string)
	for _, mv := range affVulns {
		for _, osv := range mv.Vulns {
			for _, affected := range osv.Affected {
				for _, p := range affected.EcosystemSpecific.Packages {
					syms := p.Symbols
					if len(syms) == 0 {
						// If every symbol of pkg is vulnerable, we would ideally
						// compute every symbol mentioned in the pkg and then add
						// Vuln entry for it, just as we do in Source. However,
						// we don't have code of pkg here and we don't even have
						// pkg symbols used in stripped binary, so we add a placeholder
						// symbol.
						//
						// Note: this should not affect output of govulncheck since
						// in binary mode no symbol/call stack information is
						// communicated back to the user.
						syms = []string{fmt.Sprintf("%s/*", p.Path)}
					}

					pkgSymbols[p.Path] = append(pkgSymbols[p.Path], syms...)
				}
			}
		}
	}
	return pkgSymbols
}