Initialize module and dependencies
This commit is contained in:
27
vendor/golang.org/x/vuln/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/vuln/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright 2009 The Go Authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
22
vendor/golang.org/x/vuln/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/vuln/PATENTS
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
||||
106
vendor/golang.org/x/vuln/cmd/govulncheck/doc.go
generated
vendored
Normal file
106
vendor/golang.org/x/vuln/cmd/govulncheck/doc.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
/*
|
||||
Govulncheck reports known vulnerabilities that affect Go code. It uses static
|
||||
analysis of source code or a binary's symbol table to narrow down reports to
|
||||
only those that could affect the application.
|
||||
|
||||
By default, govulncheck makes requests to the Go vulnerability database at
|
||||
https://vuln.go.dev. Requests to the vulnerability database contain only module
|
||||
paths with vulnerabilities already known to the database, not code or other
|
||||
properties of your program. See https://vuln.go.dev/privacy.html for more.
|
||||
Use the -db flag to specify a different database, which must implement the
|
||||
specification at https://go.dev/security/vuln/database.
|
||||
|
||||
Govulncheck looks for vulnerabilities in Go programs using a specific build
|
||||
configuration. For analyzing source code, that configuration is the Go version
|
||||
specified by the “go” command found on the PATH. For binaries, the build
|
||||
configuration is the one used to build the binary. Note that different build
|
||||
configurations may have different known vulnerabilities.
|
||||
|
||||
# Usage
|
||||
|
||||
To analyze source code, run govulncheck from the module directory, using the
|
||||
same package path syntax that the go command uses:
|
||||
|
||||
$ cd my-module
|
||||
$ govulncheck ./...
|
||||
|
||||
If no vulnerabilities are found, govulncheck will display a short message. If
|
||||
there are vulnerabilities, each is displayed briefly, with a summary of a call
|
||||
stack. The summary shows in brief how the package calls a vulnerable function.
|
||||
For example, it might say
|
||||
|
||||
main.go:[line]:[column]: mypackage.main calls golang.org/x/text/language.Parse
|
||||
|
||||
To control which files are processed, use the -tags flag to provide a
|
||||
comma-separated list of build tags, and the -test flag to indicate that test
|
||||
files should be included.
|
||||
|
||||
To include more detailed stack traces, pass '-show traces', this will cause it to
|
||||
print the full call stack for each entry.
|
||||
|
||||
To include progress messages and more details on findings, pass '-show verbose'.
|
||||
|
||||
To run govulncheck on a compiled binary, pass it the path to the binary file
|
||||
with the '-mode binary' flag:
|
||||
|
||||
$ govulncheck -mode binary $HOME/go/bin/my-go-program
|
||||
|
||||
Govulncheck uses the binary's symbol information to find mentions of vulnerable
|
||||
functions. These functions can belong to binary's transitive dependencies and
|
||||
also the main module of the binary. The latter functions are checked for only
|
||||
when the precise version of the binary module is known. Govulncheck output on
|
||||
binaries omits call stacks, which require source code analysis.
|
||||
|
||||
Govulncheck also supports '-mode extract' on a Go binary for extraction of minimal
|
||||
information needed to analyze the binary. This will produce a blob, typically much
|
||||
smaller than the binary, that can also be passed to govulncheck as an argument with
|
||||
'-mode binary'. The users should not rely on the contents or representation of the blob.
|
||||
|
||||
# Integrations
|
||||
|
||||
Govulncheck supports streaming JSON. For more details, please see [golang.org/x/vuln/internal/govulncheck].
|
||||
|
||||
Govulncheck also supports Static Analysis Results Interchange Format (SARIF) output
|
||||
format, following the specification at https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=sarif.
|
||||
For more details, please see [golang.org/x/vuln/internal/sarif].
|
||||
|
||||
Govulncheck supports the Vulnerability EXchange (VEX) output format, following
|
||||
the specification at https://github.com/openvex/spec.
|
||||
For more details, please see [golang.org/x/vuln/internal/openvex].
|
||||
|
||||
# Exit codes
|
||||
|
||||
Govulncheck exits successfully (exit code 0) if there are no vulnerabilities,
|
||||
and exits unsuccessfully if there are. It also exits successfully if the
|
||||
'format -json' ('-json'), '-format sarif', or '-format openvex' is provided,
|
||||
regardless of the number of detected vulnerabilities.
|
||||
|
||||
# Limitations
|
||||
|
||||
Govulncheck has these limitations:
|
||||
|
||||
- Govulncheck analyzes function pointer and interface calls conservatively,
|
||||
which may result in false positives or inaccurate call stacks in some cases.
|
||||
- Calls to functions made using package reflect are not visible to static
|
||||
analysis. Vulnerable code reachable only through those calls will not be
|
||||
reported in source scan mode. Similarly, use of the unsafe package may
|
||||
result in false negatives.
|
||||
- Because Go binaries do not contain detailed call information, govulncheck
|
||||
cannot show the call graphs for detected vulnerabilities. It may also
|
||||
report false positives for code that is in the binary but unreachable.
|
||||
- There is no support for silencing vulnerability findings. See https://go.dev/issue/61211 for
|
||||
updates.
|
||||
- Govulncheck reports only standard library vulnerabilities for binaries
|
||||
built with Go versions prior to Go 1.18.
|
||||
- For binaries where the symbol information cannot be extracted, govulncheck
|
||||
reports vulnerabilities for all modules on which the binary depends.
|
||||
|
||||
# Feedback
|
||||
|
||||
To share feedback, see https://go.dev/security/vuln#feedback.
|
||||
*/
|
||||
package main
|
||||
12
vendor/golang.org/x/vuln/cmd/govulncheck/gotypesalias.go
generated
vendored
Normal file
12
vendor/golang.org/x/vuln/cmd/govulncheck/gotypesalias.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2024 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.23
|
||||
|
||||
//go:debug gotypesalias=1
|
||||
|
||||
package main
|
||||
|
||||
// Materialize aliases whenever the go toolchain version is after 1.23 (#69772).
|
||||
// Remove this file after go.mod >= 1.23 (which implies gotypesalias=1).
|
||||
34
vendor/golang.org/x/vuln/cmd/govulncheck/main.go
generated
vendored
Normal file
34
vendor/golang.org/x/vuln/cmd/govulncheck/main.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/telemetry"
|
||||
"golang.org/x/vuln/scan"
|
||||
)
|
||||
|
||||
func main() {
|
||||
telemetry.Start(telemetry.Config{ReportCrashes: true})
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
cmd := scan.Command(ctx, os.Args[1:]...)
|
||||
err := cmd.Start()
|
||||
if err == nil {
|
||||
err = cmd.Wait()
|
||||
}
|
||||
switch err := err.(type) {
|
||||
case nil:
|
||||
case interface{ ExitCode() int }:
|
||||
os.Exit(err.ExitCode())
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
128
vendor/golang.org/x/vuln/cmd/govulncheck/test_utils.go
generated
vendored
Normal file
128
vendor/golang.org/x/vuln/cmd/govulncheck/test_utils.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// copyTestCase copies the test case at dir into a
|
||||
// temporary directory. The created files have 0644
|
||||
// permission and directories 0755. It does not create
|
||||
// symlinks.
|
||||
func copyTestCase(dir string, t *testing.T) string {
|
||||
newDir, err := filepath.Abs(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to copy test case %s: cannot create root %v", dir, err)
|
||||
}
|
||||
|
||||
if err := copyDir(dir, newDir); err != nil {
|
||||
t.Fatalf("failed to copy test case %s: copy failure %v", dir, err)
|
||||
}
|
||||
return newDir
|
||||
}
|
||||
|
||||
func copyDir(srcDir, destDir string) error {
|
||||
entries, err := os.ReadDir(srcDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
src := filepath.Join(srcDir, entry.Name())
|
||||
dest := filepath.Join(destDir, entry.Name())
|
||||
|
||||
fileInfo, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch fileInfo.Mode() & os.ModeType {
|
||||
case os.ModeDir:
|
||||
if err := os.MkdirAll(dest, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := copyDir(src, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
if err := copyFile(src, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(src, dest string) error {
|
||||
b, err := os.ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(dest, b, 0644)
|
||||
}
|
||||
|
||||
type config struct {
|
||||
// SkipGOOS is a list of GOOS to skip
|
||||
SkipGOOS []string `json:"skipGOOS,omitempty"`
|
||||
// Copy the folder to isolate it
|
||||
Copy bool `json:"copy,omitempty"`
|
||||
// SkipBuild the test case
|
||||
SkipBuild bool `json:"skipBuild,omitempty"`
|
||||
// Strip indicates if binaries should be stripped
|
||||
Strip bool `json:"strip,omitempty"`
|
||||
// EnableSBOM indicates if sbom should be
|
||||
// printed in JSON.
|
||||
EnableSBOM bool `json:"sbom,omitempty"`
|
||||
|
||||
Fixups []fixup `json:"fixups,omitempty"`
|
||||
}
|
||||
|
||||
func (c *config) skip() bool {
|
||||
for _, sg := range c.SkipGOOS {
|
||||
if runtime.GOOS == sg {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type fixup struct {
|
||||
Pattern string `json:"pattern,omitempty"`
|
||||
Replace string `json:"replace,omitempty"`
|
||||
compiled *regexp.Regexp
|
||||
replaceFunc func(b []byte) []byte
|
||||
}
|
||||
|
||||
func (f *fixup) init() {
|
||||
f.compiled = regexp.MustCompile(f.Pattern)
|
||||
}
|
||||
|
||||
func (f *fixup) apply(data []byte) []byte {
|
||||
if f.replaceFunc != nil {
|
||||
return f.compiled.ReplaceAllFunc(data, f.replaceFunc)
|
||||
}
|
||||
return f.compiled.ReplaceAll(data, []byte(f.Replace))
|
||||
}
|
||||
|
||||
// loadConfig loads and initializes the config from path.
|
||||
func loadConfig(path string) (*config, error) {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cfg config
|
||||
if err := json.Unmarshal(b, &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range cfg.Fixups {
|
||||
cfg.Fixups[i].init()
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
9
vendor/golang.org/x/vuln/internal/buildinfo/README.md
generated
vendored
Normal file
9
vendor/golang.org/x/vuln/internal/buildinfo/README.md
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
This code is a copied and slightly modified subset of go/src/debug/buildinfo.
|
||||
|
||||
It contains logic for parsing Go binary files for the purpose of extracting
|
||||
module dependency and symbol table information.
|
||||
|
||||
Logic added by vulncheck is located in files with "additions_" prefix.
|
||||
|
||||
Within the originally named files, changed or added logic is annotated with
|
||||
a comment starting with "Addition:".
|
||||
257
vendor/golang.org/x/vuln/internal/buildinfo/additions_buildinfo.go
generated
vendored
Normal file
257
vendor/golang.org/x/vuln/internal/buildinfo/additions_buildinfo.go
generated
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
// Copyright 2022 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 buildinfo
|
||||
|
||||
// This file adds to buildinfo the functionality for extracting the PCLN table.
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"debug/macho"
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ErrNoSymbols represents non-existence of symbol
|
||||
// table in binaries supported by buildinfo.
|
||||
var ErrNoSymbols = errors.New("no symbol section")
|
||||
|
||||
// SymbolInfo is derived from cmd/internal/objfile/elf.go:symbols, symbolData.
|
||||
func (x *elfExe) SymbolInfo(name string) (uint64, uint64, io.ReaderAt, error) {
|
||||
sym, err := x.lookupSymbol(name)
|
||||
if err != nil || sym == nil {
|
||||
if errors.Is(err, elf.ErrNoSymbols) {
|
||||
return 0, 0, nil, ErrNoSymbols
|
||||
}
|
||||
return 0, 0, nil, fmt.Errorf("no symbol %q", name)
|
||||
}
|
||||
prog := x.progContaining(sym.Value)
|
||||
if prog == nil {
|
||||
return 0, 0, nil, fmt.Errorf("no Prog containing value %d for %q", sym.Value, name)
|
||||
}
|
||||
return sym.Value, prog.Vaddr, prog.ReaderAt, nil
|
||||
}
|
||||
|
||||
func (x *elfExe) lookupSymbol(name string) (*elf.Symbol, error) {
|
||||
x.symbolsOnce.Do(func() {
|
||||
syms, err := x.f.Symbols()
|
||||
if err != nil {
|
||||
x.symbolsErr = err
|
||||
return
|
||||
}
|
||||
x.symbols = make(map[string]*elf.Symbol, len(syms))
|
||||
for _, s := range syms {
|
||||
s := s // make a copy to prevent aliasing
|
||||
x.symbols[s.Name] = &s
|
||||
}
|
||||
})
|
||||
if x.symbolsErr != nil {
|
||||
return nil, x.symbolsErr
|
||||
}
|
||||
return x.symbols[name], nil
|
||||
}
|
||||
|
||||
func (x *elfExe) progContaining(addr uint64) *elf.Prog {
|
||||
for _, p := range x.f.Progs {
|
||||
if addr >= p.Vaddr && addr < p.Vaddr+p.Filesz {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const go12magic = 0xfffffffb
|
||||
const go116magic = 0xfffffffa
|
||||
|
||||
// PCLNTab is derived from cmd/internal/objfile/elf.go:pcln.
|
||||
func (x *elfExe) PCLNTab() ([]byte, uint64) {
|
||||
var offset uint64
|
||||
text := x.f.Section(".text")
|
||||
if text != nil {
|
||||
offset = text.Offset
|
||||
}
|
||||
pclntab := x.f.Section(".gopclntab")
|
||||
if pclntab == nil {
|
||||
// Addition: this code is added to support some form of stripping.
|
||||
pclntab = x.f.Section(".data.rel.ro.gopclntab")
|
||||
if pclntab == nil {
|
||||
pclntab = x.f.Section(".data.rel.ro")
|
||||
if pclntab == nil {
|
||||
return nil, 0
|
||||
}
|
||||
// Possibly the PCLN table has been stuck in the .data.rel.ro section, but without
|
||||
// its own section header. We can search for for the start by looking for the four
|
||||
// byte magic and the go magic.
|
||||
b, err := pclntab.Data()
|
||||
if err != nil {
|
||||
return nil, 0
|
||||
}
|
||||
// TODO(rolandshoemaker): I'm not sure if the 16 byte increment during the search is
|
||||
// actually correct. During testing it worked, but that may be because I got lucky
|
||||
// with the binary I was using, and we need to do four byte jumps to exhaustively
|
||||
// search the section?
|
||||
for i := 0; i < len(b); i += 16 {
|
||||
if len(b)-i > 16 && b[i+4] == 0 && b[i+5] == 0 &&
|
||||
(b[i+6] == 1 || b[i+6] == 2 || b[i+6] == 4) &&
|
||||
(b[i+7] == 4 || b[i+7] == 8) {
|
||||
// Also check for the go magic
|
||||
leMagic := binary.LittleEndian.Uint32(b[i:])
|
||||
beMagic := binary.BigEndian.Uint32(b[i:])
|
||||
switch {
|
||||
case leMagic == go12magic:
|
||||
fallthrough
|
||||
case beMagic == go12magic:
|
||||
fallthrough
|
||||
case leMagic == go116magic:
|
||||
fallthrough
|
||||
case beMagic == go116magic:
|
||||
return b[i:], offset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
b, err := pclntab.Data()
|
||||
if err != nil {
|
||||
return nil, 0
|
||||
}
|
||||
return b, offset
|
||||
}
|
||||
|
||||
// SymbolInfo is derived from cmd/internal/objfile/pe.go:findPESymbol, loadPETable.
|
||||
func (x *peExe) SymbolInfo(name string) (uint64, uint64, io.ReaderAt, error) {
|
||||
sym, err := x.lookupSymbol(name)
|
||||
if err != nil {
|
||||
return 0, 0, nil, err
|
||||
}
|
||||
if sym == nil {
|
||||
return 0, 0, nil, fmt.Errorf("no symbol %q", name)
|
||||
}
|
||||
sect := x.f.Sections[sym.SectionNumber-1]
|
||||
// In PE, the symbol's value is the offset from the section start.
|
||||
return uint64(sym.Value), 0, sect.ReaderAt, nil
|
||||
}
|
||||
|
||||
func (x *peExe) lookupSymbol(name string) (*pe.Symbol, error) {
|
||||
x.symbolsOnce.Do(func() {
|
||||
x.symbols = make(map[string]*pe.Symbol, len(x.f.Symbols))
|
||||
if len(x.f.Symbols) == 0 {
|
||||
x.symbolsErr = ErrNoSymbols
|
||||
return
|
||||
}
|
||||
for _, s := range x.f.Symbols {
|
||||
x.symbols[s.Name] = s
|
||||
}
|
||||
})
|
||||
if x.symbolsErr != nil {
|
||||
return nil, x.symbolsErr
|
||||
}
|
||||
return x.symbols[name], nil
|
||||
}
|
||||
|
||||
// PCLNTab is derived from cmd/internal/objfile/pe.go:pcln.
|
||||
// Assumes that the underlying symbol table exists, otherwise
|
||||
// it might panic.
|
||||
func (x *peExe) PCLNTab() ([]byte, uint64) {
|
||||
var textOffset uint64
|
||||
for _, section := range x.f.Sections {
|
||||
if section.Name == ".text" {
|
||||
textOffset = uint64(section.Offset)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var start, end int64
|
||||
var section int
|
||||
if s, _ := x.lookupSymbol("runtime.pclntab"); s != nil {
|
||||
start = int64(s.Value)
|
||||
section = int(s.SectionNumber - 1)
|
||||
}
|
||||
if s, _ := x.lookupSymbol("runtime.epclntab"); s != nil {
|
||||
end = int64(s.Value)
|
||||
}
|
||||
if start == 0 || end == 0 {
|
||||
return nil, 0
|
||||
}
|
||||
offset := int64(x.f.Sections[section].Offset) + start
|
||||
size := end - start
|
||||
|
||||
pclntab := make([]byte, size)
|
||||
if _, err := x.r.ReadAt(pclntab, offset); err != nil {
|
||||
return nil, 0
|
||||
}
|
||||
return pclntab, textOffset
|
||||
}
|
||||
|
||||
// SymbolInfo is derived from cmd/internal/objfile/macho.go:symbols.
|
||||
func (x *machoExe) SymbolInfo(name string) (uint64, uint64, io.ReaderAt, error) {
|
||||
sym, err := x.lookupSymbol(name)
|
||||
if err != nil {
|
||||
return 0, 0, nil, err
|
||||
}
|
||||
if sym == nil {
|
||||
return 0, 0, nil, fmt.Errorf("no symbol %q", name)
|
||||
}
|
||||
seg := x.segmentContaining(sym.Value)
|
||||
if seg == nil {
|
||||
return 0, 0, nil, fmt.Errorf("no Segment containing value %d for %q", sym.Value, name)
|
||||
}
|
||||
return sym.Value, seg.Addr, seg.ReaderAt, nil
|
||||
}
|
||||
|
||||
func (x *machoExe) lookupSymbol(name string) (*macho.Symbol, error) {
|
||||
const mustExistSymbol = "runtime.main"
|
||||
x.symbolsOnce.Do(func() {
|
||||
x.symbols = make(map[string]*macho.Symbol, len(x.f.Symtab.Syms))
|
||||
for _, s := range x.f.Symtab.Syms {
|
||||
s := s // make a copy to prevent aliasing
|
||||
x.symbols[s.Name] = &s
|
||||
}
|
||||
// In the presence of stripping, the symbol table for darwin
|
||||
// binaries will not be empty, but the program symbols will
|
||||
// be missing.
|
||||
if _, ok := x.symbols[mustExistSymbol]; !ok {
|
||||
x.symbolsErr = ErrNoSymbols
|
||||
}
|
||||
})
|
||||
|
||||
if x.symbolsErr != nil {
|
||||
return nil, x.symbolsErr
|
||||
}
|
||||
return x.symbols[name], nil
|
||||
}
|
||||
|
||||
func (x *machoExe) segmentContaining(addr uint64) *macho.Segment {
|
||||
for _, load := range x.f.Loads {
|
||||
seg, ok := load.(*macho.Segment)
|
||||
if ok && seg.Addr <= addr && addr <= seg.Addr+seg.Filesz-1 && seg.Name != "__PAGEZERO" {
|
||||
return seg
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SymbolInfo is derived from cmd/internal/objfile/macho.go:pcln.
|
||||
func (x *machoExe) PCLNTab() ([]byte, uint64) {
|
||||
var textOffset uint64
|
||||
text := x.f.Section("__text")
|
||||
if text != nil {
|
||||
textOffset = uint64(text.Offset)
|
||||
}
|
||||
pclntab := x.f.Section("__gopclntab")
|
||||
if pclntab == nil {
|
||||
return nil, 0
|
||||
}
|
||||
b, err := pclntab.Data()
|
||||
if err != nil {
|
||||
return nil, 0
|
||||
}
|
||||
return b, textOffset
|
||||
}
|
||||
160
vendor/golang.org/x/vuln/internal/buildinfo/additions_scan.go
generated
vendored
Normal file
160
vendor/golang.org/x/vuln/internal/buildinfo/additions_scan.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
// 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 buildinfo
|
||||
|
||||
// Code in this package is dervied from src/cmd/go/internal/version/version.go
|
||||
// and cmd/go/internal/version/exe.go.
|
||||
|
||||
import (
|
||||
"debug/buildinfo"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/vuln/internal/gosym"
|
||||
"golang.org/x/vuln/internal/goversion"
|
||||
)
|
||||
|
||||
func debugModulesToPackagesModules(debugModules []*debug.Module) []*packages.Module {
|
||||
packagesModules := make([]*packages.Module, len(debugModules))
|
||||
for i, mod := range debugModules {
|
||||
packagesModules[i] = &packages.Module{
|
||||
Path: mod.Path,
|
||||
Version: mod.Version,
|
||||
}
|
||||
if mod.Replace != nil {
|
||||
packagesModules[i].Replace = &packages.Module{
|
||||
Path: mod.Replace.Path,
|
||||
Version: mod.Replace.Version,
|
||||
}
|
||||
}
|
||||
}
|
||||
return packagesModules
|
||||
}
|
||||
|
||||
type Symbol struct {
|
||||
Pkg string `json:"pkg,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// ExtractPackagesAndSymbols extracts symbols, packages, modules from
|
||||
// Go binary file as well as bin's metadata.
|
||||
//
|
||||
// If the symbol table is not available, such as in the case of stripped
|
||||
// binaries, returns module and binary info but without the symbol info.
|
||||
func ExtractPackagesAndSymbols(file string) ([]*packages.Module, []Symbol, *debug.BuildInfo, error) {
|
||||
bin, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
defer bin.Close()
|
||||
|
||||
bi, err := buildinfo.Read(bin)
|
||||
if err != nil {
|
||||
// It could be that bin is an ancient Go binary.
|
||||
v, err := goversion.ReadExe(file)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
bi := &debug.BuildInfo{
|
||||
GoVersion: v.Release,
|
||||
Main: debug.Module{Path: v.ModuleInfo},
|
||||
}
|
||||
// We cannot analyze symbol tables of ancient binaries.
|
||||
return nil, nil, bi, nil
|
||||
}
|
||||
|
||||
funcSymName := gosym.FuncSymName(bi.GoVersion)
|
||||
if funcSymName == "" {
|
||||
return nil, nil, nil, fmt.Errorf("binary built using unsupported Go version: %q", bi.GoVersion)
|
||||
}
|
||||
|
||||
x, err := openExe(bin)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
value, base, r, err := x.SymbolInfo(funcSymName)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNoSymbols) {
|
||||
// bin is stripped, so return just module info and metadata.
|
||||
return debugModulesToPackagesModules(bi.Deps), nil, bi, nil
|
||||
}
|
||||
return nil, nil, nil, fmt.Errorf("reading %v: %v", funcSymName, err)
|
||||
}
|
||||
|
||||
pclntab, textOffset := x.PCLNTab()
|
||||
if pclntab == nil {
|
||||
// If we have build information, but not PCLN table, fall
|
||||
// back to much higher granularity vulnerability checking.
|
||||
return debugModulesToPackagesModules(bi.Deps), nil, bi, nil
|
||||
}
|
||||
lineTab := gosym.NewLineTable(pclntab, textOffset)
|
||||
if lineTab == nil {
|
||||
return nil, nil, nil, errors.New("invalid line table")
|
||||
}
|
||||
tab, err := gosym.NewTable(nil, lineTab)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
pkgSyms := make(map[Symbol]bool)
|
||||
for _, f := range tab.Funcs {
|
||||
if f.Func == nil {
|
||||
continue
|
||||
}
|
||||
pkgName, symName, err := parseName(f.Func.Sym)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
pkgSyms[Symbol{pkgName, symName}] = true
|
||||
|
||||
// Collect symbols that were inlined in f.
|
||||
it, err := lineTab.InlineTree(&f, value, base, r)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("InlineTree: %v", err)
|
||||
}
|
||||
for _, ic := range it {
|
||||
pkgName, symName, err := parseName(&gosym.Sym{Name: ic.Name})
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
pkgSyms[Symbol{pkgName, symName}] = true
|
||||
}
|
||||
}
|
||||
|
||||
var syms []Symbol
|
||||
for ps := range pkgSyms {
|
||||
syms = append(syms, ps)
|
||||
}
|
||||
|
||||
return debugModulesToPackagesModules(bi.Deps), syms, bi, nil
|
||||
}
|
||||
|
||||
func parseName(s *gosym.Sym) (pkg, sym string, err error) {
|
||||
symName := s.BaseName()
|
||||
if r := s.ReceiverName(); r != "" {
|
||||
if strings.HasPrefix(r, "(*") {
|
||||
r = strings.Trim(r, "(*)")
|
||||
}
|
||||
symName = fmt.Sprintf("%s.%s", r, symName)
|
||||
}
|
||||
|
||||
pkgName := s.PackageName()
|
||||
if pkgName != "" {
|
||||
pkgName, err = url.PathUnescape(pkgName)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
return pkgName, symName, nil
|
||||
}
|
||||
221
vendor/golang.org/x/vuln/internal/buildinfo/buildinfo.go
generated
vendored
Normal file
221
vendor/golang.org/x/vuln/internal/buildinfo/buildinfo.go
generated
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
// 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 buildinfo
|
||||
|
||||
// Addition: this file is a trimmed and slightly modified version of debug/buildinfo/buildinfo.go
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"debug/macho"
|
||||
"debug/pe"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
// "internal/xcoff"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Addition: modification of rawBuildInfo in the original file.
|
||||
// openExe returns reader r as an exe.
|
||||
func openExe(r io.ReaderAt) (exe, error) {
|
||||
data := make([]byte, 16)
|
||||
if _, err := r.ReadAt(data, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bytes.HasPrefix(data, []byte("\x7FELF")) {
|
||||
e, err := elf.NewFile(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &elfExe{f: e}, nil
|
||||
}
|
||||
if bytes.HasPrefix(data, []byte("MZ")) {
|
||||
e, err := pe.NewFile(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &peExe{r: r, f: e}, nil
|
||||
}
|
||||
if bytes.HasPrefix(data, []byte("\xFE\xED\xFA")) || bytes.HasPrefix(data[1:], []byte("\xFA\xED\xFE")) {
|
||||
e, err := macho.NewFile(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &machoExe{f: e}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unrecognized executable format")
|
||||
}
|
||||
|
||||
type exe interface {
|
||||
// ReadData reads and returns up to size byte starting at virtual address addr.
|
||||
ReadData(addr, size uint64) ([]byte, error)
|
||||
|
||||
// DataStart returns the virtual address of the segment or section that
|
||||
// should contain build information. This is either a specially named section
|
||||
// or the first writable non-zero data segment.
|
||||
DataStart() uint64
|
||||
|
||||
PCLNTab() ([]byte, uint64) // Addition: for constructing symbol table
|
||||
|
||||
SymbolInfo(name string) (uint64, uint64, io.ReaderAt, error) // Addition: for inlining purposes
|
||||
}
|
||||
|
||||
// elfExe is the ELF implementation of the exe interface.
|
||||
type elfExe struct {
|
||||
f *elf.File
|
||||
|
||||
symbols map[string]*elf.Symbol // Addition: symbols in the binary
|
||||
symbolsOnce sync.Once // Addition: for computing symbols
|
||||
symbolsErr error // Addition: error for computing symbols
|
||||
}
|
||||
|
||||
func (x *elfExe) ReadData(addr, size uint64) ([]byte, error) {
|
||||
for _, prog := range x.f.Progs {
|
||||
if prog.Vaddr <= addr && addr <= prog.Vaddr+prog.Filesz-1 {
|
||||
n := prog.Vaddr + prog.Filesz - addr
|
||||
if n > size {
|
||||
n = size
|
||||
}
|
||||
data := make([]byte, n)
|
||||
_, err := prog.ReadAt(data, int64(addr-prog.Vaddr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("address not mapped") // Addition: custom error
|
||||
}
|
||||
|
||||
func (x *elfExe) DataStart() uint64 {
|
||||
for _, s := range x.f.Sections {
|
||||
if s.Name == ".go.buildinfo" {
|
||||
return s.Addr
|
||||
}
|
||||
}
|
||||
for _, p := range x.f.Progs {
|
||||
if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_X|elf.PF_W) == elf.PF_W {
|
||||
return p.Vaddr
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// peExe is the PE (Windows Portable Executable) implementation of the exe interface.
|
||||
type peExe struct {
|
||||
r io.ReaderAt
|
||||
f *pe.File
|
||||
|
||||
symbols map[string]*pe.Symbol // Addition: symbols in the binary
|
||||
symbolsOnce sync.Once // Addition: for computing symbols
|
||||
symbolsErr error // Addition: error for computing symbols
|
||||
}
|
||||
|
||||
func (x *peExe) imageBase() uint64 {
|
||||
switch oh := x.f.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
return uint64(oh.ImageBase)
|
||||
case *pe.OptionalHeader64:
|
||||
return oh.ImageBase
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *peExe) ReadData(addr, size uint64) ([]byte, error) {
|
||||
addr -= x.imageBase()
|
||||
for _, sect := range x.f.Sections {
|
||||
if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) {
|
||||
n := uint64(sect.VirtualAddress+sect.Size) - addr
|
||||
if n > size {
|
||||
n = size
|
||||
}
|
||||
data := make([]byte, n)
|
||||
_, err := sect.ReadAt(data, int64(addr-uint64(sect.VirtualAddress)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("address not mapped") // Addition: custom error
|
||||
}
|
||||
|
||||
func (x *peExe) DataStart() uint64 {
|
||||
// Assume data is first writable section.
|
||||
const (
|
||||
IMAGE_SCN_CNT_CODE = 0x00000020
|
||||
IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
|
||||
IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080
|
||||
IMAGE_SCN_MEM_EXECUTE = 0x20000000
|
||||
IMAGE_SCN_MEM_READ = 0x40000000
|
||||
IMAGE_SCN_MEM_WRITE = 0x80000000
|
||||
IMAGE_SCN_MEM_DISCARDABLE = 0x2000000
|
||||
IMAGE_SCN_LNK_NRELOC_OVFL = 0x1000000
|
||||
IMAGE_SCN_ALIGN_32BYTES = 0x600000
|
||||
)
|
||||
for _, sect := range x.f.Sections {
|
||||
if sect.VirtualAddress != 0 && sect.Size != 0 &&
|
||||
sect.Characteristics&^IMAGE_SCN_ALIGN_32BYTES == IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE {
|
||||
return uint64(sect.VirtualAddress) + x.imageBase()
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// machoExe is the Mach-O (Apple macOS/iOS) implementation of the exe interface.
|
||||
type machoExe struct {
|
||||
f *macho.File
|
||||
|
||||
symbols map[string]*macho.Symbol // Addition: symbols in the binary
|
||||
symbolsOnce sync.Once // Addition: for computing symbols
|
||||
symbolsErr error // Addition: error for computing symbols
|
||||
}
|
||||
|
||||
func (x *machoExe) ReadData(addr, size uint64) ([]byte, error) {
|
||||
for _, load := range x.f.Loads {
|
||||
seg, ok := load.(*macho.Segment)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if seg.Addr <= addr && addr <= seg.Addr+seg.Filesz-1 {
|
||||
if seg.Name == "__PAGEZERO" {
|
||||
continue
|
||||
}
|
||||
n := seg.Addr + seg.Filesz - addr
|
||||
if n > size {
|
||||
n = size
|
||||
}
|
||||
data := make([]byte, n)
|
||||
_, err := seg.ReadAt(data, int64(addr-seg.Addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("address not mapped") // Addition: custom error
|
||||
}
|
||||
|
||||
func (x *machoExe) DataStart() uint64 {
|
||||
// Look for section named "__go_buildinfo".
|
||||
for _, sec := range x.f.Sections {
|
||||
if sec.Name == "__go_buildinfo" {
|
||||
return sec.Addr
|
||||
}
|
||||
}
|
||||
// Try the first non-empty writable segment.
|
||||
const RW = 3
|
||||
for _, load := range x.f.Loads {
|
||||
seg, ok := load.(*macho.Segment)
|
||||
if ok && seg.Addr != 0 && seg.Filesz != 0 && seg.Prot == RW && seg.Maxprot == RW {
|
||||
return seg.Addr
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
347
vendor/golang.org/x/vuln/internal/client/client.go
generated
vendored
Normal file
347
vendor/golang.org/x/vuln/internal/client/client.go
generated
vendored
Normal file
@@ -0,0 +1,347 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
// Package client provides an interface for accessing vulnerability
|
||||
// databases, via either HTTP or local filesystem access.
|
||||
//
|
||||
// The protocol is described at https://go.dev/security/vuln/database.
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/vuln/internal/derrors"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
isem "golang.org/x/vuln/internal/semver"
|
||||
"golang.org/x/vuln/internal/web"
|
||||
)
|
||||
|
||||
// A Client for reading vulnerability databases.
|
||||
type Client struct {
|
||||
source
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a client that reads the vulnerability database
|
||||
// in source (an "http" or "file" prefixed URL).
|
||||
//
|
||||
// It supports databases following the API described
|
||||
// in https://go.dev/security/vuln/database#api.
|
||||
func NewClient(source string, opts *Options) (_ *Client, err error) {
|
||||
source = strings.TrimRight(source, "/")
|
||||
uri, err := url.Parse(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch uri.Scheme {
|
||||
case "http", "https":
|
||||
return newHTTPClient(uri, opts)
|
||||
case "file":
|
||||
return newLocalClient(uri)
|
||||
default:
|
||||
return nil, fmt.Errorf("source %q has unsupported scheme", uri)
|
||||
}
|
||||
}
|
||||
|
||||
var errUnknownSchema = errors.New("unrecognized vulndb format; see https://go.dev/security/vuln/database#api for accepted schema")
|
||||
|
||||
func newHTTPClient(uri *url.URL, opts *Options) (*Client, error) {
|
||||
source := uri.String()
|
||||
|
||||
// v1 returns true if the source likely follows the V1 schema.
|
||||
v1 := func() bool {
|
||||
return source == "https://vuln.go.dev" ||
|
||||
endpointExistsHTTP(source, "index/modules.json.gz")
|
||||
}
|
||||
|
||||
if v1() {
|
||||
return &Client{source: newHTTPSource(uri.String(), opts)}, nil
|
||||
}
|
||||
|
||||
return nil, errUnknownSchema
|
||||
}
|
||||
|
||||
func endpointExistsHTTP(source, endpoint string) bool {
|
||||
r, err := http.Head(source + "/" + endpoint)
|
||||
return err == nil && r.StatusCode == http.StatusOK
|
||||
}
|
||||
|
||||
func newLocalClient(uri *url.URL) (*Client, error) {
|
||||
dir, err := toDir(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if the DB likely follows the v1 schema by
|
||||
// looking for the "index/modules.json" endpoint.
|
||||
if endpointExistsDir(dir, modulesEndpoint+".json") {
|
||||
return &Client{source: newLocalSource(dir)}, nil
|
||||
}
|
||||
|
||||
// If the DB doesn't follow the v1 schema,
|
||||
// attempt to intepret it as a flat list of OSV files.
|
||||
// This is currently a "hidden" feature, so don't output the
|
||||
// specific error if this fails.
|
||||
src, err := newHybridSource(dir)
|
||||
if err != nil {
|
||||
return nil, errUnknownSchema
|
||||
}
|
||||
return &Client{source: src}, nil
|
||||
}
|
||||
|
||||
func toDir(uri *url.URL) (string, error) {
|
||||
dir, err := web.URLToFilePath(uri)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fi, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return "", fmt.Errorf("%s is not a directory", dir)
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func endpointExistsDir(dir, endpoint string) bool {
|
||||
_, err := os.Stat(filepath.Join(dir, endpoint))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func NewInMemoryClient(entries []*osv.Entry) (*Client, error) {
|
||||
s, err := newInMemorySource(entries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{source: s}, nil
|
||||
}
|
||||
|
||||
func (c *Client) LastModifiedTime(ctx context.Context) (_ time.Time, err error) {
|
||||
derrors.Wrap(&err, "LastModifiedTime()")
|
||||
|
||||
b, err := c.source.get(ctx, dbEndpoint)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
var dbMeta dbMeta
|
||||
if err := json.Unmarshal(b, &dbMeta); err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
return dbMeta.Modified, nil
|
||||
}
|
||||
|
||||
type ModuleRequest struct {
|
||||
// The module path to filter on.
|
||||
// This must be set (if empty, ByModule errors).
|
||||
Path string
|
||||
// (Optional) If set, only return vulnerabilities affected
|
||||
// at this version.
|
||||
Version string
|
||||
}
|
||||
|
||||
type ModuleResponse struct {
|
||||
Path string
|
||||
Version string
|
||||
Entries []*osv.Entry
|
||||
}
|
||||
|
||||
// ByModules returns a list of responses
|
||||
// containing the OSV entries corresponding to each request.
|
||||
//
|
||||
// The order of the requests is preserved, and each request has
|
||||
// a response even if there are no entries (in which case the Entries
|
||||
// field is nil).
|
||||
func (c *Client) ByModules(ctx context.Context, reqs []*ModuleRequest) (_ []*ModuleResponse, err error) {
|
||||
derrors.Wrap(&err, "ByModules(%v)", reqs)
|
||||
|
||||
metas, err := c.moduleMetas(ctx, reqs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resps := make([]*ModuleResponse, len(reqs))
|
||||
g, gctx := errgroup.WithContext(ctx)
|
||||
g.SetLimit(10)
|
||||
for i, req := range reqs {
|
||||
i, req := i, req
|
||||
g.Go(func() error {
|
||||
entries, err := c.byModule(gctx, req, metas[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resps[i] = &ModuleResponse{
|
||||
Path: req.Path,
|
||||
Version: req.Version,
|
||||
Entries: entries,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (c *Client) moduleMetas(ctx context.Context, reqs []*ModuleRequest) (_ []*moduleMeta, err error) {
|
||||
b, err := c.source.get(ctx, modulesEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dec, err := newStreamDecoder(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metas := make([]*moduleMeta, len(reqs))
|
||||
for dec.More() {
|
||||
var m moduleMeta
|
||||
err := dec.Decode(&m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, req := range reqs {
|
||||
if m.Path == req.Path {
|
||||
metas[i] = &m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return metas, nil
|
||||
}
|
||||
|
||||
// byModule returns the OSV entries matching the ModuleRequest,
|
||||
// or (nil, nil) if there are none.
|
||||
func (c *Client) byModule(ctx context.Context, req *ModuleRequest, m *moduleMeta) (_ []*osv.Entry, err error) {
|
||||
// This module isn't in the database.
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if req.Path == "" {
|
||||
return nil, fmt.Errorf("module path must be set")
|
||||
}
|
||||
|
||||
if req.Version != "" && !isem.Valid(req.Version) {
|
||||
return nil, fmt.Errorf("version %s is not valid semver", req.Version)
|
||||
}
|
||||
|
||||
var ids []string
|
||||
for _, v := range m.Vulns {
|
||||
if v.Fixed == "" || isem.Less(req.Version, v.Fixed) {
|
||||
ids = append(ids, v.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
entries, err := c.byIDs(ctx, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Filter by version.
|
||||
if req.Version != "" {
|
||||
affected := func(e *osv.Entry) bool {
|
||||
for _, a := range e.Affected {
|
||||
if a.Module.Path == req.Path && isem.Affects(a.Ranges, req.Version) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var filtered []*osv.Entry
|
||||
for _, entry := range entries {
|
||||
if affected(entry) {
|
||||
filtered = append(filtered, entry)
|
||||
}
|
||||
}
|
||||
if len(filtered) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(entries, func(i, j int) bool {
|
||||
return entries[i].ID < entries[j].ID
|
||||
})
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (c *Client) byIDs(ctx context.Context, ids []string) (_ []*osv.Entry, err error) {
|
||||
entries := make([]*osv.Entry, len(ids))
|
||||
g, gctx := errgroup.WithContext(ctx)
|
||||
g.SetLimit(10)
|
||||
for i, id := range ids {
|
||||
i, id := i, id
|
||||
g.Go(func() error {
|
||||
e, err := c.byID(gctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entries[i] = e
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// byID returns the OSV entry with the given ID,
|
||||
// or an error if it does not exist / cannot be unmarshaled.
|
||||
func (c *Client) byID(ctx context.Context, id string) (_ *osv.Entry, err error) {
|
||||
derrors.Wrap(&err, "byID(%s)", id)
|
||||
|
||||
b, err := c.source.get(ctx, entryEndpoint(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var entry osv.Entry
|
||||
if err := json.Unmarshal(b, &entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entry, nil
|
||||
}
|
||||
|
||||
// newStreamDecoder returns a decoder that can be used
|
||||
// to read an array of JSON objects.
|
||||
func newStreamDecoder(b []byte) (*json.Decoder, error) {
|
||||
dec := json.NewDecoder(bytes.NewBuffer(b))
|
||||
|
||||
// skip open bracket
|
||||
_, err := dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dec, nil
|
||||
}
|
||||
120
vendor/golang.org/x/vuln/internal/client/index.go
generated
vendored
Normal file
120
vendor/golang.org/x/vuln/internal/client/index.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
isem "golang.org/x/vuln/internal/semver"
|
||||
)
|
||||
|
||||
// indexFromDir returns a raw index created from a directory
|
||||
// containing OSV entries.
|
||||
// It skips any non-JSON files but errors if any of the JSON files
|
||||
// cannot be unmarshaled into OSV, or have a filename other than <ID>.json.
|
||||
func indexFromDir(dir string) (map[string][]byte, error) {
|
||||
idx := newIndex()
|
||||
f := os.DirFS(dir)
|
||||
|
||||
if err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
|
||||
fname := d.Name()
|
||||
ext := filepath.Ext(fname)
|
||||
switch {
|
||||
case err != nil:
|
||||
return err
|
||||
case d.IsDir():
|
||||
return nil
|
||||
case ext != ".json":
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := fs.ReadFile(f, d.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var entry osv.Entry
|
||||
if err := json.Unmarshal(b, &entry); err != nil {
|
||||
return err
|
||||
}
|
||||
if fname != entry.ID+".json" {
|
||||
return fmt.Errorf("OSV entries must have filename of the form <ID>.json, got %s", fname)
|
||||
}
|
||||
|
||||
idx.add(&entry)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return idx.raw()
|
||||
}
|
||||
|
||||
func indexFromEntries(entries []*osv.Entry) (map[string][]byte, error) {
|
||||
idx := newIndex()
|
||||
|
||||
for _, entry := range entries {
|
||||
idx.add(entry)
|
||||
}
|
||||
|
||||
return idx.raw()
|
||||
}
|
||||
|
||||
type index struct {
|
||||
db *dbMeta
|
||||
modules modulesIndex
|
||||
}
|
||||
|
||||
func newIndex() *index {
|
||||
return &index{
|
||||
db: &dbMeta{},
|
||||
modules: make(map[string]*moduleMeta),
|
||||
}
|
||||
}
|
||||
|
||||
func (i *index) add(entry *osv.Entry) {
|
||||
// Add to db index.
|
||||
if entry.Modified.After(i.db.Modified) {
|
||||
i.db.Modified = entry.Modified
|
||||
}
|
||||
// Add to modules index.
|
||||
for _, affected := range entry.Affected {
|
||||
modulePath := affected.Module.Path
|
||||
if _, ok := i.modules[modulePath]; !ok {
|
||||
i.modules[modulePath] = &moduleMeta{
|
||||
Path: modulePath,
|
||||
Vulns: []moduleVuln{},
|
||||
}
|
||||
}
|
||||
module := i.modules[modulePath]
|
||||
module.Vulns = append(module.Vulns, moduleVuln{
|
||||
ID: entry.ID,
|
||||
Modified: entry.Modified,
|
||||
Fixed: isem.NonSupersededFix(affected.Ranges),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (i *index) raw() (map[string][]byte, error) {
|
||||
data := make(map[string][]byte)
|
||||
|
||||
b, err := json.Marshal(i.db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data[dbEndpoint] = b
|
||||
|
||||
b, err = json.Marshal(i.modules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data[modulesEndpoint] = b
|
||||
|
||||
return data, nil
|
||||
}
|
||||
77
vendor/golang.org/x/vuln/internal/client/schema.go
generated
vendored
Normal file
77
vendor/golang.org/x/vuln/internal/client/schema.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
idDir = "ID"
|
||||
indexDir = "index"
|
||||
)
|
||||
|
||||
var (
|
||||
dbEndpoint = path.Join(indexDir, "db")
|
||||
modulesEndpoint = path.Join(indexDir, "modules")
|
||||
)
|
||||
|
||||
func entryEndpoint(id string) string {
|
||||
return path.Join(idDir, id)
|
||||
}
|
||||
|
||||
// dbMeta contains metadata about the database itself.
|
||||
type dbMeta struct {
|
||||
// Modified is the time the database was last modified, calculated
|
||||
// as the most recent time any single OSV entry was modified.
|
||||
Modified time.Time `json:"modified"`
|
||||
}
|
||||
|
||||
// moduleMeta contains metadata about a Go module that has one
|
||||
// or more vulnerabilities in the database.
|
||||
//
|
||||
// Found in the "index/modules" endpoint of the vulnerability database.
|
||||
type moduleMeta struct {
|
||||
// Path is the module path.
|
||||
Path string `json:"path"`
|
||||
// Vulns is a list of vulnerabilities that affect this module.
|
||||
Vulns []moduleVuln `json:"vulns"`
|
||||
}
|
||||
|
||||
// moduleVuln contains metadata about a vulnerability that affects
|
||||
// a certain module.
|
||||
type moduleVuln struct {
|
||||
// ID is a unique identifier for the vulnerability.
|
||||
// The Go vulnerability database issues IDs of the form
|
||||
// GO-<YEAR>-<ENTRYID>.
|
||||
ID string `json:"id"`
|
||||
// Modified is the time the vuln was last modified.
|
||||
Modified time.Time `json:"modified"`
|
||||
// Fixed is the latest version that introduces a fix for the
|
||||
// vulnerability, in SemVer 2.0.0 format, with no leading "v" prefix.
|
||||
Fixed string `json:"fixed,omitempty"`
|
||||
}
|
||||
|
||||
// modulesIndex represents an in-memory modules index.
|
||||
type modulesIndex map[string]*moduleMeta
|
||||
|
||||
func (m modulesIndex) MarshalJSON() ([]byte, error) {
|
||||
modules := make([]*moduleMeta, 0, len(m))
|
||||
for _, module := range m {
|
||||
modules = append(modules, module)
|
||||
}
|
||||
sort.SliceStable(modules, func(i, j int) bool {
|
||||
return modules[i].Path < modules[j].Path
|
||||
})
|
||||
for _, module := range modules {
|
||||
sort.SliceStable(module.Vulns, func(i, j int) bool {
|
||||
return module.Vulns[i].ID < module.Vulns[j].ID
|
||||
})
|
||||
}
|
||||
return json.Marshal(modules)
|
||||
}
|
||||
150
vendor/golang.org/x/vuln/internal/client/source.go
generated
vendored
Normal file
150
vendor/golang.org/x/vuln/internal/client/source.go
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/vuln/internal/derrors"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
)
|
||||
|
||||
type source interface {
|
||||
// get returns the raw, uncompressed bytes at the
|
||||
// requested endpoint, which should be bare with no file extensions
|
||||
// (e.g., "index/modules" instead of "index/modules.json.gz").
|
||||
// It errors if the endpoint cannot be reached or does not exist
|
||||
// in the expected form.
|
||||
get(ctx context.Context, endpoint string) ([]byte, error)
|
||||
}
|
||||
|
||||
func newHTTPSource(url string, opts *Options) *httpSource {
|
||||
c := http.DefaultClient
|
||||
if opts != nil && opts.HTTPClient != nil {
|
||||
c = opts.HTTPClient
|
||||
}
|
||||
return &httpSource{url: url, c: c}
|
||||
}
|
||||
|
||||
// httpSource reads a vulnerability database from an http(s) source.
|
||||
type httpSource struct {
|
||||
url string
|
||||
c *http.Client
|
||||
}
|
||||
|
||||
func (hs *httpSource) get(ctx context.Context, endpoint string) (_ []byte, err error) {
|
||||
derrors.Wrap(&err, "get(%s)", endpoint)
|
||||
|
||||
method := http.MethodGet
|
||||
reqURL := fmt.Sprintf("%s/%s", hs.url, endpoint+".json.gz")
|
||||
req, err := http.NewRequestWithContext(ctx, method, reqURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := hs.c.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("HTTP %s %s returned unexpected status: %s", method, reqURL, resp.Status)
|
||||
}
|
||||
|
||||
// Uncompress the result.
|
||||
r, err := gzip.NewReader(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
return io.ReadAll(r)
|
||||
}
|
||||
|
||||
func newLocalSource(dir string) *localSource {
|
||||
return &localSource{fs: os.DirFS(dir)}
|
||||
}
|
||||
|
||||
// localSource reads a vulnerability database from a local file system.
|
||||
type localSource struct {
|
||||
fs fs.FS
|
||||
}
|
||||
|
||||
func (ls *localSource) get(ctx context.Context, endpoint string) (_ []byte, err error) {
|
||||
derrors.Wrap(&err, "get(%s)", endpoint)
|
||||
|
||||
return fs.ReadFile(ls.fs, endpoint+".json")
|
||||
}
|
||||
|
||||
func newHybridSource(dir string) (*hybridSource, error) {
|
||||
index, err := indexFromDir(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &hybridSource{
|
||||
index: &inMemorySource{data: index},
|
||||
osv: &localSource{fs: os.DirFS(dir)},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// hybridSource reads OSV entries from a local file system, but reads
|
||||
// indexes from an in-memory map.
|
||||
type hybridSource struct {
|
||||
index *inMemorySource
|
||||
osv *localSource
|
||||
}
|
||||
|
||||
func (hs *hybridSource) get(ctx context.Context, endpoint string) (_ []byte, err error) {
|
||||
derrors.Wrap(&err, "get(%s)", endpoint)
|
||||
|
||||
dir, file := filepath.Split(endpoint)
|
||||
|
||||
if filepath.Dir(dir) == indexDir {
|
||||
return hs.index.get(ctx, endpoint)
|
||||
}
|
||||
|
||||
return hs.osv.get(ctx, file)
|
||||
}
|
||||
|
||||
// newInMemorySource creates a new in-memory source from OSV entries.
|
||||
// Adapted from x/vulndb/internal/database.go.
|
||||
func newInMemorySource(entries []*osv.Entry) (*inMemorySource, error) {
|
||||
data, err := indexFromEntries(entries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
b, err := json.Marshal(entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data[entryEndpoint(entry.ID)] = b
|
||||
}
|
||||
|
||||
return &inMemorySource{data: data}, nil
|
||||
}
|
||||
|
||||
// inMemorySource reads databases from an in-memory map.
|
||||
// Currently intended for use only in unit tests.
|
||||
type inMemorySource struct {
|
||||
data map[string][]byte
|
||||
}
|
||||
|
||||
func (db *inMemorySource) get(ctx context.Context, endpoint string) ([]byte, error) {
|
||||
b, ok := db.data[endpoint]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no data found at endpoint %q", endpoint)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
23
vendor/golang.org/x/vuln/internal/derrors/derrors.go
generated
vendored
Normal file
23
vendor/golang.org/x/vuln/internal/derrors/derrors.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
// 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.
|
||||
|
||||
// Package derrors defines internal error values to categorize the different
|
||||
// types error semantics supported by x/vuln.
|
||||
package derrors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Wrap adds context to the error and allows
|
||||
// unwrapping the result to recover the original error.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// defer derrors.Wrap(&err, "copy(%s, %s)", dst, src)
|
||||
func Wrap(errp *error, format string, args ...interface{}) {
|
||||
if *errp != nil {
|
||||
*errp = fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), *errp)
|
||||
}
|
||||
}
|
||||
11
vendor/golang.org/x/vuln/internal/gosym/README.md
generated
vendored
Normal file
11
vendor/golang.org/x/vuln/internal/gosym/README.md
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
This code is a copied and slightly modified version of go/src/debug/gosym.
|
||||
|
||||
The original code contains logic for accessing symbol tables and line numbers
|
||||
in Go binaries. The only reason why this is copied is to support inlining.
|
||||
|
||||
Code added by vulncheck is located in files with "additions_" prefix and it
|
||||
contains logic for accessing inlining information.
|
||||
|
||||
Within the originally named files, deleted or added logic is annotated with
|
||||
a comment starting with "Addition:". The modified logic allows the inlining
|
||||
code in "additions_*" files to access the necessary information.
|
||||
184
vendor/golang.org/x/vuln/internal/gosym/additions.go
generated
vendored
Normal file
184
vendor/golang.org/x/vuln/internal/gosym/additions.go
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
package gosym
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
sv "golang.org/x/mod/semver"
|
||||
"golang.org/x/vuln/internal/semver"
|
||||
)
|
||||
|
||||
const (
|
||||
funcSymNameGo119Lower string = "go.func.*"
|
||||
funcSymNameGo120 string = "go:func.*"
|
||||
)
|
||||
|
||||
// FuncSymName returns symbol name for Go functions used in binaries
|
||||
// based on Go version. Supported Go versions are 1.18 and greater.
|
||||
// If the go version is unreadable it assumes that it is a newer version
|
||||
// and returns the symbol name for go version 1.20 or greater.
|
||||
func FuncSymName(goVersion string) string {
|
||||
// Support devel goX.Y...
|
||||
v := strings.TrimPrefix(goVersion, "devel ")
|
||||
v = semver.GoTagToSemver(v)
|
||||
mm := sv.MajorMinor(v)
|
||||
if sv.Compare(mm, "v1.20") >= 0 || mm == "" {
|
||||
return funcSymNameGo120
|
||||
} else if sv.Compare(mm, "v1.18") >= 0 {
|
||||
return funcSymNameGo119Lower
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Additions to the original package from cmd/internal/objabi/funcdata.go
|
||||
const (
|
||||
pcdata_InlTreeIndex = 2
|
||||
funcdata_InlTree = 3
|
||||
)
|
||||
|
||||
// InlineTree returns the inline tree for Func f as a sequence of InlinedCalls.
|
||||
// goFuncValue is the value of the gosym.FuncSymName symbol.
|
||||
// baseAddr is the address of the memory region (ELF Prog) containing goFuncValue.
|
||||
// progReader is a ReaderAt positioned at the start of that region.
|
||||
func (t *LineTable) InlineTree(f *Func, goFuncValue, baseAddr uint64, progReader io.ReaderAt) ([]InlinedCall, error) {
|
||||
if f.inlineTreeCount == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if f.inlineTreeOffset == ^uint32(0) {
|
||||
return nil, nil
|
||||
}
|
||||
var offset int64
|
||||
if t.version >= ver118 {
|
||||
offset = int64(goFuncValue - baseAddr + uint64(f.inlineTreeOffset))
|
||||
} else {
|
||||
offset = int64(uint64(f.inlineTreeOffset) - baseAddr)
|
||||
}
|
||||
|
||||
r := io.NewSectionReader(progReader, offset, 1<<32) // pick a size larger than we need
|
||||
ics := make([]InlinedCall, 0, f.inlineTreeCount)
|
||||
for i := 0; i < f.inlineTreeCount; i++ {
|
||||
if t.version >= ver120 {
|
||||
var ric rawInlinedCall120
|
||||
if err := binary.Read(r, t.binary, &ric); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ics = append(ics, InlinedCall{
|
||||
FuncID: ric.FuncID,
|
||||
Name: t.funcName(uint32(ric.NameOff)),
|
||||
ParentPC: ric.ParentPC,
|
||||
})
|
||||
} else {
|
||||
var ric rawInlinedCall112
|
||||
if err := binary.Read(r, t.binary, &ric); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ics = append(ics, InlinedCall{
|
||||
FuncID: ric.FuncID,
|
||||
Name: t.funcName(uint32(ric.Func_)),
|
||||
ParentPC: ric.ParentPC,
|
||||
})
|
||||
}
|
||||
}
|
||||
return ics, nil
|
||||
}
|
||||
|
||||
// InlinedCall describes a call to an inlined function.
|
||||
type InlinedCall struct {
|
||||
FuncID uint8 // type of the called function
|
||||
Name string // name of called function
|
||||
ParentPC int32 // position of an instruction whose source position is the call site (offset from entry)
|
||||
}
|
||||
|
||||
// rawInlinedCall112 is the encoding of entries in the FUNCDATA_InlTree table
|
||||
// from Go 1.12 through 1.19. It is equivalent to runtime.inlinedCall.
|
||||
type rawInlinedCall112 struct {
|
||||
Parent int16 // index of parent in the inltree, or < 0
|
||||
FuncID uint8 // type of the called function
|
||||
_ byte
|
||||
File int32 // perCU file index for inlined call. See cmd/link:pcln.go
|
||||
Line int32 // line number of the call site
|
||||
Func_ int32 // offset into pclntab for name of called function
|
||||
ParentPC int32 // position of an instruction whose source position is the call site (offset from entry)
|
||||
}
|
||||
|
||||
// rawInlinedCall120 is the encoding of entries in the FUNCDATA_InlTree table
|
||||
// from Go 1.20. It is equivalent to runtime.inlinedCall.
|
||||
type rawInlinedCall120 struct {
|
||||
FuncID uint8 // type of the called function
|
||||
_ [3]byte
|
||||
NameOff int32 // offset into pclntab for name of called function
|
||||
ParentPC int32 // position of an instruction whose source position is the call site (offset from entry)
|
||||
StartLine int32 // line number of start of function (func keyword/TEXT directive)
|
||||
}
|
||||
|
||||
func (f funcData) npcdata() uint32 { return f.field(7) }
|
||||
func (f funcData) nfuncdata(numFuncFields uint32) uint32 {
|
||||
return uint32(f.data[f.fieldOffset(numFuncFields-1)+3])
|
||||
}
|
||||
|
||||
func (f funcData) funcdataOffset(i uint8, numFuncFields uint32) uint32 {
|
||||
if uint32(i) >= f.nfuncdata(numFuncFields) {
|
||||
return ^uint32(0)
|
||||
}
|
||||
var off uint32
|
||||
if f.t.version >= ver118 {
|
||||
off = f.fieldOffset(numFuncFields) + // skip fixed part of _func
|
||||
f.npcdata()*4 + // skip pcdata
|
||||
uint32(i)*4 // index of i'th FUNCDATA
|
||||
} else {
|
||||
off = f.fieldOffset(numFuncFields) + // skip fixed part of _func
|
||||
f.npcdata()*4
|
||||
off += uint32(i) * f.t.ptrsize
|
||||
}
|
||||
return f.t.binary.Uint32(f.data[off:])
|
||||
}
|
||||
|
||||
func (f funcData) fieldOffset(n uint32) uint32 {
|
||||
// In Go 1.18, the first field of _func changed
|
||||
// from a uintptr entry PC to a uint32 entry offset.
|
||||
sz0 := f.t.ptrsize
|
||||
if f.t.version >= ver118 {
|
||||
sz0 = 4
|
||||
}
|
||||
return sz0 + (n-1)*4 // subsequent fields are 4 bytes each
|
||||
}
|
||||
|
||||
func (f funcData) pcdataOffset(i uint8, numFuncFields uint32) uint32 {
|
||||
if uint32(i) >= f.npcdata() {
|
||||
return ^uint32(0)
|
||||
}
|
||||
off := f.fieldOffset(numFuncFields) + // skip fixed part of _func
|
||||
uint32(i)*4 // index of i'th PCDATA
|
||||
return f.t.binary.Uint32(f.data[off:])
|
||||
}
|
||||
|
||||
// maxInlineTreeIndexValue returns the maximum value of the inline tree index
|
||||
// pc-value table in info. This is the only way to determine how many
|
||||
// IndexedCalls are in an inline tree, since the data of the tree itself is not
|
||||
// delimited in any way.
|
||||
func (t *LineTable) maxInlineTreeIndexValue(info funcData, numFuncFields uint32) int {
|
||||
if info.npcdata() <= pcdata_InlTreeIndex {
|
||||
return -1
|
||||
}
|
||||
off := info.pcdataOffset(pcdata_InlTreeIndex, numFuncFields)
|
||||
p := t.pctab[off:]
|
||||
val := int32(-1)
|
||||
max := int32(-1)
|
||||
var pc uint64
|
||||
for t.step(&p, &pc, &val, pc == 0) {
|
||||
if val > max {
|
||||
max = val
|
||||
}
|
||||
}
|
||||
return int(max)
|
||||
}
|
||||
|
||||
type inlTree struct {
|
||||
inlineTreeOffset uint32 // offset from go.func.* symbol
|
||||
inlineTreeCount int // number of entries in inline tree
|
||||
}
|
||||
704
vendor/golang.org/x/vuln/internal/gosym/pclntab.go
generated
vendored
Normal file
704
vendor/golang.org/x/vuln/internal/gosym/pclntab.go
generated
vendored
Normal file
@@ -0,0 +1,704 @@
|
||||
// Copyright 2009 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.
|
||||
|
||||
/*
|
||||
* Line tables
|
||||
*/
|
||||
|
||||
package gosym
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// version of the pclntab
|
||||
type version int
|
||||
|
||||
const (
|
||||
verUnknown version = iota
|
||||
ver11
|
||||
ver12
|
||||
ver116
|
||||
ver118
|
||||
ver120
|
||||
)
|
||||
|
||||
// A LineTable is a data structure mapping program counters to line numbers.
|
||||
//
|
||||
// In Go 1.1 and earlier, each function (represented by a Func) had its own LineTable,
|
||||
// and the line number corresponded to a numbering of all source lines in the
|
||||
// program, across all files. That absolute line number would then have to be
|
||||
// converted separately to a file name and line number within the file.
|
||||
//
|
||||
// In Go 1.2, the format of the data changed so that there is a single LineTable
|
||||
// for the entire program, shared by all Funcs, and there are no absolute line
|
||||
// numbers, just line numbers within specific files.
|
||||
//
|
||||
// For the most part, LineTable's methods should be treated as an internal
|
||||
// detail of the package; callers should use the methods on Table instead.
|
||||
type LineTable struct {
|
||||
Data []byte
|
||||
PC uint64
|
||||
Line int
|
||||
|
||||
// This mutex is used to keep parsing of pclntab synchronous.
|
||||
mu sync.Mutex
|
||||
|
||||
// Contains the version of the pclntab section.
|
||||
version version
|
||||
|
||||
// Go 1.2/1.16/1.18 state
|
||||
binary binary.ByteOrder
|
||||
quantum uint32
|
||||
ptrsize uint32
|
||||
textStart uint64 // address of runtime.text symbol (1.18+)
|
||||
funcnametab []byte
|
||||
cutab []byte
|
||||
funcdata []byte
|
||||
functab []byte
|
||||
nfunctab uint32
|
||||
filetab []byte
|
||||
pctab []byte // points to the pctables.
|
||||
nfiletab uint32
|
||||
funcNames map[uint32]string // cache the function names
|
||||
strings map[uint32]string // interned substrings of Data, keyed by offset
|
||||
// fileMap varies depending on the version of the object file.
|
||||
// For ver12, it maps the name to the index in the file table.
|
||||
// For ver116, it maps the name to the offset in filetab.
|
||||
fileMap map[string]uint32
|
||||
}
|
||||
|
||||
// NOTE(rsc): This is wrong for GOARCH=arm, which uses a quantum of 4,
|
||||
// but we have no idea whether we're using arm or not. This only
|
||||
// matters in the old (pre-Go 1.2) symbol table format, so it's not worth
|
||||
// fixing.
|
||||
const oldQuantum = 1
|
||||
|
||||
func (t *LineTable) parse(targetPC uint64, targetLine int) (b []byte, pc uint64, line int) {
|
||||
// The PC/line table can be thought of as a sequence of
|
||||
// <pc update>* <line update>
|
||||
// batches. Each update batch results in a (pc, line) pair,
|
||||
// where line applies to every PC from pc up to but not
|
||||
// including the pc of the next pair.
|
||||
//
|
||||
// Here we process each update individually, which simplifies
|
||||
// the code, but makes the corner cases more confusing.
|
||||
b, pc, line = t.Data, t.PC, t.Line
|
||||
for pc <= targetPC && line != targetLine && len(b) > 0 {
|
||||
code := b[0]
|
||||
b = b[1:]
|
||||
switch {
|
||||
case code == 0:
|
||||
if len(b) < 4 {
|
||||
b = b[0:0]
|
||||
break
|
||||
}
|
||||
val := binary.BigEndian.Uint32(b)
|
||||
b = b[4:]
|
||||
line += int(val)
|
||||
case code <= 64:
|
||||
line += int(code)
|
||||
case code <= 128:
|
||||
line -= int(code - 64)
|
||||
default:
|
||||
pc += oldQuantum * uint64(code-128)
|
||||
continue
|
||||
}
|
||||
pc += oldQuantum
|
||||
}
|
||||
return b, pc, line
|
||||
}
|
||||
|
||||
func (t *LineTable) slice(pc uint64) *LineTable {
|
||||
data, pc, line := t.parse(pc, -1)
|
||||
return &LineTable{Data: data, PC: pc, Line: line}
|
||||
}
|
||||
|
||||
// PCToLine returns the line number for the given program counter.
|
||||
//
|
||||
// Deprecated: Use Table's PCToLine method instead.
|
||||
func (t *LineTable) PCToLine(pc uint64) int {
|
||||
if t.isGo12() {
|
||||
return t.go12PCToLine(pc)
|
||||
}
|
||||
_, _, line := t.parse(pc, -1)
|
||||
return line
|
||||
}
|
||||
|
||||
// LineToPC returns the program counter for the given line number,
|
||||
// considering only program counters before maxpc.
|
||||
//
|
||||
// Deprecated: Use Table's LineToPC method instead.
|
||||
func (t *LineTable) LineToPC(line int, maxpc uint64) uint64 {
|
||||
if t.isGo12() {
|
||||
return 0
|
||||
}
|
||||
_, pc, line1 := t.parse(maxpc, line)
|
||||
if line1 != line {
|
||||
return 0
|
||||
}
|
||||
// Subtract quantum from PC to account for post-line increment
|
||||
return pc - oldQuantum
|
||||
}
|
||||
|
||||
// NewLineTable returns a new PC/line table
|
||||
// corresponding to the encoded data.
|
||||
// Text must be the start address of the
|
||||
// corresponding text segment.
|
||||
func NewLineTable(data []byte, text uint64) *LineTable {
|
||||
return &LineTable{Data: data, PC: text, Line: 0, funcNames: make(map[uint32]string), strings: make(map[uint32]string)}
|
||||
}
|
||||
|
||||
// Go 1.2 symbol table format.
|
||||
// See golang.org/s/go12symtab.
|
||||
//
|
||||
// A general note about the methods here: rather than try to avoid
|
||||
// index out of bounds errors, we trust Go to detect them, and then
|
||||
// we recover from the panics and treat them as indicative of a malformed
|
||||
// or incomplete table.
|
||||
//
|
||||
// The methods called by symtab.go, which begin with "go12" prefixes,
|
||||
// are expected to have that recovery logic.
|
||||
|
||||
// isGo12 reports whether this is a Go 1.2 (or later) symbol table.
|
||||
func (t *LineTable) isGo12() bool {
|
||||
t.parsePclnTab()
|
||||
return t.version >= ver12
|
||||
}
|
||||
|
||||
const (
|
||||
go12magic = 0xfffffffb
|
||||
go116magic = 0xfffffffa
|
||||
go118magic = 0xfffffff0
|
||||
go120magic = 0xfffffff1
|
||||
)
|
||||
|
||||
// uintptr returns the pointer-sized value encoded at b.
|
||||
// The pointer size is dictated by the table being read.
|
||||
func (t *LineTable) uintptr(b []byte) uint64 {
|
||||
if t.ptrsize == 4 {
|
||||
return uint64(t.binary.Uint32(b))
|
||||
}
|
||||
return t.binary.Uint64(b)
|
||||
}
|
||||
|
||||
// parsePclnTab parses the pclntab, setting the version.
|
||||
func (t *LineTable) parsePclnTab() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.version != verUnknown {
|
||||
return
|
||||
}
|
||||
|
||||
// Note that during this function, setting the version is the last thing we do.
|
||||
// If we set the version too early, and parsing failed (likely as a panic on
|
||||
// slice lookups), we'd have a mistaken version.
|
||||
//
|
||||
// Error paths through this code will default the version to 1.1.
|
||||
t.version = ver11
|
||||
|
||||
if !disableRecover {
|
||||
defer func() {
|
||||
// If we panic parsing, assume it's a Go 1.1 pclntab.
|
||||
_ = recover()
|
||||
}()
|
||||
}
|
||||
|
||||
// Check header: 4-byte magic, two zeros, pc quantum, pointer size.
|
||||
if len(t.Data) < 16 || t.Data[4] != 0 || t.Data[5] != 0 ||
|
||||
(t.Data[6] != 1 && t.Data[6] != 2 && t.Data[6] != 4) || // pc quantum
|
||||
(t.Data[7] != 4 && t.Data[7] != 8) { // pointer size
|
||||
return
|
||||
}
|
||||
|
||||
var possibleVersion version
|
||||
leMagic := binary.LittleEndian.Uint32(t.Data)
|
||||
beMagic := binary.BigEndian.Uint32(t.Data)
|
||||
switch {
|
||||
case leMagic == go12magic:
|
||||
t.binary, possibleVersion = binary.LittleEndian, ver12
|
||||
case beMagic == go12magic:
|
||||
t.binary, possibleVersion = binary.BigEndian, ver12
|
||||
case leMagic == go116magic:
|
||||
t.binary, possibleVersion = binary.LittleEndian, ver116
|
||||
case beMagic == go116magic:
|
||||
t.binary, possibleVersion = binary.BigEndian, ver116
|
||||
case leMagic == go118magic:
|
||||
t.binary, possibleVersion = binary.LittleEndian, ver118
|
||||
case beMagic == go118magic:
|
||||
t.binary, possibleVersion = binary.BigEndian, ver118
|
||||
case leMagic == go120magic:
|
||||
t.binary, possibleVersion = binary.LittleEndian, ver120
|
||||
case beMagic == go120magic:
|
||||
t.binary, possibleVersion = binary.BigEndian, ver120
|
||||
default:
|
||||
return
|
||||
}
|
||||
t.version = possibleVersion
|
||||
|
||||
// quantum and ptrSize are the same between 1.2, 1.16, and 1.18
|
||||
t.quantum = uint32(t.Data[6])
|
||||
t.ptrsize = uint32(t.Data[7])
|
||||
|
||||
offset := func(word uint32) uint64 {
|
||||
return t.uintptr(t.Data[8+word*t.ptrsize:])
|
||||
}
|
||||
data := func(word uint32) []byte {
|
||||
return t.Data[offset(word):]
|
||||
}
|
||||
|
||||
switch possibleVersion {
|
||||
case ver118, ver120:
|
||||
t.nfunctab = uint32(offset(0))
|
||||
t.nfiletab = uint32(offset(1))
|
||||
t.textStart = t.PC // use the start PC instead of reading from the table, which may be unrelocated
|
||||
t.funcnametab = data(3)
|
||||
t.cutab = data(4)
|
||||
t.filetab = data(5)
|
||||
t.pctab = data(6)
|
||||
t.funcdata = data(7)
|
||||
t.functab = data(7)
|
||||
functabsize := (int(t.nfunctab)*2 + 1) * t.functabFieldSize()
|
||||
t.functab = t.functab[:functabsize]
|
||||
case ver116:
|
||||
t.nfunctab = uint32(offset(0))
|
||||
t.nfiletab = uint32(offset(1))
|
||||
t.funcnametab = data(2)
|
||||
t.cutab = data(3)
|
||||
t.filetab = data(4)
|
||||
t.pctab = data(5)
|
||||
t.funcdata = data(6)
|
||||
t.functab = data(6)
|
||||
functabsize := (int(t.nfunctab)*2 + 1) * t.functabFieldSize()
|
||||
t.functab = t.functab[:functabsize]
|
||||
case ver12:
|
||||
t.nfunctab = uint32(t.uintptr(t.Data[8:]))
|
||||
t.funcdata = t.Data
|
||||
t.funcnametab = t.Data
|
||||
t.functab = t.Data[8+t.ptrsize:]
|
||||
t.pctab = t.Data
|
||||
functabsize := (int(t.nfunctab)*2 + 1) * t.functabFieldSize()
|
||||
fileoff := t.binary.Uint32(t.functab[functabsize:])
|
||||
t.functab = t.functab[:functabsize]
|
||||
t.filetab = t.Data[fileoff:]
|
||||
t.nfiletab = t.binary.Uint32(t.filetab)
|
||||
t.filetab = t.filetab[:t.nfiletab*4]
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// go12Funcs returns a slice of Funcs derived from the Go 1.2+ pcln table.
|
||||
func (t *LineTable) go12Funcs() []Func {
|
||||
// Assume it is malformed and return nil on error.
|
||||
if !disableRecover {
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
}
|
||||
|
||||
ft := t.funcTab()
|
||||
funcs := make([]Func, ft.Count())
|
||||
syms := make([]Sym, len(funcs))
|
||||
for i := range funcs {
|
||||
f := &funcs[i]
|
||||
f.Entry = ft.pc(i)
|
||||
f.End = ft.pc(i + 1)
|
||||
info := t.funcData(uint32(i))
|
||||
f.LineTable = t
|
||||
f.FrameSize = int(info.deferreturn())
|
||||
|
||||
// Additions:
|
||||
// numFuncField is the number of (32 bit) fields in _func (src/runtime/runtime2.go)
|
||||
// Note that the last 4 fields are 32 bits combined. This number is 11 for go1.20,
|
||||
// 10 for earlier versions down to go1.16, and 9 before that.
|
||||
var numFuncFields uint32 = 11
|
||||
if t.version < ver116 {
|
||||
numFuncFields = 9
|
||||
} else if t.version < ver120 {
|
||||
numFuncFields = 10
|
||||
}
|
||||
f.inlineTreeOffset = info.funcdataOffset(funcdata_InlTree, numFuncFields)
|
||||
f.inlineTreeCount = 1 + t.maxInlineTreeIndexValue(info, numFuncFields)
|
||||
|
||||
syms[i] = Sym{
|
||||
Value: f.Entry,
|
||||
Type: 'T',
|
||||
Name: t.funcName(info.nameOff()),
|
||||
GoType: 0,
|
||||
Func: f,
|
||||
goVersion: t.version,
|
||||
}
|
||||
f.Sym = &syms[i]
|
||||
}
|
||||
return funcs
|
||||
}
|
||||
|
||||
// findFunc returns the funcData corresponding to the given program counter.
|
||||
func (t *LineTable) findFunc(pc uint64) funcData {
|
||||
ft := t.funcTab()
|
||||
if pc < ft.pc(0) || pc >= ft.pc(ft.Count()) {
|
||||
return funcData{}
|
||||
}
|
||||
idx := sort.Search(int(t.nfunctab), func(i int) bool {
|
||||
return ft.pc(i) > pc
|
||||
})
|
||||
idx--
|
||||
return t.funcData(uint32(idx))
|
||||
}
|
||||
|
||||
// readvarint reads, removes, and returns a varint from *pp.
|
||||
func (t *LineTable) readvarint(pp *[]byte) uint32 {
|
||||
var v, shift uint32
|
||||
p := *pp
|
||||
for shift = 0; ; shift += 7 {
|
||||
b := p[0]
|
||||
p = p[1:]
|
||||
v |= (uint32(b) & 0x7F) << shift
|
||||
if b&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
*pp = p
|
||||
return v
|
||||
}
|
||||
|
||||
// funcName returns the name of the function found at off.
|
||||
func (t *LineTable) funcName(off uint32) string {
|
||||
if s, ok := t.funcNames[off]; ok {
|
||||
return s
|
||||
}
|
||||
i := bytes.IndexByte(t.funcnametab[off:], 0)
|
||||
s := string(t.funcnametab[off : off+uint32(i)])
|
||||
t.funcNames[off] = s
|
||||
return s
|
||||
}
|
||||
|
||||
// stringFrom returns a Go string found at off from a position.
|
||||
func (t *LineTable) stringFrom(arr []byte, off uint32) string {
|
||||
if s, ok := t.strings[off]; ok {
|
||||
return s
|
||||
}
|
||||
i := bytes.IndexByte(arr[off:], 0)
|
||||
s := string(arr[off : off+uint32(i)])
|
||||
t.strings[off] = s
|
||||
return s
|
||||
}
|
||||
|
||||
// string returns a Go string found at off.
|
||||
func (t *LineTable) string(off uint32) string {
|
||||
return t.stringFrom(t.funcdata, off)
|
||||
}
|
||||
|
||||
// functabFieldSize returns the size in bytes of a single functab field.
|
||||
func (t *LineTable) functabFieldSize() int {
|
||||
if t.version >= ver118 {
|
||||
return 4
|
||||
}
|
||||
return int(t.ptrsize)
|
||||
}
|
||||
|
||||
// funcTab returns t's funcTab.
|
||||
func (t *LineTable) funcTab() funcTab {
|
||||
return funcTab{LineTable: t, sz: t.functabFieldSize()}
|
||||
}
|
||||
|
||||
// funcTab is memory corresponding to a slice of functab structs, followed by an invalid PC.
|
||||
// A functab struct is a PC and a func offset.
|
||||
type funcTab struct {
|
||||
*LineTable
|
||||
sz int // cached result of t.functabFieldSize
|
||||
}
|
||||
|
||||
// Count returns the number of func entries in f.
|
||||
func (f funcTab) Count() int {
|
||||
return int(f.nfunctab)
|
||||
}
|
||||
|
||||
// pc returns the PC of the i'th func in f.
|
||||
func (f funcTab) pc(i int) uint64 {
|
||||
u := f.uint(f.functab[2*i*f.sz:])
|
||||
if f.version >= ver118 {
|
||||
u += f.textStart
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// funcOff returns the funcdata offset of the i'th func in f.
|
||||
func (f funcTab) funcOff(i int) uint64 {
|
||||
return f.uint(f.functab[(2*i+1)*f.sz:])
|
||||
}
|
||||
|
||||
// uint returns the uint stored at b.
|
||||
func (f funcTab) uint(b []byte) uint64 {
|
||||
if f.sz == 4 {
|
||||
return uint64(f.binary.Uint32(b))
|
||||
}
|
||||
return f.binary.Uint64(b)
|
||||
}
|
||||
|
||||
// funcData is memory corresponding to an _func struct.
|
||||
type funcData struct {
|
||||
t *LineTable // LineTable this data is a part of
|
||||
data []byte // raw memory for the function
|
||||
}
|
||||
|
||||
// funcData returns the ith funcData in t.functab.
|
||||
func (t *LineTable) funcData(i uint32) funcData {
|
||||
data := t.funcdata[t.funcTab().funcOff(int(i)):]
|
||||
return funcData{t: t, data: data}
|
||||
}
|
||||
|
||||
// IsZero reports whether f is the zero value.
|
||||
func (f funcData) IsZero() bool {
|
||||
return f.t == nil && f.data == nil
|
||||
}
|
||||
|
||||
// entryPC returns the func's entry PC.
|
||||
func (f *funcData) entryPC() uint64 {
|
||||
// In Go 1.18, the first field of _func changed
|
||||
// from a uintptr entry PC to a uint32 entry offset.
|
||||
if f.t.version >= ver118 {
|
||||
// TODO: support multiple text sections.
|
||||
// See runtime/symtab.go:(*moduledata).textAddr.
|
||||
return uint64(f.t.binary.Uint32(f.data)) + f.t.textStart
|
||||
}
|
||||
return f.t.uintptr(f.data)
|
||||
}
|
||||
|
||||
func (f funcData) nameOff() uint32 { return f.field(1) }
|
||||
func (f funcData) deferreturn() uint32 { return f.field(3) }
|
||||
func (f funcData) pcfile() uint32 { return f.field(5) }
|
||||
func (f funcData) pcln() uint32 { return f.field(6) }
|
||||
func (f funcData) cuOffset() uint32 { return f.field(8) }
|
||||
|
||||
// field returns the nth field of the _func struct.
|
||||
// It panics if n == 0 or n > 9; for n == 0, call f.entryPC.
|
||||
// Most callers should use a named field accessor (just above).
|
||||
func (f funcData) field(n uint32) uint32 {
|
||||
if n == 0 || n > 9 {
|
||||
panic("bad funcdata field")
|
||||
}
|
||||
// Addition: some code deleted here to support inlining.
|
||||
off := f.fieldOffset(n)
|
||||
data := f.data[off:]
|
||||
return f.t.binary.Uint32(data)
|
||||
}
|
||||
|
||||
// step advances to the next pc, value pair in the encoded table.
|
||||
func (t *LineTable) step(p *[]byte, pc *uint64, val *int32, first bool) bool {
|
||||
uvdelta := t.readvarint(p)
|
||||
if uvdelta == 0 && !first {
|
||||
return false
|
||||
}
|
||||
if uvdelta&1 != 0 {
|
||||
uvdelta = ^(uvdelta >> 1)
|
||||
} else {
|
||||
uvdelta >>= 1
|
||||
}
|
||||
vdelta := int32(uvdelta)
|
||||
pcdelta := t.readvarint(p) * t.quantum
|
||||
*pc += uint64(pcdelta)
|
||||
*val += vdelta
|
||||
return true
|
||||
}
|
||||
|
||||
// pcvalue reports the value associated with the target pc.
|
||||
// off is the offset to the beginning of the pc-value table,
|
||||
// and entry is the start PC for the corresponding function.
|
||||
func (t *LineTable) pcvalue(off uint32, entry, targetpc uint64) int32 {
|
||||
p := t.pctab[off:]
|
||||
|
||||
val := int32(-1)
|
||||
pc := entry
|
||||
for t.step(&p, &pc, &val, pc == entry) {
|
||||
if targetpc < pc {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// findFileLine scans one function in the binary looking for a
|
||||
// program counter in the given file on the given line.
|
||||
// It does so by running the pc-value tables mapping program counter
|
||||
// to file number. Since most functions come from a single file, these
|
||||
// are usually short and quick to scan. If a file match is found, then the
|
||||
// code goes to the expense of looking for a simultaneous line number match.
|
||||
func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum, line int32, cutab []byte) uint64 {
|
||||
if filetab == 0 || linetab == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
fp := t.pctab[filetab:]
|
||||
fl := t.pctab[linetab:]
|
||||
fileVal := int32(-1)
|
||||
filePC := entry
|
||||
lineVal := int32(-1)
|
||||
linePC := entry
|
||||
fileStartPC := filePC
|
||||
for t.step(&fp, &filePC, &fileVal, filePC == entry) {
|
||||
fileIndex := fileVal
|
||||
if t.version == ver116 || t.version == ver118 || t.version == ver120 {
|
||||
fileIndex = int32(t.binary.Uint32(cutab[fileVal*4:]))
|
||||
}
|
||||
if fileIndex == filenum && fileStartPC < filePC {
|
||||
// fileIndex is in effect starting at fileStartPC up to
|
||||
// but not including filePC, and it's the file we want.
|
||||
// Run the PC table looking for a matching line number
|
||||
// or until we reach filePC.
|
||||
lineStartPC := linePC
|
||||
for linePC < filePC && t.step(&fl, &linePC, &lineVal, linePC == entry) {
|
||||
// lineVal is in effect until linePC, and lineStartPC < filePC.
|
||||
if lineVal == line {
|
||||
if fileStartPC <= lineStartPC {
|
||||
return lineStartPC
|
||||
}
|
||||
if fileStartPC < linePC {
|
||||
return fileStartPC
|
||||
}
|
||||
}
|
||||
lineStartPC = linePC
|
||||
}
|
||||
}
|
||||
fileStartPC = filePC
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// go12PCToLine maps program counter to line number for the Go 1.2+ pcln table.
|
||||
func (t *LineTable) go12PCToLine(pc uint64) (line int) {
|
||||
defer func() {
|
||||
if !disableRecover && recover() != nil {
|
||||
line = -1
|
||||
}
|
||||
}()
|
||||
|
||||
f := t.findFunc(pc)
|
||||
if f.IsZero() {
|
||||
return -1
|
||||
}
|
||||
entry := f.entryPC()
|
||||
linetab := f.pcln()
|
||||
return int(t.pcvalue(linetab, entry, pc))
|
||||
}
|
||||
|
||||
// go12PCToFile maps program counter to file name for the Go 1.2+ pcln table.
|
||||
func (t *LineTable) go12PCToFile(pc uint64) (file string) {
|
||||
defer func() {
|
||||
if !disableRecover && recover() != nil {
|
||||
file = ""
|
||||
}
|
||||
}()
|
||||
|
||||
f := t.findFunc(pc)
|
||||
if f.IsZero() {
|
||||
return ""
|
||||
}
|
||||
entry := f.entryPC()
|
||||
filetab := f.pcfile()
|
||||
fno := t.pcvalue(filetab, entry, pc)
|
||||
if t.version == ver12 {
|
||||
if fno <= 0 {
|
||||
return ""
|
||||
}
|
||||
return t.string(t.binary.Uint32(t.filetab[4*fno:]))
|
||||
}
|
||||
// Go ≥ 1.16
|
||||
if fno < 0 { // 0 is valid for ≥ 1.16
|
||||
return ""
|
||||
}
|
||||
cuoff := f.cuOffset()
|
||||
if fnoff := t.binary.Uint32(t.cutab[(cuoff+uint32(fno))*4:]); fnoff != ^uint32(0) {
|
||||
return t.stringFrom(t.filetab, fnoff)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// go12LineToPC maps a (file, line) pair to a program counter for the Go 1.2+ pcln table.
|
||||
func (t *LineTable) go12LineToPC(file string, line int) (pc uint64) {
|
||||
defer func() {
|
||||
if !disableRecover && recover() != nil {
|
||||
pc = 0
|
||||
}
|
||||
}()
|
||||
|
||||
t.initFileMap()
|
||||
filenum, ok := t.fileMap[file]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Scan all functions.
|
||||
// If this turns out to be a bottleneck, we could build a map[int32][]int32
|
||||
// mapping file number to a list of functions with code from that file.
|
||||
var cutab []byte
|
||||
for i := uint32(0); i < t.nfunctab; i++ {
|
||||
f := t.funcData(i)
|
||||
entry := f.entryPC()
|
||||
filetab := f.pcfile()
|
||||
linetab := f.pcln()
|
||||
if t.version == ver116 || t.version == ver118 || t.version == ver120 {
|
||||
if f.cuOffset() == ^uint32(0) {
|
||||
// skip functions without compilation unit (not real function, or linker generated)
|
||||
continue
|
||||
}
|
||||
cutab = t.cutab[f.cuOffset()*4:]
|
||||
}
|
||||
pc := t.findFileLine(entry, filetab, linetab, int32(filenum), int32(line), cutab)
|
||||
if pc != 0 {
|
||||
return pc
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// initFileMap initializes the map from file name to file number.
|
||||
func (t *LineTable) initFileMap() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
if t.fileMap != nil {
|
||||
return
|
||||
}
|
||||
m := make(map[string]uint32)
|
||||
|
||||
if t.version == ver12 {
|
||||
for i := uint32(1); i < t.nfiletab; i++ {
|
||||
s := t.string(t.binary.Uint32(t.filetab[4*i:]))
|
||||
m[s] = i
|
||||
}
|
||||
} else {
|
||||
var pos uint32
|
||||
for i := uint32(0); i < t.nfiletab; i++ {
|
||||
s := t.stringFrom(t.filetab, pos)
|
||||
m[s] = pos
|
||||
pos += uint32(len(s) + 1)
|
||||
}
|
||||
}
|
||||
t.fileMap = m
|
||||
}
|
||||
|
||||
// go12MapFiles adds to m a key for every file in the Go 1.2 LineTable.
|
||||
// Every key maps to obj. That's not a very interesting map, but it provides
|
||||
// a way for callers to obtain the list of files in the program.
|
||||
func (t *LineTable) go12MapFiles(m map[string]*Obj, obj *Obj) {
|
||||
if !disableRecover {
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
}
|
||||
|
||||
t.initFileMap()
|
||||
for file := range t.fileMap {
|
||||
m[file] = obj
|
||||
}
|
||||
}
|
||||
|
||||
// disableRecover causes this package not to swallow panics.
|
||||
// This is useful when making changes.
|
||||
const disableRecover = true
|
||||
776
vendor/golang.org/x/vuln/internal/gosym/symtab.go
generated
vendored
Normal file
776
vendor/golang.org/x/vuln/internal/gosym/symtab.go
generated
vendored
Normal file
@@ -0,0 +1,776 @@
|
||||
// Copyright 2009 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.
|
||||
|
||||
// Package gosym implements access to the Go symbol
|
||||
// and line number tables embedded in Go binaries generated
|
||||
// by the gc compilers.
|
||||
package gosym
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
* Symbols
|
||||
*/
|
||||
|
||||
// A Sym represents a single symbol table entry.
|
||||
type Sym struct {
|
||||
Value uint64
|
||||
Type byte
|
||||
Name string
|
||||
GoType uint64
|
||||
// If this symbol is a function symbol, the corresponding Func
|
||||
Func *Func
|
||||
|
||||
goVersion version
|
||||
}
|
||||
|
||||
// Static reports whether this symbol is static (not visible outside its file).
|
||||
func (s *Sym) Static() bool { return s.Type >= 'a' }
|
||||
|
||||
// nameWithoutInst returns s.Name if s.Name has no brackets (does not reference an
|
||||
// instantiated type, function, or method). If s.Name contains brackets, then it
|
||||
// returns s.Name with all the contents between (and including) the outermost left
|
||||
// and right bracket removed. This is useful to ignore any extra slashes or dots
|
||||
// inside the brackets from the string searches below, where needed.
|
||||
func (s *Sym) nameWithoutInst() string {
|
||||
start := strings.Index(s.Name, "[")
|
||||
if start < 0 {
|
||||
return s.Name
|
||||
}
|
||||
end := strings.LastIndex(s.Name, "]")
|
||||
if end < 0 {
|
||||
// Malformed name, should contain closing bracket too.
|
||||
return s.Name
|
||||
}
|
||||
return s.Name[0:start] + s.Name[end+1:]
|
||||
}
|
||||
|
||||
// PackageName returns the package part of the symbol name,
|
||||
// or the empty string if there is none.
|
||||
func (s *Sym) PackageName() string {
|
||||
name := s.nameWithoutInst()
|
||||
|
||||
// Since go1.20, a prefix of "type:" and "go:" is a compiler-generated symbol,
|
||||
// they do not belong to any package.
|
||||
//
|
||||
// See cmd/compile/internal/base/link.go:ReservedImports variable.
|
||||
if s.goVersion >= ver120 && (strings.HasPrefix(name, "go:") || strings.HasPrefix(name, "type:")) {
|
||||
return ""
|
||||
}
|
||||
|
||||
// For go1.18 and below, the prefix are "type." and "go." instead.
|
||||
if s.goVersion <= ver118 && (strings.HasPrefix(name, "go.") || strings.HasPrefix(name, "type.")) {
|
||||
return ""
|
||||
}
|
||||
|
||||
pathend := strings.LastIndex(name, "/")
|
||||
if pathend < 0 {
|
||||
pathend = 0
|
||||
}
|
||||
|
||||
if i := strings.Index(name[pathend:], "."); i != -1 {
|
||||
return name[:pathend+i]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ReceiverName returns the receiver type name of this symbol,
|
||||
// or the empty string if there is none. A receiver name is only detected in
|
||||
// the case that s.Name is fully-specified with a package name.
|
||||
func (s *Sym) ReceiverName() string {
|
||||
name := s.nameWithoutInst()
|
||||
// If we find a slash in name, it should precede any bracketed expression
|
||||
// that was removed, so pathend will apply correctly to name and s.Name.
|
||||
pathend := strings.LastIndex(name, "/")
|
||||
if pathend < 0 {
|
||||
pathend = 0
|
||||
}
|
||||
// Find the first dot after pathend (or from the beginning, if there was
|
||||
// no slash in name).
|
||||
l := strings.Index(name[pathend:], ".")
|
||||
// Find the last dot after pathend (or the beginning).
|
||||
r := strings.LastIndex(name[pathend:], ".")
|
||||
if l == -1 || r == -1 || l == r {
|
||||
// There is no receiver if we didn't find two distinct dots after pathend.
|
||||
return ""
|
||||
}
|
||||
// Given there is a trailing '.' that is in name, find it now in s.Name.
|
||||
// pathend+l should apply to s.Name, because it should be the dot in the
|
||||
// package name.
|
||||
r = strings.LastIndex(s.Name[pathend:], ".")
|
||||
return s.Name[pathend+l+1 : pathend+r]
|
||||
}
|
||||
|
||||
// BaseName returns the symbol name without the package or receiver name.
|
||||
func (s *Sym) BaseName() string {
|
||||
name := s.nameWithoutInst()
|
||||
if i := strings.LastIndex(name, "."); i != -1 {
|
||||
if s.Name != name {
|
||||
brack := strings.Index(s.Name, "[")
|
||||
if i > brack {
|
||||
// BaseName is a method name after the brackets, so
|
||||
// recalculate for s.Name. Otherwise, i applies
|
||||
// correctly to s.Name, since it is before the
|
||||
// brackets.
|
||||
i = strings.LastIndex(s.Name, ".")
|
||||
}
|
||||
}
|
||||
return s.Name[i+1:]
|
||||
}
|
||||
return s.Name
|
||||
}
|
||||
|
||||
// A Func collects information about a single function.
|
||||
type Func struct {
|
||||
Entry uint64
|
||||
*Sym
|
||||
End uint64
|
||||
Params []*Sym // nil for Go 1.3 and later binaries
|
||||
Locals []*Sym // nil for Go 1.3 and later binaries
|
||||
FrameSize int
|
||||
LineTable *LineTable
|
||||
Obj *Obj
|
||||
// Addition: extra data to support inlining.
|
||||
inlTree
|
||||
}
|
||||
|
||||
// An Obj represents a collection of functions in a symbol table.
|
||||
//
|
||||
// The exact method of division of a binary into separate Objs is an internal detail
|
||||
// of the symbol table format.
|
||||
//
|
||||
// In early versions of Go each source file became a different Obj.
|
||||
//
|
||||
// In Go 1 and Go 1.1, each package produced one Obj for all Go sources
|
||||
// and one Obj per C source file.
|
||||
//
|
||||
// In Go 1.2, there is a single Obj for the entire program.
|
||||
type Obj struct {
|
||||
// Funcs is a list of functions in the Obj.
|
||||
Funcs []Func
|
||||
|
||||
// In Go 1.1 and earlier, Paths is a list of symbols corresponding
|
||||
// to the source file names that produced the Obj.
|
||||
// In Go 1.2, Paths is nil.
|
||||
// Use the keys of Table.Files to obtain a list of source files.
|
||||
Paths []Sym // meta
|
||||
}
|
||||
|
||||
/*
|
||||
* Symbol tables
|
||||
*/
|
||||
|
||||
// Table represents a Go symbol table. It stores all of the
|
||||
// symbols decoded from the program and provides methods to translate
|
||||
// between symbols, names, and addresses.
|
||||
type Table struct {
|
||||
Syms []Sym // nil for Go 1.3 and later binaries
|
||||
Funcs []Func
|
||||
Files map[string]*Obj // for Go 1.2 and later all files map to one Obj
|
||||
Objs []Obj // for Go 1.2 and later only one Obj in slice
|
||||
|
||||
go12line *LineTable // Go 1.2 line number table
|
||||
}
|
||||
|
||||
type sym struct {
|
||||
value uint64
|
||||
gotype uint64
|
||||
typ byte
|
||||
name []byte
|
||||
}
|
||||
|
||||
var (
|
||||
littleEndianSymtab = []byte{0xFD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}
|
||||
bigEndianSymtab = []byte{0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00}
|
||||
oldLittleEndianSymtab = []byte{0xFE, 0xFF, 0xFF, 0xFF, 0x00, 0x00}
|
||||
)
|
||||
|
||||
func walksymtab(data []byte, fn func(sym) error) error {
|
||||
if len(data) == 0 { // missing symtab is okay
|
||||
return nil
|
||||
}
|
||||
var order binary.ByteOrder = binary.BigEndian
|
||||
newTable := false
|
||||
switch {
|
||||
case bytes.HasPrefix(data, oldLittleEndianSymtab):
|
||||
// Same as Go 1.0, but little endian.
|
||||
// Format was used during interim development between Go 1.0 and Go 1.1.
|
||||
// Should not be widespread, but easy to support.
|
||||
data = data[6:]
|
||||
order = binary.LittleEndian
|
||||
case bytes.HasPrefix(data, bigEndianSymtab):
|
||||
newTable = true
|
||||
case bytes.HasPrefix(data, littleEndianSymtab):
|
||||
newTable = true
|
||||
order = binary.LittleEndian
|
||||
}
|
||||
var ptrsz int
|
||||
if newTable {
|
||||
if len(data) < 8 {
|
||||
return &DecodingError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
ptrsz = int(data[7])
|
||||
if ptrsz != 4 && ptrsz != 8 {
|
||||
return &DecodingError{7, "invalid pointer size", ptrsz}
|
||||
}
|
||||
data = data[8:]
|
||||
}
|
||||
var s sym
|
||||
p := data
|
||||
for len(p) >= 4 {
|
||||
var typ byte
|
||||
if newTable {
|
||||
// Symbol type, value, Go type.
|
||||
typ = p[0] & 0x3F
|
||||
wideValue := p[0]&0x40 != 0
|
||||
goType := p[0]&0x80 != 0
|
||||
if typ < 26 {
|
||||
typ += 'A'
|
||||
} else {
|
||||
typ += 'a' - 26
|
||||
}
|
||||
s.typ = typ
|
||||
p = p[1:]
|
||||
if wideValue {
|
||||
if len(p) < ptrsz {
|
||||
return &DecodingError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
// fixed-width value
|
||||
if ptrsz == 8 {
|
||||
s.value = order.Uint64(p[0:8])
|
||||
p = p[8:]
|
||||
} else {
|
||||
s.value = uint64(order.Uint32(p[0:4]))
|
||||
p = p[4:]
|
||||
}
|
||||
} else {
|
||||
// varint value
|
||||
s.value = 0
|
||||
shift := uint(0)
|
||||
for len(p) > 0 && p[0]&0x80 != 0 {
|
||||
s.value |= uint64(p[0]&0x7F) << shift
|
||||
shift += 7
|
||||
p = p[1:]
|
||||
}
|
||||
if len(p) == 0 {
|
||||
return &DecodingError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
s.value |= uint64(p[0]) << shift
|
||||
p = p[1:]
|
||||
}
|
||||
if goType {
|
||||
if len(p) < ptrsz {
|
||||
return &DecodingError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
// fixed-width go type
|
||||
if ptrsz == 8 {
|
||||
s.gotype = order.Uint64(p[0:8])
|
||||
p = p[8:]
|
||||
} else {
|
||||
s.gotype = uint64(order.Uint32(p[0:4]))
|
||||
p = p[4:]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Value, symbol type.
|
||||
s.value = uint64(order.Uint32(p[0:4]))
|
||||
if len(p) < 5 {
|
||||
return &DecodingError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
typ = p[4]
|
||||
if typ&0x80 == 0 {
|
||||
return &DecodingError{len(data) - len(p) + 4, "bad symbol type", typ}
|
||||
}
|
||||
typ &^= 0x80
|
||||
s.typ = typ
|
||||
p = p[5:]
|
||||
}
|
||||
|
||||
// Name.
|
||||
var i int
|
||||
var nnul int
|
||||
for i = 0; i < len(p); i++ {
|
||||
if p[i] == 0 {
|
||||
nnul = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
switch typ {
|
||||
case 'z', 'Z':
|
||||
p = p[i+nnul:]
|
||||
for i = 0; i+2 <= len(p); i += 2 {
|
||||
if p[i] == 0 && p[i+1] == 0 {
|
||||
nnul = 2
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(p) < i+nnul {
|
||||
return &DecodingError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
s.name = p[0:i]
|
||||
i += nnul
|
||||
p = p[i:]
|
||||
|
||||
if !newTable {
|
||||
if len(p) < 4 {
|
||||
return &DecodingError{len(data), "unexpected EOF", nil}
|
||||
}
|
||||
// Go type.
|
||||
s.gotype = uint64(order.Uint32(p[:4]))
|
||||
p = p[4:]
|
||||
}
|
||||
_ = fn(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewTable decodes the Go symbol table (the ".gosymtab" section in ELF),
|
||||
// returning an in-memory representation.
|
||||
// Starting with Go 1.3, the Go symbol table no longer includes symbol data.
|
||||
func NewTable(symtab []byte, pcln *LineTable) (*Table, error) {
|
||||
var n int
|
||||
err := walksymtab(symtab, func(s sym) error {
|
||||
n++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var t Table
|
||||
if pcln.isGo12() {
|
||||
t.go12line = pcln
|
||||
}
|
||||
fname := make(map[uint16]string)
|
||||
t.Syms = make([]Sym, 0, n)
|
||||
nf := 0
|
||||
nz := 0
|
||||
lasttyp := uint8(0)
|
||||
err = walksymtab(symtab, func(s sym) error {
|
||||
n := len(t.Syms)
|
||||
t.Syms = t.Syms[0 : n+1]
|
||||
ts := &t.Syms[n]
|
||||
ts.Type = s.typ
|
||||
ts.Value = s.value
|
||||
ts.GoType = s.gotype
|
||||
ts.goVersion = pcln.version
|
||||
switch s.typ {
|
||||
default:
|
||||
// rewrite name to use . instead of · (c2 b7)
|
||||
w := 0
|
||||
b := s.name
|
||||
for i := 0; i < len(b); i++ {
|
||||
if b[i] == 0xc2 && i+1 < len(b) && b[i+1] == 0xb7 {
|
||||
i++
|
||||
b[i] = '.'
|
||||
}
|
||||
b[w] = b[i]
|
||||
w++
|
||||
}
|
||||
ts.Name = string(s.name[0:w])
|
||||
case 'z', 'Z':
|
||||
if lasttyp != 'z' && lasttyp != 'Z' {
|
||||
nz++
|
||||
}
|
||||
for i := 0; i < len(s.name); i += 2 {
|
||||
eltIdx := binary.BigEndian.Uint16(s.name[i : i+2])
|
||||
elt, ok := fname[eltIdx]
|
||||
if !ok {
|
||||
return &DecodingError{-1, "bad filename code", eltIdx}
|
||||
}
|
||||
if n := len(ts.Name); n > 0 && ts.Name[n-1] != '/' {
|
||||
ts.Name += "/"
|
||||
}
|
||||
ts.Name += elt
|
||||
}
|
||||
}
|
||||
switch s.typ {
|
||||
case 'T', 't', 'L', 'l':
|
||||
nf++
|
||||
case 'f':
|
||||
fname[uint16(s.value)] = ts.Name
|
||||
}
|
||||
lasttyp = s.typ
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.Funcs = make([]Func, 0, nf)
|
||||
t.Files = make(map[string]*Obj)
|
||||
|
||||
var obj *Obj
|
||||
if t.go12line != nil {
|
||||
// Put all functions into one Obj.
|
||||
t.Objs = make([]Obj, 1)
|
||||
obj = &t.Objs[0]
|
||||
t.go12line.go12MapFiles(t.Files, obj)
|
||||
} else {
|
||||
t.Objs = make([]Obj, 0, nz)
|
||||
}
|
||||
|
||||
// Count text symbols and attach frame sizes, parameters, and
|
||||
// locals to them. Also, find object file boundaries.
|
||||
lastf := 0
|
||||
for i := 0; i < len(t.Syms); i++ {
|
||||
sym := &t.Syms[i]
|
||||
switch sym.Type {
|
||||
case 'Z', 'z': // path symbol
|
||||
if t.go12line != nil {
|
||||
// Go 1.2 binaries have the file information elsewhere. Ignore.
|
||||
break
|
||||
}
|
||||
// Finish the current object
|
||||
if obj != nil {
|
||||
obj.Funcs = t.Funcs[lastf:]
|
||||
}
|
||||
lastf = len(t.Funcs)
|
||||
|
||||
// Start new object
|
||||
n := len(t.Objs)
|
||||
t.Objs = t.Objs[0 : n+1]
|
||||
obj = &t.Objs[n]
|
||||
|
||||
// Count & copy path symbols
|
||||
var end int
|
||||
for end = i + 1; end < len(t.Syms); end++ {
|
||||
if c := t.Syms[end].Type; c != 'Z' && c != 'z' {
|
||||
break
|
||||
}
|
||||
}
|
||||
obj.Paths = t.Syms[i:end]
|
||||
i = end - 1 // loop will i++
|
||||
|
||||
// Record file names
|
||||
depth := 0
|
||||
for j := range obj.Paths {
|
||||
s := &obj.Paths[j]
|
||||
if s.Name == "" {
|
||||
depth--
|
||||
} else {
|
||||
if depth == 0 {
|
||||
t.Files[s.Name] = obj
|
||||
}
|
||||
depth++
|
||||
}
|
||||
}
|
||||
|
||||
case 'T', 't', 'L', 'l': // text symbol
|
||||
if n := len(t.Funcs); n > 0 {
|
||||
t.Funcs[n-1].End = sym.Value
|
||||
}
|
||||
if sym.Name == "runtime.etext" || sym.Name == "etext" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Count parameter and local (auto) syms
|
||||
var np, na int
|
||||
var end int
|
||||
countloop:
|
||||
for end = i + 1; end < len(t.Syms); end++ {
|
||||
switch t.Syms[end].Type {
|
||||
case 'T', 't', 'L', 'l', 'Z', 'z':
|
||||
break countloop
|
||||
case 'p':
|
||||
np++
|
||||
case 'a':
|
||||
na++
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in the function symbol
|
||||
n := len(t.Funcs)
|
||||
t.Funcs = t.Funcs[0 : n+1]
|
||||
fn := &t.Funcs[n]
|
||||
sym.Func = fn
|
||||
fn.Params = make([]*Sym, 0, np)
|
||||
fn.Locals = make([]*Sym, 0, na)
|
||||
fn.Sym = sym
|
||||
fn.Entry = sym.Value
|
||||
fn.Obj = obj
|
||||
if t.go12line != nil {
|
||||
// All functions share the same line table.
|
||||
// It knows how to narrow down to a specific
|
||||
// function quickly.
|
||||
fn.LineTable = t.go12line
|
||||
} else if pcln != nil {
|
||||
fn.LineTable = pcln.slice(fn.Entry)
|
||||
pcln = fn.LineTable
|
||||
}
|
||||
for j := i; j < end; j++ {
|
||||
s := &t.Syms[j]
|
||||
switch s.Type {
|
||||
case 'm':
|
||||
fn.FrameSize = int(s.Value)
|
||||
case 'p':
|
||||
n := len(fn.Params)
|
||||
fn.Params = fn.Params[0 : n+1]
|
||||
fn.Params[n] = s
|
||||
case 'a':
|
||||
n := len(fn.Locals)
|
||||
fn.Locals = fn.Locals[0 : n+1]
|
||||
fn.Locals[n] = s
|
||||
}
|
||||
}
|
||||
i = end - 1 // loop will i++
|
||||
}
|
||||
}
|
||||
|
||||
if t.go12line != nil && nf == 0 {
|
||||
t.Funcs = t.go12line.go12Funcs()
|
||||
}
|
||||
if obj != nil {
|
||||
obj.Funcs = t.Funcs[lastf:]
|
||||
}
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
// PCToFunc returns the function containing the program counter pc,
|
||||
// or nil if there is no such function.
|
||||
func (t *Table) PCToFunc(pc uint64) *Func {
|
||||
funcs := t.Funcs
|
||||
for len(funcs) > 0 {
|
||||
m := len(funcs) / 2
|
||||
fn := &funcs[m]
|
||||
switch {
|
||||
case pc < fn.Entry:
|
||||
funcs = funcs[0:m]
|
||||
case fn.Entry <= pc && pc < fn.End:
|
||||
return fn
|
||||
default:
|
||||
funcs = funcs[m+1:]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PCToLine looks up line number information for a program counter.
|
||||
// If there is no information, it returns fn == nil.
|
||||
func (t *Table) PCToLine(pc uint64) (file string, line int, fn *Func) {
|
||||
if fn = t.PCToFunc(pc); fn == nil {
|
||||
return
|
||||
}
|
||||
if t.go12line != nil {
|
||||
file = t.go12line.go12PCToFile(pc)
|
||||
line = t.go12line.go12PCToLine(pc)
|
||||
} else {
|
||||
file, line = fn.Obj.lineFromAline(fn.LineTable.PCToLine(pc))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// LineToPC looks up the first program counter on the given line in
|
||||
// the named file. It returns UnknownPathError or UnknownLineError if
|
||||
// there is an error looking up this line.
|
||||
func (t *Table) LineToPC(file string, line int) (pc uint64, fn *Func, err error) {
|
||||
obj, ok := t.Files[file]
|
||||
if !ok {
|
||||
return 0, nil, UnknownFileError(file)
|
||||
}
|
||||
|
||||
if t.go12line != nil {
|
||||
pc := t.go12line.go12LineToPC(file, line)
|
||||
if pc == 0 {
|
||||
return 0, nil, &UnknownLineError{file, line}
|
||||
}
|
||||
return pc, t.PCToFunc(pc), nil
|
||||
}
|
||||
|
||||
abs, err := obj.alineFromLine(file, line)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for i := range obj.Funcs {
|
||||
f := &obj.Funcs[i]
|
||||
pc := f.LineTable.LineToPC(abs, f.End)
|
||||
if pc != 0 {
|
||||
return pc, f, nil
|
||||
}
|
||||
}
|
||||
return 0, nil, &UnknownLineError{file, line}
|
||||
}
|
||||
|
||||
// LookupSym returns the text, data, or bss symbol with the given name,
|
||||
// or nil if no such symbol is found.
|
||||
func (t *Table) LookupSym(name string) *Sym {
|
||||
// TODO(austin) Maybe make a map
|
||||
for i := range t.Syms {
|
||||
s := &t.Syms[i]
|
||||
switch s.Type {
|
||||
case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b':
|
||||
if s.Name == name {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookupFunc returns the text, data, or bss symbol with the given name,
|
||||
// or nil if no such symbol is found.
|
||||
func (t *Table) LookupFunc(name string) *Func {
|
||||
for i := range t.Funcs {
|
||||
f := &t.Funcs[i]
|
||||
if f.Sym.Name == name {
|
||||
return f
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SymByAddr returns the text, data, or bss symbol starting at the given address.
|
||||
func (t *Table) SymByAddr(addr uint64) *Sym {
|
||||
for i := range t.Syms {
|
||||
s := &t.Syms[i]
|
||||
switch s.Type {
|
||||
case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b':
|
||||
if s.Value == addr {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
* Object files
|
||||
*/
|
||||
|
||||
// This is legacy code for Go 1.1 and earlier, which used the
|
||||
// Plan 9 format for pc-line tables. This code was never quite
|
||||
// correct. It's probably very close, and it's usually correct, but
|
||||
// we never quite found all the corner cases.
|
||||
//
|
||||
// Go 1.2 and later use a simpler format, documented at golang.org/s/go12symtab.
|
||||
|
||||
func (o *Obj) lineFromAline(aline int) (string, int) {
|
||||
type stackEnt struct {
|
||||
path string
|
||||
start int
|
||||
offset int
|
||||
prev *stackEnt
|
||||
}
|
||||
|
||||
noPath := &stackEnt{"", 0, 0, nil}
|
||||
tos := noPath
|
||||
|
||||
pathloop:
|
||||
for _, s := range o.Paths {
|
||||
val := int(s.Value)
|
||||
switch {
|
||||
case val > aline:
|
||||
break pathloop
|
||||
|
||||
case val == 1:
|
||||
// Start a new stack
|
||||
tos = &stackEnt{s.Name, val, 0, noPath}
|
||||
|
||||
case s.Name == "":
|
||||
// Pop
|
||||
if tos == noPath {
|
||||
return "<malformed symbol table>", 0
|
||||
}
|
||||
tos.prev.offset += val - tos.start
|
||||
tos = tos.prev
|
||||
|
||||
default:
|
||||
// Push
|
||||
tos = &stackEnt{s.Name, val, 0, tos}
|
||||
}
|
||||
}
|
||||
|
||||
if tos == noPath {
|
||||
return "", 0
|
||||
}
|
||||
return tos.path, aline - tos.start - tos.offset + 1
|
||||
}
|
||||
|
||||
func (o *Obj) alineFromLine(path string, line int) (int, error) {
|
||||
if line < 1 {
|
||||
return 0, &UnknownLineError{path, line}
|
||||
}
|
||||
|
||||
for i, s := range o.Paths {
|
||||
// Find this path
|
||||
if s.Name != path {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find this line at this stack level
|
||||
depth := 0
|
||||
var incstart int
|
||||
line += int(s.Value)
|
||||
pathloop:
|
||||
for _, s := range o.Paths[i:] {
|
||||
val := int(s.Value)
|
||||
switch {
|
||||
case depth == 1 && val >= line:
|
||||
return line - 1, nil
|
||||
|
||||
case s.Name == "":
|
||||
depth--
|
||||
if depth == 0 {
|
||||
break pathloop
|
||||
} else if depth == 1 {
|
||||
line += val - incstart
|
||||
}
|
||||
|
||||
default:
|
||||
if depth == 1 {
|
||||
incstart = val
|
||||
}
|
||||
depth++
|
||||
}
|
||||
}
|
||||
return 0, &UnknownLineError{path, line}
|
||||
}
|
||||
return 0, UnknownFileError(path)
|
||||
}
|
||||
|
||||
/*
|
||||
* Errors
|
||||
*/
|
||||
|
||||
// UnknownFileError represents a failure to find the specific file in
|
||||
// the symbol table.
|
||||
type UnknownFileError string
|
||||
|
||||
func (e UnknownFileError) Error() string { return "unknown file: " + string(e) }
|
||||
|
||||
// UnknownLineError represents a failure to map a line to a program
|
||||
// counter, either because the line is beyond the bounds of the file
|
||||
// or because there is no code on the given line.
|
||||
type UnknownLineError struct {
|
||||
File string
|
||||
Line int
|
||||
}
|
||||
|
||||
func (e *UnknownLineError) Error() string {
|
||||
return "no code at " + e.File + ":" + strconv.Itoa(e.Line)
|
||||
}
|
||||
|
||||
// DecodingError represents an error during the decoding of
|
||||
// the symbol table.
|
||||
type DecodingError struct {
|
||||
off int
|
||||
msg string
|
||||
val any
|
||||
}
|
||||
|
||||
func (e *DecodingError) Error() string {
|
||||
msg := e.msg
|
||||
if e.val != nil {
|
||||
msg += fmt.Sprintf(" '%v'", e.val)
|
||||
}
|
||||
msg += fmt.Sprintf(" at byte %#x", e.off)
|
||||
return msg
|
||||
}
|
||||
349
vendor/golang.org/x/vuln/internal/goversion/asm.go
generated
vendored
Normal file
349
vendor/golang.org/x/vuln/internal/goversion/asm.go
generated
vendored
Normal file
@@ -0,0 +1,349 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
package goversion
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type matcher [][]uint32
|
||||
|
||||
const (
|
||||
pWild uint32 = 0xff00
|
||||
pAddr uint32 = 0x10000
|
||||
pEnd uint32 = 0x20000
|
||||
pRelAddr uint32 = 0x30000
|
||||
|
||||
opMaybe = 1 + iota
|
||||
opMust
|
||||
opDone
|
||||
opAnchor = 0x100
|
||||
opSub8 = 0x200
|
||||
opFlags = opAnchor | opSub8
|
||||
)
|
||||
|
||||
var amd64Matcher = matcher{
|
||||
{opMaybe | opAnchor,
|
||||
// __rt0_amd64_darwin:
|
||||
// JMP __rt0_amd64
|
||||
0xe9, pWild | pAddr, pWild, pWild, pWild | pEnd, 0xcc, 0xcc, 0xcc,
|
||||
},
|
||||
{opMaybe,
|
||||
// _rt0_amd64_linux:
|
||||
// lea 0x8(%rsp), %rsi
|
||||
// mov (%rsp), %rdi
|
||||
// lea ADDR(%rip), %rax # main
|
||||
// jmpq *%rax
|
||||
0x48, 0x8d, 0x74, 0x24, 0x08,
|
||||
0x48, 0x8b, 0x3c, 0x24, 0x48,
|
||||
0x8d, 0x05, pWild | pAddr, pWild, pWild, pWild | pEnd,
|
||||
0xff, 0xe0,
|
||||
},
|
||||
{opMaybe,
|
||||
// _rt0_amd64_linux:
|
||||
// lea 0x8(%rsp), %rsi
|
||||
// mov (%rsp), %rdi
|
||||
// mov $ADDR, %eax # main
|
||||
// jmpq *%rax
|
||||
0x48, 0x8d, 0x74, 0x24, 0x08,
|
||||
0x48, 0x8b, 0x3c, 0x24,
|
||||
0xb8, pWild | pAddr, pWild, pWild, pWild,
|
||||
0xff, 0xe0,
|
||||
},
|
||||
{opMaybe,
|
||||
// __rt0_amd64:
|
||||
// mov (%rsp), %rdi
|
||||
// lea 8(%rsp), %rsi
|
||||
// jmp runtime.rt0_g0
|
||||
0x48, 0x8b, 0x3c, 0x24,
|
||||
0x48, 0x8d, 0x74, 0x24, 0x08,
|
||||
0xe9, pWild | pAddr, pWild, pWild, pWild | pEnd,
|
||||
0xcc, 0xcc,
|
||||
},
|
||||
{opMaybe,
|
||||
// _start (toward end)
|
||||
// lea __libc_csu_fini(%rip), %r8
|
||||
// lea __libc_csu_init(%rip), %rcx
|
||||
// lea ADDR(%rip), %rdi # main
|
||||
// callq *xxx(%rip)
|
||||
0x4c, 0x8d, 0x05, pWild, pWild, pWild, pWild,
|
||||
0x48, 0x8d, 0x0d, pWild, pWild, pWild, pWild,
|
||||
0x48, 0x8d, 0x3d, pWild | pAddr, pWild, pWild, pWild | pEnd,
|
||||
0xff, 0x15,
|
||||
},
|
||||
{opMaybe,
|
||||
// _start (toward end)
|
||||
// push %rsp (1)
|
||||
// mov $__libc_csu_fini, %r8 (7)
|
||||
// mov $__libc_csu_init, %rcx (7)
|
||||
// mov $ADDR, %rdi # main (7)
|
||||
// callq *xxx(%rip)
|
||||
0x54,
|
||||
0x49, 0xc7, 0xc0, pWild, pWild, pWild, pWild,
|
||||
0x48, 0xc7, 0xc1, pWild, pWild, pWild, pWild,
|
||||
0x48, 0xc7, 0xc7, pAddr | pWild, pWild, pWild, pWild,
|
||||
},
|
||||
{opMaybe | opAnchor,
|
||||
// main:
|
||||
// lea ADDR(%rip), %rax # rt0_go
|
||||
// jmpq *%rax
|
||||
0x48, 0x8d, 0x05, pWild | pAddr, pWild, pWild, pWild | pEnd,
|
||||
0xff, 0xe0,
|
||||
},
|
||||
{opMaybe | opAnchor,
|
||||
// main:
|
||||
// mov $ADDR, %eax
|
||||
// jmpq *%rax
|
||||
0xb8, pWild | pAddr, pWild, pWild, pWild,
|
||||
0xff, 0xe0,
|
||||
},
|
||||
{opMaybe | opAnchor,
|
||||
// main:
|
||||
// JMP runtime.rt0_go(SB)
|
||||
0xe9, pWild | pAddr, pWild, pWild, pWild | pEnd, 0xcc, 0xcc, 0xcc,
|
||||
},
|
||||
{opMust | opAnchor,
|
||||
// rt0_go:
|
||||
// mov %rdi, %rax
|
||||
// mov %rsi, %rbx
|
||||
// sub %0x27, %rsp
|
||||
// and $0xfffffffffffffff0,%rsp
|
||||
// mov %rax,0x10(%rsp)
|
||||
// mov %rbx,0x18(%rsp)
|
||||
0x48, 0x89, 0xf8,
|
||||
0x48, 0x89, 0xf3,
|
||||
0x48, 0x83, 0xec, 0x27,
|
||||
0x48, 0x83, 0xe4, 0xf0,
|
||||
0x48, 0x89, 0x44, 0x24, 0x10,
|
||||
0x48, 0x89, 0x5c, 0x24, 0x18,
|
||||
},
|
||||
{opMust,
|
||||
// later in rt0_go:
|
||||
// mov %eax, (%rsp)
|
||||
// mov 0x18(%rsp), %rax
|
||||
// mov %rax, 0x8(%rsp)
|
||||
// callq runtime.args
|
||||
// callq runtime.osinit
|
||||
// callq runtime.schedinit (ADDR)
|
||||
0x89, 0x04, 0x24,
|
||||
0x48, 0x8b, 0x44, 0x24, 0x18,
|
||||
0x48, 0x89, 0x44, 0x24, 0x08,
|
||||
0xe8, pWild, pWild, pWild, pWild,
|
||||
0xe8, pWild, pWild, pWild, pWild,
|
||||
0xe8, pWild, pWild, pWild, pWild,
|
||||
},
|
||||
{opMaybe,
|
||||
// later in rt0_go:
|
||||
// mov %eax, (%rsp)
|
||||
// mov 0x18(%rsp), %rax
|
||||
// mov %rax, 0x8(%rsp)
|
||||
// callq runtime.args
|
||||
// callq runtime.osinit
|
||||
// callq runtime.schedinit (ADDR)
|
||||
// lea other(%rip), %rdi
|
||||
0x89, 0x04, 0x24,
|
||||
0x48, 0x8b, 0x44, 0x24, 0x18,
|
||||
0x48, 0x89, 0x44, 0x24, 0x08,
|
||||
0xe8, pWild, pWild, pWild, pWild,
|
||||
0xe8, pWild, pWild, pWild, pWild,
|
||||
0xe8, pWild | pAddr, pWild, pWild, pWild | pEnd,
|
||||
0x48, 0x8d, 0x05,
|
||||
},
|
||||
{opMaybe,
|
||||
// later in rt0_go:
|
||||
// mov %eax, (%rsp)
|
||||
// mov 0x18(%rsp), %rax
|
||||
// mov %rax, 0x8(%rsp)
|
||||
// callq runtime.args
|
||||
// callq runtime.osinit
|
||||
// callq runtime.hashinit
|
||||
// callq runtime.schedinit (ADDR)
|
||||
// pushq $main.main
|
||||
0x89, 0x04, 0x24,
|
||||
0x48, 0x8b, 0x44, 0x24, 0x18,
|
||||
0x48, 0x89, 0x44, 0x24, 0x08,
|
||||
0xe8, pWild, pWild, pWild, pWild,
|
||||
0xe8, pWild, pWild, pWild, pWild,
|
||||
0xe8, pWild, pWild, pWild, pWild,
|
||||
0xe8, pWild | pAddr, pWild, pWild, pWild | pEnd,
|
||||
0x68,
|
||||
},
|
||||
{opDone | opSub8,
|
||||
// schedinit (toward end)
|
||||
// mov ADDR(%rip), %rax
|
||||
// test %rax, %rax
|
||||
// jne <short>
|
||||
// movq $0x7, ADDR(%rip)
|
||||
//
|
||||
0x48, 0x8b, 0x05, pWild, pWild, pWild, pWild,
|
||||
0x48, 0x85, 0xc0,
|
||||
0x75, pWild,
|
||||
0x48, 0xc7, 0x05, pWild | pAddr, pWild, pWild, pWild, 0x07, 0x00, 0x00, 0x00 | pEnd,
|
||||
},
|
||||
{opDone | opSub8,
|
||||
// schedinit (toward end)
|
||||
// mov ADDR(%rip), %rbx
|
||||
// cmp $0x0, %rbx
|
||||
// jne <short>
|
||||
// lea "unknown"(%rip), %rbx
|
||||
// mov %rbx, ADDR(%rip)
|
||||
// movq $7, (ADDR+8)(%rip)
|
||||
0x48, 0x8b, 0x1d, pWild, pWild, pWild, pWild,
|
||||
0x48, 0x83, 0xfb, 0x00,
|
||||
0x75, pWild,
|
||||
0x48, 0x8d, 0x1d, pWild, pWild, pWild, pWild,
|
||||
0x48, 0x89, 0x1d, pWild, pWild, pWild, pWild,
|
||||
0x48, 0xc7, 0x05, pWild | pAddr, pWild, pWild, pWild, 0x07, 0x00, 0x00, 0x00 | pEnd,
|
||||
},
|
||||
{opDone,
|
||||
// schedinit (toward end)
|
||||
// cmpq $0x0, ADDR(%rip)
|
||||
// jne <short>
|
||||
// lea "unknown"(%rip), %rax
|
||||
// mov %rax, ADDR(%rip)
|
||||
// lea ADDR(%rip), %rax
|
||||
// movq $7, 8(%rax)
|
||||
0x48, 0x83, 0x3d, pWild | pAddr, pWild, pWild, pWild, 0x00,
|
||||
0x75, pWild,
|
||||
0x48, 0x8d, 0x05, pWild, pWild, pWild, pWild,
|
||||
0x48, 0x89, 0x05, pWild, pWild, pWild, pWild,
|
||||
0x48, 0x8d, 0x05, pWild | pAddr, pWild, pWild, pWild | pEnd,
|
||||
0x48, 0xc7, 0x40, 0x08, 0x07, 0x00, 0x00, 0x00,
|
||||
},
|
||||
{opDone,
|
||||
// schedinit (toward end)
|
||||
// cmpq $0x0, ADDR(%rip)
|
||||
// jne <short>
|
||||
// movq $0x7, ADDR(%rip)
|
||||
0x48, 0x83, 0x3d, pWild | pAddr, pWild, pWild, pWild, 0x00,
|
||||
0x75, pWild,
|
||||
0x48, 0xc7, 0x05 | pEnd, pWild | pAddr, pWild, pWild, pWild, 0x07, 0x00, 0x00, 0x00,
|
||||
},
|
||||
{opDone,
|
||||
// test %eax, %eax
|
||||
// jne <later>
|
||||
// lea "unknown"(RIP), %rax
|
||||
// mov %rax, ADDR(%rip)
|
||||
0x48, 0x85, 0xc0, 0x75, pWild, 0x48, 0x8d, 0x05, pWild, pWild, pWild, pWild, 0x48, 0x89, 0x05, pWild | pAddr, pWild, pWild, pWild | pEnd,
|
||||
},
|
||||
{opDone,
|
||||
// schedinit (toward end)
|
||||
// mov ADDR(%rip), %rcx
|
||||
// test %rcx, %rcx
|
||||
// jne <short>
|
||||
// movq $0x7, ADDR(%rip)
|
||||
//
|
||||
0x48, 0x8b, 0x0d, pWild, pWild, pWild, pWild,
|
||||
0x48, 0x85, 0xc9,
|
||||
0x75, pWild,
|
||||
0x48, 0xc7, 0x05 | pEnd, pWild | pAddr, pWild, pWild, pWild, 0x07, 0x00, 0x00, 0x00,
|
||||
},
|
||||
}
|
||||
|
||||
var DebugMatch bool
|
||||
|
||||
func (m matcher) match(f exe, addr uint64) (uint64, bool) {
|
||||
data, err := f.ReadData(addr, 512)
|
||||
if DebugMatch {
|
||||
fmt.Fprintf(os.Stderr, "data @%#x: %x\n", addr, data[:16])
|
||||
}
|
||||
if err != nil {
|
||||
if DebugMatch {
|
||||
fmt.Fprintf(os.Stderr, "match: %v\n", err)
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
if DebugMatch {
|
||||
fmt.Fprintf(os.Stderr, "data: %x\n", data[:32])
|
||||
}
|
||||
Matchers:
|
||||
for pc, p := range m {
|
||||
op := p[0]
|
||||
p = p[1:]
|
||||
Search:
|
||||
for i := 0; i <= len(data)-len(p); i++ {
|
||||
a := -1
|
||||
e := -1
|
||||
if i > 0 && op&opAnchor != 0 {
|
||||
break
|
||||
}
|
||||
for j := 0; j < len(p); j++ {
|
||||
b := byte(p[j])
|
||||
m := byte(p[j] >> 8)
|
||||
if data[i+j]&^m != b {
|
||||
continue Search
|
||||
}
|
||||
if p[j]&pAddr != 0 {
|
||||
a = j
|
||||
}
|
||||
if p[j]&pEnd != 0 {
|
||||
e = j + 1
|
||||
}
|
||||
}
|
||||
// matched
|
||||
if DebugMatch {
|
||||
fmt.Fprintf(os.Stderr, "match (%d) %#x+%d %x %x\n", pc, addr, i, p, data[i:i+len(p)])
|
||||
}
|
||||
if a != -1 {
|
||||
val := uint64(int32(binary.LittleEndian.Uint32(data[i+a:])))
|
||||
if e == -1 {
|
||||
addr = val
|
||||
} else {
|
||||
addr += uint64(i+e) + val
|
||||
}
|
||||
if op&opSub8 != 0 {
|
||||
addr -= 8
|
||||
}
|
||||
}
|
||||
if op&^opFlags == opDone {
|
||||
if DebugMatch {
|
||||
fmt.Fprintf(os.Stderr, "done %x\n", addr)
|
||||
}
|
||||
return addr, true
|
||||
}
|
||||
if a != -1 {
|
||||
// changed addr, so reload
|
||||
data, err = f.ReadData(addr, 512)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
if DebugMatch {
|
||||
fmt.Fprintf(os.Stderr, "reload @%#x: %x\n", addr, data[:32])
|
||||
}
|
||||
}
|
||||
continue Matchers
|
||||
}
|
||||
// not matched
|
||||
if DebugMatch {
|
||||
fmt.Fprintf(os.Stderr, "no match (%d) %#x %x %x\n", pc, addr, p, data[:32])
|
||||
}
|
||||
if op&^opFlags == opMust {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
// ran off end of matcher
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func readBuildVersionX86Asm(f exe) (isGo bool, buildVersion string) {
|
||||
entry := f.Entry()
|
||||
if entry == 0 {
|
||||
if DebugMatch {
|
||||
fmt.Fprintf(os.Stderr, "missing entry!\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
addr, ok := amd64Matcher.match(f, entry)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
v, err := readBuildVersion(f, addr, 16)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return true, v
|
||||
}
|
||||
324
vendor/golang.org/x/vuln/internal/goversion/exe.go
generated
vendored
Normal file
324
vendor/golang.org/x/vuln/internal/goversion/exe.go
generated
vendored
Normal file
@@ -0,0 +1,324 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
package goversion
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"debug/macho"
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type sym struct {
|
||||
Name string
|
||||
Addr uint64
|
||||
Size uint64
|
||||
}
|
||||
|
||||
type exe interface {
|
||||
AddrSize() int // bytes
|
||||
ReadData(addr, size uint64) ([]byte, error)
|
||||
Symbols() ([]sym, error)
|
||||
SectionNames() []string
|
||||
Close() error
|
||||
ByteOrder() binary.ByteOrder
|
||||
Entry() uint64
|
||||
TextRange() (uint64, uint64)
|
||||
RODataRange() (uint64, uint64)
|
||||
}
|
||||
|
||||
func openExe(file string) (exe, error) {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]byte, 16)
|
||||
if _, err := io.ReadFull(f, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Seek(0, 0)
|
||||
if bytes.HasPrefix(data, []byte("\x7FELF")) {
|
||||
e, err := elf.NewFile(f)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &elfExe{f, e}, nil
|
||||
}
|
||||
if bytes.HasPrefix(data, []byte("MZ")) {
|
||||
e, err := pe.NewFile(f)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &peExe{f, e}, nil
|
||||
}
|
||||
if bytes.HasPrefix(data, []byte("\xFE\xED\xFA")) || bytes.HasPrefix(data[1:], []byte("\xFA\xED\xFE")) {
|
||||
e, err := macho.NewFile(f)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &machoExe{f, e}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unrecognized executable format")
|
||||
}
|
||||
|
||||
type elfExe struct {
|
||||
os *os.File
|
||||
f *elf.File
|
||||
}
|
||||
|
||||
func (x *elfExe) AddrSize() int { return 0 }
|
||||
|
||||
func (x *elfExe) ByteOrder() binary.ByteOrder { return x.f.ByteOrder }
|
||||
|
||||
func (x *elfExe) Close() error {
|
||||
return x.os.Close()
|
||||
}
|
||||
|
||||
func (x *elfExe) Entry() uint64 { return x.f.Entry }
|
||||
|
||||
func (x *elfExe) ReadData(addr, size uint64) ([]byte, error) {
|
||||
for _, prog := range x.f.Progs {
|
||||
// The following line was commented from the original code.
|
||||
//fmt.Printf("%#x %#x %#x\n", addr, prog.Vaddr, prog.Vaddr+prog.Filesz)
|
||||
if prog.Vaddr <= addr && addr <= prog.Vaddr+prog.Filesz-1 {
|
||||
n := prog.Vaddr + prog.Filesz - addr
|
||||
if n > size {
|
||||
n = size
|
||||
}
|
||||
data := make([]byte, n)
|
||||
_, err := prog.ReadAt(data, int64(addr-prog.Vaddr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("address not mapped")
|
||||
}
|
||||
|
||||
func (x *elfExe) Symbols() ([]sym, error) {
|
||||
syms, err := x.f.Symbols()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []sym
|
||||
for _, s := range syms {
|
||||
out = append(out, sym{s.Name, s.Value, s.Size})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (x *elfExe) SectionNames() []string {
|
||||
var names []string
|
||||
for _, sect := range x.f.Sections {
|
||||
names = append(names, sect.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (x *elfExe) TextRange() (uint64, uint64) {
|
||||
for _, p := range x.f.Progs {
|
||||
if p.Type == elf.PT_LOAD && p.Flags&elf.PF_X != 0 {
|
||||
return p.Vaddr, p.Vaddr + p.Filesz
|
||||
}
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func (x *elfExe) RODataRange() (uint64, uint64) {
|
||||
for _, p := range x.f.Progs {
|
||||
if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_R|elf.PF_W|elf.PF_X) == elf.PF_R {
|
||||
return p.Vaddr, p.Vaddr + p.Filesz
|
||||
}
|
||||
}
|
||||
for _, p := range x.f.Progs {
|
||||
if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_R|elf.PF_W|elf.PF_X) == (elf.PF_R|elf.PF_X) {
|
||||
return p.Vaddr, p.Vaddr + p.Filesz
|
||||
}
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
type peExe struct {
|
||||
os *os.File
|
||||
f *pe.File
|
||||
}
|
||||
|
||||
func (x *peExe) imageBase() uint64 {
|
||||
switch oh := x.f.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
return uint64(oh.ImageBase)
|
||||
case *pe.OptionalHeader64:
|
||||
return oh.ImageBase
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *peExe) AddrSize() int {
|
||||
if x.f.Machine == pe.IMAGE_FILE_MACHINE_AMD64 {
|
||||
return 8
|
||||
}
|
||||
return 4
|
||||
}
|
||||
|
||||
func (x *peExe) ByteOrder() binary.ByteOrder { return binary.LittleEndian }
|
||||
|
||||
func (x *peExe) Close() error {
|
||||
return x.os.Close()
|
||||
}
|
||||
|
||||
func (x *peExe) Entry() uint64 {
|
||||
switch oh := x.f.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
return uint64(oh.ImageBase + oh.AddressOfEntryPoint)
|
||||
case *pe.OptionalHeader64:
|
||||
return oh.ImageBase + uint64(oh.AddressOfEntryPoint)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *peExe) ReadData(addr, size uint64) ([]byte, error) {
|
||||
addr -= x.imageBase()
|
||||
data := make([]byte, size)
|
||||
for _, sect := range x.f.Sections {
|
||||
if uint64(sect.VirtualAddress) <= addr && addr+size-1 <= uint64(sect.VirtualAddress+sect.Size-1) {
|
||||
_, err := sect.ReadAt(data, int64(addr-uint64(sect.VirtualAddress)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("address not mapped")
|
||||
}
|
||||
|
||||
func (x *peExe) Symbols() ([]sym, error) {
|
||||
base := x.imageBase()
|
||||
var out []sym
|
||||
for _, s := range x.f.Symbols {
|
||||
if s.SectionNumber <= 0 || int(s.SectionNumber) > len(x.f.Sections) {
|
||||
continue
|
||||
}
|
||||
sect := x.f.Sections[s.SectionNumber-1]
|
||||
out = append(out, sym{s.Name, uint64(s.Value) + base + uint64(sect.VirtualAddress), 0})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (x *peExe) SectionNames() []string {
|
||||
var names []string
|
||||
for _, sect := range x.f.Sections {
|
||||
names = append(names, sect.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (x *peExe) TextRange() (uint64, uint64) {
|
||||
// Assume text is first non-empty section.
|
||||
for _, sect := range x.f.Sections {
|
||||
if sect.VirtualAddress != 0 && sect.Size != 0 {
|
||||
return uint64(sect.VirtualAddress) + x.imageBase(), uint64(sect.VirtualAddress+sect.Size) + x.imageBase()
|
||||
}
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func (x *peExe) RODataRange() (uint64, uint64) {
|
||||
return x.TextRange()
|
||||
}
|
||||
|
||||
type machoExe struct {
|
||||
os *os.File
|
||||
f *macho.File
|
||||
}
|
||||
|
||||
func (x *machoExe) AddrSize() int {
|
||||
if x.f.Cpu&0x01000000 != 0 {
|
||||
return 8
|
||||
}
|
||||
return 4
|
||||
}
|
||||
|
||||
func (x *machoExe) ByteOrder() binary.ByteOrder { return x.f.ByteOrder }
|
||||
|
||||
func (x *machoExe) Close() error {
|
||||
return x.os.Close()
|
||||
}
|
||||
|
||||
func (x *machoExe) Entry() uint64 {
|
||||
for _, load := range x.f.Loads {
|
||||
b, ok := load.(macho.LoadBytes)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// TODO: Other thread states.
|
||||
bo := x.f.ByteOrder
|
||||
const x86_THREAD_STATE64 = 4
|
||||
cmd, siz := macho.LoadCmd(bo.Uint32(b[0:4])), bo.Uint32(b[4:8])
|
||||
if cmd == macho.LoadCmdUnixThread && siz == 184 && bo.Uint32(b[8:12]) == x86_THREAD_STATE64 {
|
||||
return bo.Uint64(b[144:])
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *machoExe) ReadData(addr, size uint64) ([]byte, error) {
|
||||
data := make([]byte, size)
|
||||
for _, load := range x.f.Loads {
|
||||
seg, ok := load.(*macho.Segment)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if seg.Addr <= addr && addr+size-1 <= seg.Addr+seg.Filesz-1 {
|
||||
if seg.Name == "__PAGEZERO" {
|
||||
continue
|
||||
}
|
||||
_, err := seg.ReadAt(data, int64(addr-seg.Addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("address not mapped")
|
||||
}
|
||||
|
||||
func (x *machoExe) Symbols() ([]sym, error) {
|
||||
var out []sym
|
||||
for _, s := range x.f.Symtab.Syms {
|
||||
out = append(out, sym{s.Name, s.Value, 0})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (x *machoExe) SectionNames() []string {
|
||||
var names []string
|
||||
for _, sect := range x.f.Sections {
|
||||
names = append(names, sect.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (x *machoExe) TextRange() (uint64, uint64) {
|
||||
// Assume text is first non-empty segment.
|
||||
for _, load := range x.f.Loads {
|
||||
seg, ok := load.(*macho.Segment)
|
||||
if ok && seg.Name != "__PAGEZERO" && seg.Addr != 0 && seg.Filesz != 0 {
|
||||
return seg.Addr, seg.Addr + seg.Filesz
|
||||
}
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func (x *machoExe) RODataRange() (uint64, uint64) {
|
||||
return x.TextRange()
|
||||
}
|
||||
246
vendor/golang.org/x/vuln/internal/goversion/read.go
generated
vendored
Normal file
246
vendor/golang.org/x/vuln/internal/goversion/read.go
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
// Package goversion reports the Go version used to build program executables.
|
||||
//
|
||||
// This is a copy of rsc.io/goversion/version. We renamed the package to goversion
|
||||
// to differentiate between version package in standard library that we also use.
|
||||
package goversion
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Version is the information reported by ReadExe.
|
||||
type Version struct {
|
||||
Release string // Go version (runtime.Version in the program)
|
||||
ModuleInfo string // program's module information
|
||||
BoringCrypto bool // program uses BoringCrypto
|
||||
StandardCrypto bool // program uses standard crypto (replaced by BoringCrypto)
|
||||
FIPSOnly bool // program imports "crypto/tls/fipsonly"
|
||||
}
|
||||
|
||||
// ReadExe reports information about the Go version used to build
|
||||
// the program executable named by file.
|
||||
func ReadExe(file string) (Version, error) {
|
||||
var v Version
|
||||
f, err := openExe(file)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
defer f.Close()
|
||||
isGo := false
|
||||
for _, name := range f.SectionNames() {
|
||||
if name == ".note.go.buildid" {
|
||||
isGo = true
|
||||
}
|
||||
}
|
||||
syms, symsErr := f.Symbols()
|
||||
isGccgo := false
|
||||
for _, sym := range syms {
|
||||
name := sym.Name
|
||||
if name == "runtime.main" || name == "main.main" {
|
||||
isGo = true
|
||||
}
|
||||
if strings.HasPrefix(name, "runtime.") && strings.HasSuffix(name, "$descriptor") {
|
||||
isGccgo = true
|
||||
}
|
||||
if name == "runtime.buildVersion" {
|
||||
isGo = true
|
||||
release, err := readBuildVersion(f, sym.Addr, sym.Size)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
v.Release = release
|
||||
}
|
||||
// Note: Using strings.HasPrefix because Go 1.17+ adds ".abi0" to many of these symbols.
|
||||
if strings.Contains(name, "_Cfunc__goboringcrypto_") || strings.HasPrefix(name, "crypto/internal/boring/sig.BoringCrypto") {
|
||||
v.BoringCrypto = true
|
||||
}
|
||||
if strings.HasPrefix(name, "crypto/internal/boring/sig.FIPSOnly") {
|
||||
v.FIPSOnly = true
|
||||
}
|
||||
for _, re := range standardCryptoNames {
|
||||
if re.MatchString(name) {
|
||||
v.StandardCrypto = true
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(name, "crypto/internal/boring/sig.StandardCrypto") {
|
||||
v.StandardCrypto = true
|
||||
}
|
||||
}
|
||||
|
||||
if DebugMatch {
|
||||
v.Release = ""
|
||||
}
|
||||
if err := findModuleInfo(&v, f); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if v.Release == "" {
|
||||
g, release := readBuildVersionX86Asm(f)
|
||||
if g {
|
||||
isGo = true
|
||||
v.Release = release
|
||||
if err := findCryptoSigs(&v, f); err != nil {
|
||||
return v, err
|
||||
}
|
||||
}
|
||||
}
|
||||
if isGccgo && v.Release == "" {
|
||||
isGo = true
|
||||
v.Release = "gccgo (version unknown)"
|
||||
}
|
||||
if !isGo && symsErr != nil {
|
||||
return v, symsErr
|
||||
}
|
||||
|
||||
if !isGo {
|
||||
return v, errors.New("not a Go executable")
|
||||
}
|
||||
if v.Release == "" {
|
||||
v.Release = "unknown Go version"
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
var re = regexp.MustCompile
|
||||
|
||||
var standardCryptoNames = []*regexp.Regexp{
|
||||
re(`^crypto/sha1\.\(\*digest\)`),
|
||||
re(`^crypto/sha256\.\(\*digest\)`),
|
||||
re(`^crypto/rand\.\(\*devReader\)`),
|
||||
re(`^crypto/rsa\.encrypt(\.abi.)?$`),
|
||||
re(`^crypto/rsa\.decrypt(\.abi.)?$`),
|
||||
}
|
||||
|
||||
func readBuildVersion(f exe, addr, size uint64) (string, error) {
|
||||
if size == 0 {
|
||||
size = uint64(f.AddrSize() * 2)
|
||||
}
|
||||
if size != 8 && size != 16 {
|
||||
return "", fmt.Errorf("invalid size for runtime.buildVersion")
|
||||
}
|
||||
data, err := f.ReadData(addr, size)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("reading runtime.buildVersion: %v", err)
|
||||
}
|
||||
|
||||
if size == 8 {
|
||||
addr = uint64(f.ByteOrder().Uint32(data))
|
||||
size = uint64(f.ByteOrder().Uint32(data[4:]))
|
||||
} else {
|
||||
addr = f.ByteOrder().Uint64(data)
|
||||
size = f.ByteOrder().Uint64(data[8:])
|
||||
}
|
||||
if size > 1000 {
|
||||
return "", fmt.Errorf("implausible string size %d for runtime.buildVersion", size)
|
||||
}
|
||||
|
||||
data, err = f.ReadData(addr, size)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("reading runtime.buildVersion string data: %v", err)
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// Code signatures that indicate BoringCrypto or crypto/internal/fipsonly.
|
||||
// These are not byte literals in order to avoid the actual
|
||||
// byte signatures appearing in the goversion binary,
|
||||
// because on some systems you can't tell rodata from text.
|
||||
var (
|
||||
sigBoringCrypto, _ = hex.DecodeString("EB1DF448F44BF4B332F52813A3B450D441CC2485F001454E92101B1D2F1950C3")
|
||||
sigStandardCrypto, _ = hex.DecodeString("EB1DF448F44BF4BAEE4DFA9851CA56A91145E83E99C59CF911CB8E80DAF12FC3")
|
||||
sigFIPSOnly, _ = hex.DecodeString("EB1DF448F44BF4363CB9CE9D68047D31F28D325D5CA5873F5D80CAF6D6151BC3")
|
||||
)
|
||||
|
||||
func findCryptoSigs(v *Version, f exe) error {
|
||||
const maxSigLen = 1 << 10
|
||||
start, end := f.TextRange()
|
||||
for addr := start; addr < end; {
|
||||
size := uint64(1 << 20)
|
||||
if end-addr < size {
|
||||
size = end - addr
|
||||
}
|
||||
data, err := f.ReadData(addr, size)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading text: %v", err)
|
||||
}
|
||||
if haveSig(data, sigBoringCrypto) {
|
||||
v.BoringCrypto = true
|
||||
}
|
||||
if haveSig(data, sigFIPSOnly) {
|
||||
v.FIPSOnly = true
|
||||
}
|
||||
if haveSig(data, sigStandardCrypto) {
|
||||
v.StandardCrypto = true
|
||||
}
|
||||
if addr+size < end {
|
||||
size -= maxSigLen
|
||||
}
|
||||
addr += size
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func haveSig(data, sig []byte) bool {
|
||||
const align = 16
|
||||
for {
|
||||
i := bytes.Index(data, sig)
|
||||
if i < 0 {
|
||||
return false
|
||||
}
|
||||
if i&(align-1) == 0 {
|
||||
return true
|
||||
}
|
||||
// Found unaligned match; unexpected but
|
||||
// skip to next aligned boundary and keep searching.
|
||||
data = data[(i+align-1)&^(align-1):]
|
||||
}
|
||||
}
|
||||
|
||||
func findModuleInfo(v *Version, f exe) error {
|
||||
const maxModInfo = 128 << 10
|
||||
start, end := f.RODataRange()
|
||||
for addr := start; addr < end; {
|
||||
size := uint64(4 << 20)
|
||||
if end-addr < size {
|
||||
size = end - addr
|
||||
}
|
||||
data, err := f.ReadData(addr, size)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading text: %v", err)
|
||||
}
|
||||
if haveModuleInfo(data, v) {
|
||||
return nil
|
||||
}
|
||||
if addr+size < end {
|
||||
size -= maxModInfo
|
||||
}
|
||||
addr += size
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6")
|
||||
infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2")
|
||||
)
|
||||
|
||||
func haveModuleInfo(data []byte, v *Version) bool {
|
||||
i := bytes.Index(data, infoStart)
|
||||
if i < 0 {
|
||||
return false
|
||||
}
|
||||
j := bytes.Index(data[i:], infoEnd)
|
||||
if j < 0 {
|
||||
return false
|
||||
}
|
||||
v.ModuleInfo = string(data[i+len(infoStart) : i+j])
|
||||
return true
|
||||
}
|
||||
241
vendor/golang.org/x/vuln/internal/govulncheck/govulncheck.go
generated
vendored
Normal file
241
vendor/golang.org/x/vuln/internal/govulncheck/govulncheck.go
generated
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
// Package govulncheck contains the JSON output structs for govulncheck.
|
||||
//
|
||||
// govulncheck supports streaming JSON by emitting a series of Message
|
||||
// objects as it analyzes user code and discovers vulnerabilities.
|
||||
// Streaming JSON is useful for displaying progress in real-time for
|
||||
// large projects where govulncheck execution might take some time.
|
||||
//
|
||||
// govulncheck JSON emits configuration used to perform the analysis,
|
||||
// a user-friendly message about what is being analyzed, and the
|
||||
// vulnerability findings. Findings for the same vulnerability can
|
||||
// can be emitted several times. For instance, govulncheck JSON will
|
||||
// emit a finding when it sees that a vulnerable module is required
|
||||
// before proceeding to check if the vulnerability is imported or called.
|
||||
// Please see documentation on Message and related types for precise
|
||||
// details on the stream encoding.
|
||||
//
|
||||
// There are no guarantees on the order of messages. The pattern of emitted
|
||||
// messages can change in the future. Clients can follow code in handler.go
|
||||
// for consuming the streaming JSON programmatically.
|
||||
package govulncheck
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
)
|
||||
|
||||
const (
|
||||
// ProtocolVersion is the current protocol version this file implements
|
||||
ProtocolVersion = "v1.0.0"
|
||||
)
|
||||
|
||||
// Message is an entry in the output stream. It will always have exactly one
|
||||
// field filled in.
|
||||
type Message struct {
|
||||
Config *Config `json:"config,omitempty"`
|
||||
Progress *Progress `json:"progress,omitempty"`
|
||||
SBOM *SBOM `json:"SBOM,omitempty"`
|
||||
// OSV is emitted for every vulnerability in the current database
|
||||
// that applies to user modules regardless of their version. If a
|
||||
// module is being used at a vulnerable version, the corresponding
|
||||
// OSV will be referenced in Findings depending on the type of usage
|
||||
// and the desired scan level.
|
||||
OSV *osv.Entry `json:"osv,omitempty"`
|
||||
Finding *Finding `json:"finding,omitempty"`
|
||||
}
|
||||
|
||||
// Config must occur as the first message of a stream and informs the client
|
||||
// about the information used to generate the findings.
|
||||
// The only required field is the protocol version.
|
||||
type Config struct {
|
||||
// ProtocolVersion specifies the version of the JSON protocol.
|
||||
ProtocolVersion string `json:"protocol_version"`
|
||||
|
||||
// ScannerName is the name of the tool, for example, govulncheck.
|
||||
//
|
||||
// We expect this JSON format to be used by other tools that wrap
|
||||
// govulncheck, which will have a different name.
|
||||
ScannerName string `json:"scanner_name,omitempty"`
|
||||
|
||||
// ScannerVersion is the version of the tool.
|
||||
ScannerVersion string `json:"scanner_version,omitempty"`
|
||||
|
||||
// DB is the database used by the tool, for example,
|
||||
// vuln.go.dev.
|
||||
DB string `json:"db,omitempty"`
|
||||
|
||||
// LastModified is the last modified time of the data source.
|
||||
DBLastModified *time.Time `json:"db_last_modified,omitempty"`
|
||||
|
||||
// GoVersion is the version of Go used for analyzing standard library
|
||||
// vulnerabilities.
|
||||
GoVersion string `json:"go_version,omitempty"`
|
||||
|
||||
// ScanLevel instructs govulncheck to analyze at a specific level of detail.
|
||||
// Valid values include module, package and symbol.
|
||||
ScanLevel ScanLevel `json:"scan_level,omitempty"`
|
||||
|
||||
// ScanMode instructs govulncheck how to interpret the input and
|
||||
// what to do with it. Valid values are source, binary, query,
|
||||
// and extract.
|
||||
ScanMode ScanMode `json:"scan_mode,omitempty"`
|
||||
}
|
||||
|
||||
// SBOM contains minimal information about the artifacts govulncheck is scanning.
|
||||
type SBOM struct {
|
||||
// The go version used by govulncheck when scanning, which also defines
|
||||
// the version of the standard library used for detecting vulns.
|
||||
GoVersion string `json:"go_version,omitempty"`
|
||||
|
||||
// The set of modules included in the scan.
|
||||
Modules []*Module `json:"modules,omitempty"`
|
||||
|
||||
// The roots of the scan, as package paths.
|
||||
// For binaries, this will be the main package.
|
||||
// For source code, this will be the packages matching the provided package patterns.
|
||||
Roots []string `json:"roots,omitempty"`
|
||||
}
|
||||
|
||||
type Module struct {
|
||||
// The full module path.
|
||||
Path string `json:"path,omitempty"`
|
||||
|
||||
// The version of the module.
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// Progress messages are informational only, intended to allow users to monitor
|
||||
// the progress of a long running scan.
|
||||
// A stream must remain fully valid and able to be interpreted with all progress
|
||||
// messages removed.
|
||||
type Progress struct {
|
||||
// A time stamp for the message.
|
||||
Timestamp *time.Time `json:"time,omitempty"`
|
||||
|
||||
// Message is the progress message.
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// Finding contains information on a discovered vulnerability. Each vulnerability
|
||||
// will likely have multiple findings in JSON mode. This is because govulncheck
|
||||
// emits findings as it does work, and therefore could emit one module level,
|
||||
// one package level, and potentially multiple symbol level findings depending
|
||||
// on scan level.
|
||||
// Multiple symbol level findings can be emitted when multiple symbols of the
|
||||
// same vuln are called or govulncheck decides to show multiple traces for the
|
||||
// same symbol.
|
||||
type Finding struct {
|
||||
// OSV is the id of the detected vulnerability.
|
||||
OSV string `json:"osv,omitempty"`
|
||||
|
||||
// FixedVersion is the module version where the vulnerability was
|
||||
// fixed. This is empty if a fix is not available.
|
||||
//
|
||||
// If there are multiple fixed versions in the OSV report, this will
|
||||
// be the fixed version in the latest range event for the OSV report.
|
||||
//
|
||||
// For example, if the range events are
|
||||
// {introduced: 0, fixed: 1.0.0} and {introduced: 1.1.0}, the fixed version
|
||||
// will be empty.
|
||||
//
|
||||
// For the stdlib, we will show the fixed version closest to the
|
||||
// Go version that is used. For example, if a fix is available in 1.17.5 and
|
||||
// 1.18.5, and the GOVERSION is 1.17.3, 1.17.5 will be returned as the
|
||||
// fixed version.
|
||||
FixedVersion string `json:"fixed_version,omitempty"`
|
||||
|
||||
// Trace contains an entry for each frame in the trace.
|
||||
//
|
||||
// Frames are sorted starting from the imported vulnerable symbol
|
||||
// until the entry point. The first frame in Frames should match
|
||||
// Symbol.
|
||||
//
|
||||
// In binary mode, trace will contain a single-frame with no position
|
||||
// information.
|
||||
//
|
||||
// For module level source findings, the trace will contain a single-frame
|
||||
// with no symbol, position, or package information. For package level source
|
||||
// findings, the trace will contain a single-frame with no symbol or position
|
||||
// information.
|
||||
Trace []*Frame `json:"trace,omitempty"`
|
||||
}
|
||||
|
||||
// Frame represents an entry in a finding trace.
|
||||
type Frame struct {
|
||||
// Module is the module path of the module containing this symbol.
|
||||
//
|
||||
// Importable packages in the standard library will have the path "stdlib".
|
||||
Module string `json:"module"`
|
||||
|
||||
// Version is the module version from the build graph.
|
||||
Version string `json:"version,omitempty"`
|
||||
|
||||
// Package is the import path.
|
||||
Package string `json:"package,omitempty"`
|
||||
|
||||
// Function is the function name.
|
||||
Function string `json:"function,omitempty"`
|
||||
|
||||
// Receiver is the receiver type if the called symbol is a method.
|
||||
//
|
||||
// The client can create the final symbol name by
|
||||
// prepending Receiver to FuncName.
|
||||
Receiver string `json:"receiver,omitempty"`
|
||||
|
||||
// Position describes an arbitrary source position
|
||||
// including the file, line, and column location.
|
||||
// A Position is valid if the line number is > 0.
|
||||
//
|
||||
// The filenames are relative to the directory of
|
||||
// the enclosing module and always use "/" for
|
||||
// portability.
|
||||
Position *Position `json:"position,omitempty"`
|
||||
}
|
||||
|
||||
// Position represents arbitrary source position.
|
||||
type Position struct {
|
||||
Filename string `json:"filename,omitempty"` // filename, if any
|
||||
Offset int `json:"offset"` // byte offset, starting at 0
|
||||
Line int `json:"line"` // line number, starting at 1
|
||||
Column int `json:"column"` // column number, starting at 1 (byte count)
|
||||
}
|
||||
|
||||
// ScanLevel represents the detail level at which a scan occurred.
|
||||
// This can be necessary to correctly interpret the findings, for instance if
|
||||
// a scan is at symbol level and a finding does not have a symbol it means the
|
||||
// vulnerability was imported but not called. If the scan however was at
|
||||
// "package" level, that determination cannot be made.
|
||||
type ScanLevel string
|
||||
|
||||
const (
|
||||
ScanLevelModule = "module"
|
||||
ScanLevelPackage = "package"
|
||||
ScanLevelSymbol = "symbol"
|
||||
)
|
||||
|
||||
// WantSymbols can be used to check whether the scan level is one that is able
|
||||
// to generate symbol-level findings.
|
||||
func (l ScanLevel) WantSymbols() bool { return l == ScanLevelSymbol }
|
||||
|
||||
// WantPackages can be used to check whether the scan level is one that is able
|
||||
// to generate package-level findings.
|
||||
func (l ScanLevel) WantPackages() bool { return l == ScanLevelPackage || l == ScanLevelSymbol }
|
||||
|
||||
// ScanMode represents the mode in which a scan occurred. This can
|
||||
// be necessary to correctly to interpret findings. For instance,
|
||||
// a binary can be checked for vulnerabilities or the user just wants
|
||||
// to extract minimal data necessary for the vulnerability check.
|
||||
type ScanMode string
|
||||
|
||||
const (
|
||||
ScanModeSource = "source"
|
||||
ScanModeBinary = "binary"
|
||||
ScanModeConvert = "convert"
|
||||
ScanModeQuery = "query"
|
||||
ScanModeExtract = "extract" // currently, only binary extraction is supported
|
||||
)
|
||||
65
vendor/golang.org/x/vuln/internal/govulncheck/handler.go
generated
vendored
Normal file
65
vendor/golang.org/x/vuln/internal/govulncheck/handler.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
package govulncheck
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
)
|
||||
|
||||
// Handler handles messages to be presented in a vulnerability scan output
|
||||
// stream.
|
||||
type Handler interface {
|
||||
// Config communicates introductory message to the user.
|
||||
Config(config *Config) error
|
||||
|
||||
// SBOM shows information about what govulncheck is scanning.
|
||||
SBOM(sbom *SBOM) error
|
||||
|
||||
// Progress is called to display a progress message.
|
||||
Progress(progress *Progress) error
|
||||
|
||||
// OSV is invoked for each osv Entry in the stream.
|
||||
OSV(entry *osv.Entry) error
|
||||
|
||||
// Finding is called for each vulnerability finding in the stream.
|
||||
Finding(finding *Finding) error
|
||||
}
|
||||
|
||||
// HandleJSON reads the json from the supplied stream and hands the decoded
|
||||
// output to the handler.
|
||||
func HandleJSON(from io.Reader, to Handler) error {
|
||||
dec := json.NewDecoder(from)
|
||||
for dec.More() {
|
||||
msg := Message{}
|
||||
// decode the next message in the stream
|
||||
if err := dec.Decode(&msg); err != nil {
|
||||
return err
|
||||
}
|
||||
// dispatch the message
|
||||
var err error
|
||||
if msg.Config != nil {
|
||||
err = to.Config(msg.Config)
|
||||
}
|
||||
if msg.Progress != nil {
|
||||
err = to.Progress(msg.Progress)
|
||||
}
|
||||
if msg.SBOM != nil {
|
||||
err = to.SBOM(msg.SBOM)
|
||||
}
|
||||
if msg.OSV != nil {
|
||||
err = to.OSV(msg.OSV)
|
||||
}
|
||||
if msg.Finding != nil {
|
||||
err = to.Finding(msg.Finding)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
49
vendor/golang.org/x/vuln/internal/govulncheck/jsonhandler.go
generated
vendored
Normal file
49
vendor/golang.org/x/vuln/internal/govulncheck/jsonhandler.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
package govulncheck
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"io"
|
||||
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
)
|
||||
|
||||
type jsonHandler struct {
|
||||
enc *json.Encoder
|
||||
}
|
||||
|
||||
// NewJSONHandler returns a handler that writes govulncheck output as json.
|
||||
func NewJSONHandler(w io.Writer) Handler {
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
return &jsonHandler{enc: enc}
|
||||
}
|
||||
|
||||
// Config writes config block in JSON to the underlying writer.
|
||||
func (h *jsonHandler) Config(config *Config) error {
|
||||
return h.enc.Encode(Message{Config: config})
|
||||
}
|
||||
|
||||
// Progress writes a progress message in JSON to the underlying writer.
|
||||
func (h *jsonHandler) Progress(progress *Progress) error {
|
||||
return h.enc.Encode(Message{Progress: progress})
|
||||
}
|
||||
|
||||
// SBOM writes the SBOM block in JSON to the underlying writer.
|
||||
func (h *jsonHandler) SBOM(sbom *SBOM) error {
|
||||
return h.enc.Encode(Message{SBOM: sbom})
|
||||
}
|
||||
|
||||
// OSV writes an osv entry in JSON to the underlying writer.
|
||||
func (h *jsonHandler) OSV(entry *osv.Entry) error {
|
||||
return h.enc.Encode(Message{OSV: entry})
|
||||
}
|
||||
|
||||
// Finding writes a finding in JSON to the underlying writer.
|
||||
func (h *jsonHandler) Finding(finding *Finding) error {
|
||||
return h.enc.Encode(Message{Finding: finding})
|
||||
}
|
||||
32
vendor/golang.org/x/vuln/internal/internal.go
generated
vendored
Normal file
32
vendor/golang.org/x/vuln/internal/internal.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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.
|
||||
|
||||
// Package internal contains functionality for x/vuln.
|
||||
package internal
|
||||
|
||||
// IDDirectory is the name of the directory that contains entries
|
||||
// listed by their IDs.
|
||||
const IDDirectory = "ID"
|
||||
|
||||
// Pseudo-module paths used for parts of the Go system.
|
||||
// These are technically not valid module paths, so we
|
||||
// mustn't pass them to module.EscapePath.
|
||||
// Keep in sync with vulndb/internal/database/generate.go.
|
||||
const (
|
||||
// GoStdModulePath is the internal Go module path string used
|
||||
// when listing vulnerabilities in standard library.
|
||||
GoStdModulePath = "stdlib"
|
||||
|
||||
// GoCmdModulePath is the internal Go module path string used
|
||||
// when listing vulnerabilities in the go command.
|
||||
GoCmdModulePath = "toolchain"
|
||||
|
||||
// UnknownModulePath is a special module path for when we cannot work out
|
||||
// the module for a package.
|
||||
UnknownModulePath = "unknown-module"
|
||||
|
||||
// UnknownPackagePath is a special package path for when we cannot work out
|
||||
// the packagUnknownModulePath = "unknown"
|
||||
UnknownPackagePath = "unknown-package"
|
||||
)
|
||||
261
vendor/golang.org/x/vuln/internal/openvex/handler.go
generated
vendored
Normal file
261
vendor/golang.org/x/vuln/internal/openvex/handler.go
generated
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
package openvex
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
)
|
||||
|
||||
type findingLevel int
|
||||
|
||||
const (
|
||||
invalid findingLevel = iota
|
||||
required
|
||||
imported
|
||||
called
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
w io.Writer
|
||||
cfg *govulncheck.Config
|
||||
sbom *govulncheck.SBOM
|
||||
osvs map[string]*osv.Entry
|
||||
// findings contains same-level findings for an
|
||||
// OSV at the most precise level of granularity
|
||||
// available. This means, for instance, that if
|
||||
// an osv is indeed called, then all findings for
|
||||
// the osv will have call stack info.
|
||||
findings map[string][]*govulncheck.Finding
|
||||
}
|
||||
|
||||
func NewHandler(w io.Writer) *handler {
|
||||
return &handler{
|
||||
w: w,
|
||||
osvs: make(map[string]*osv.Entry),
|
||||
findings: make(map[string][]*govulncheck.Finding),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) Config(cfg *govulncheck.Config) error {
|
||||
h.cfg = cfg
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handler) Progress(progress *govulncheck.Progress) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handler) SBOM(s *govulncheck.SBOM) error {
|
||||
h.sbom = s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handler) OSV(e *osv.Entry) error {
|
||||
h.osvs[e.ID] = e
|
||||
return nil
|
||||
}
|
||||
|
||||
// foundAtLevel returns the level at which a specific finding is present in the
|
||||
// scanned product.
|
||||
func foundAtLevel(f *govulncheck.Finding) findingLevel {
|
||||
frame := f.Trace[0]
|
||||
if frame.Function != "" {
|
||||
return called
|
||||
}
|
||||
if frame.Package != "" {
|
||||
return imported
|
||||
}
|
||||
return required
|
||||
}
|
||||
|
||||
// moreSpecific favors a call finding over a non-call
|
||||
// finding and a package finding over a module finding.
|
||||
func moreSpecific(f1, f2 *govulncheck.Finding) int {
|
||||
if len(f1.Trace) > 1 && len(f2.Trace) > 1 {
|
||||
// Both are call stack findings.
|
||||
return 0
|
||||
}
|
||||
if len(f1.Trace) > 1 {
|
||||
return -1
|
||||
}
|
||||
if len(f2.Trace) > 1 {
|
||||
return 1
|
||||
}
|
||||
|
||||
fr1, fr2 := f1.Trace[0], f2.Trace[0]
|
||||
if fr1.Function != "" && fr2.Function == "" {
|
||||
return -1
|
||||
}
|
||||
if fr1.Function == "" && fr2.Function != "" {
|
||||
return 1
|
||||
}
|
||||
if fr1.Package != "" && fr2.Package == "" {
|
||||
return -1
|
||||
}
|
||||
if fr1.Package == "" && fr2.Package != "" {
|
||||
return -1
|
||||
}
|
||||
return 0 // findings always have module info
|
||||
}
|
||||
|
||||
func (h *handler) Finding(f *govulncheck.Finding) error {
|
||||
fs := h.findings[f.OSV]
|
||||
if len(fs) == 0 {
|
||||
fs = []*govulncheck.Finding{f}
|
||||
} else {
|
||||
if ms := moreSpecific(f, fs[0]); ms == -1 {
|
||||
// The new finding is more specific, so we need
|
||||
// to erase existing findings and add the new one.
|
||||
fs = []*govulncheck.Finding{f}
|
||||
} else if ms == 0 {
|
||||
// The new finding is at the same level of precision.
|
||||
fs = append(fs, f)
|
||||
}
|
||||
// Otherwise, the new finding is at a less precise level.
|
||||
}
|
||||
h.findings[f.OSV] = fs
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush is used to print the vex json to w.
|
||||
// This is needed as vex is not streamed.
|
||||
func (h *handler) Flush() error {
|
||||
doc := toVex(h)
|
||||
out, err := json.MarshalIndent(doc, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = h.w.Write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func toVex(h *handler) Document {
|
||||
doc := Document{
|
||||
Context: ContextURI,
|
||||
Author: DefaultAuthor,
|
||||
Timestamp: time.Now().UTC(),
|
||||
Version: 1,
|
||||
Tooling: Tooling,
|
||||
Statements: statements(h),
|
||||
}
|
||||
|
||||
id := hashVex(doc)
|
||||
doc.ID = "govulncheck/vex:" + id
|
||||
return doc
|
||||
}
|
||||
|
||||
// Given a slice of findings, returns those findings as a set of subcomponents
|
||||
// that are unique per the vulnerable artifact's PURL.
|
||||
func subcomponentSet(findings []*govulncheck.Finding) []Component {
|
||||
var scs []Component
|
||||
seen := make(map[string]bool)
|
||||
for _, f := range findings {
|
||||
purl := purlFromFinding(f)
|
||||
if !seen[purl] {
|
||||
scs = append(scs, Component{
|
||||
ID: purlFromFinding(f),
|
||||
})
|
||||
seen[purl] = true
|
||||
}
|
||||
}
|
||||
return scs
|
||||
}
|
||||
|
||||
// statements combines all OSVs found by govulncheck and generates the list of
|
||||
// vex statements with the proper affected level and justification to match the
|
||||
// openVex specification.
|
||||
func statements(h *handler) []Statement {
|
||||
var scanLevel findingLevel
|
||||
switch h.cfg.ScanLevel {
|
||||
case govulncheck.ScanLevelModule:
|
||||
scanLevel = required
|
||||
case govulncheck.ScanLevelPackage:
|
||||
scanLevel = imported
|
||||
case govulncheck.ScanLevelSymbol:
|
||||
scanLevel = called
|
||||
}
|
||||
|
||||
var statements []Statement
|
||||
for id, osv := range h.osvs {
|
||||
// if there are no findings emitted for a given OSV that means that
|
||||
// the vulnerable module is not required at a vulnerable version.
|
||||
if len(h.findings[id]) == 0 {
|
||||
continue
|
||||
}
|
||||
description := osv.Summary
|
||||
if description == "" {
|
||||
description = osv.Details
|
||||
}
|
||||
|
||||
s := Statement{
|
||||
Vulnerability: Vulnerability{
|
||||
ID: fmt.Sprintf("https://pkg.go.dev/vuln/%s", id),
|
||||
Name: id,
|
||||
Description: description,
|
||||
Aliases: osv.Aliases,
|
||||
},
|
||||
Products: []Product{
|
||||
{
|
||||
Component: Component{ID: DefaultPID},
|
||||
Subcomponents: subcomponentSet(h.findings[id]),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Findings are guaranteed to be at the same level, so we can just check the first element
|
||||
fLevel := foundAtLevel(h.findings[id][0])
|
||||
if fLevel >= scanLevel {
|
||||
s.Status = StatusAffected
|
||||
} else {
|
||||
s.Status = StatusNotAffected
|
||||
s.ImpactStatement = Impact
|
||||
s.Justification = JustificationNotPresent
|
||||
// We only reach this case if running in symbol mode
|
||||
if fLevel == imported {
|
||||
s.Justification = JustificationNotExecuted
|
||||
}
|
||||
}
|
||||
statements = append(statements, s)
|
||||
}
|
||||
|
||||
slices.SortFunc(statements, func(a, b Statement) int {
|
||||
if a.Vulnerability.ID > b.Vulnerability.ID {
|
||||
return 1
|
||||
}
|
||||
if a.Vulnerability.ID < b.Vulnerability.ID {
|
||||
return -1
|
||||
}
|
||||
// this should never happen in practice, since statements are being
|
||||
// populated from a map with the vulnerability IDs as keys
|
||||
return 0
|
||||
})
|
||||
return statements
|
||||
}
|
||||
|
||||
func hashVex(doc Document) string {
|
||||
// json.Marshal should never error here (because of the structure of Document).
|
||||
// If an error does occur, it won't be a jsonerror, but instead a panic
|
||||
d := Document{
|
||||
Context: doc.Context,
|
||||
ID: doc.ID,
|
||||
Author: doc.Author,
|
||||
Version: doc.Version,
|
||||
Tooling: doc.Tooling,
|
||||
Statements: doc.Statements,
|
||||
}
|
||||
out, err := json.Marshal(d)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return fmt.Sprintf("%x", sha256.Sum256(out))
|
||||
}
|
||||
46
vendor/golang.org/x/vuln/internal/openvex/purl.go
generated
vendored
Normal file
46
vendor/golang.org/x/vuln/internal/openvex/purl.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
package openvex
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
// The PURL is printed as: pkg:golang/MODULE_PATH@VERSION
|
||||
// Conceptually there is no namespace and the name is entirely defined by
|
||||
// the module path. See https://github.com/package-url/purl-spec/issues/63
|
||||
// for further disucssion.
|
||||
|
||||
const suffix = "pkg:golang/"
|
||||
|
||||
type purl struct {
|
||||
name string
|
||||
version string
|
||||
}
|
||||
|
||||
func (p *purl) String() string {
|
||||
var b strings.Builder
|
||||
b.WriteString(suffix)
|
||||
b.WriteString(url.PathEscape(p.name))
|
||||
if p.version != "" {
|
||||
b.WriteString("@")
|
||||
b.WriteString(p.version)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// purlFromFinding takes a govulncheck finding and generates a purl to the
|
||||
// vulnerable dependency.
|
||||
func purlFromFinding(f *govulncheck.Finding) string {
|
||||
purl := purl{
|
||||
name: f.Trace[0].Module,
|
||||
version: f.Trace[0].Version,
|
||||
}
|
||||
|
||||
return purl.String()
|
||||
}
|
||||
113
vendor/golang.org/x/vuln/internal/openvex/vex.go
generated
vendored
Normal file
113
vendor/golang.org/x/vuln/internal/openvex/vex.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
// Package vex defines the Vulnerability EXchange Format (VEX) types
|
||||
// supported by govulncheck.
|
||||
//
|
||||
// These types match the OpenVEX standard. See https://github.com/openvex for
|
||||
// more information on VEX and OpenVEX.
|
||||
//
|
||||
// This is intended to be the minimimal amount of information required to output
|
||||
// a complete VEX document according to the specification.
|
||||
package openvex
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
ContextURI = "https://openvex.dev/ns/v0.2.0"
|
||||
Tooling = "https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck"
|
||||
Impact = "Govulncheck determined that the vulnerable code isn't called"
|
||||
|
||||
DefaultAuthor = "Unknown Author"
|
||||
DefaultPID = "Unknown Product"
|
||||
|
||||
// The following are defined by the VEX standard.
|
||||
StatusAffected = "affected"
|
||||
StatusNotAffected = "not_affected"
|
||||
|
||||
// The following are defined by the VEX standard.
|
||||
JustificationNotExecuted = "vulnerable_code_not_in_execute_path"
|
||||
JustificationNotPresent = "vulnerable_code_not_present"
|
||||
)
|
||||
|
||||
// Document is the top-level struct for a VEX document.
|
||||
type Document struct {
|
||||
// Context is an IRI pointing to the version of openVEX being used by the doc
|
||||
// For govulncheck, it will always be https://openvex.dev/ns/v0.2.0
|
||||
Context string `json:"@context,omitempty"`
|
||||
|
||||
// ID is the identifying string for the VEX document.
|
||||
// govulncheck/vex-[content-based-hash]
|
||||
ID string `json:"@id,omitempty"`
|
||||
|
||||
// Author is the identifier for the author of the VEX statement.
|
||||
// Govulncheck will leave this field default (Unknown author) to be filled in by the user.
|
||||
Author string `json:"author,omitempty"`
|
||||
|
||||
// Timestamp defines the time at which the document was issued.
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
|
||||
// Version is the document version. For govulncheck's output, this will always be 1.
|
||||
Version int `json:"version,omitempty"`
|
||||
|
||||
// Tooling expresses how the VEX document and contained VEX statements were
|
||||
// generated. In this case, it will always be:
|
||||
// "https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck"
|
||||
Tooling string `json:"tooling,omitempty"`
|
||||
|
||||
// Statements are all statements for a given govulncheck output.
|
||||
// Each OSV emitted by govulncheck will have a corresponding statement.
|
||||
Statements []Statement `json:"statements,omitempty"`
|
||||
}
|
||||
|
||||
// Statement conveys a single status for a single vulnerability for one or more products.
|
||||
type Statement struct {
|
||||
// Vulnerability is the vuln being referenced by the statement.
|
||||
Vulnerability Vulnerability `json:"vulnerability,omitempty"`
|
||||
|
||||
// Products are the products associated with the given vulnerability in the statement.
|
||||
Products []Product `json:"products,omitempty"`
|
||||
|
||||
// The status of the vulnerability. Will be either not_affected or affected for govulncheck.
|
||||
Status string `json:"status,omitempty"`
|
||||
|
||||
// If the status is not_affected, this must be filled. The official VEX justification that
|
||||
// best matches govulncheck's vuln filtering is "vulnerable_code_not_in_execute_path"
|
||||
Justification string `json:"justification,omitempty"`
|
||||
|
||||
// If the status is not_affected, this must be filled. For govulncheck, this will always be:
|
||||
// "Govulncheck determined that the vulnerable code isn't called"
|
||||
ImpactStatement string `json:"impact_statement,omitempty"`
|
||||
}
|
||||
|
||||
// Vulnerability captures a vulnerability and its identifiers/aliases.
|
||||
type Vulnerability struct {
|
||||
// ID is a URI that in govulncheck's case points to the govulndb link for the vulnerability.
|
||||
// I.E. https://pkg.go.dev/vuln/GO-2024-2497
|
||||
ID string `json:"@id,omitempty"`
|
||||
|
||||
// Name is the main identifier for the vulnerability (GO-YYYY-XXXX)
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// Description is a short text description of the vulnerability.
|
||||
// It will be populated from the 'summary' field of the vuln's OSV if it exists,
|
||||
// and the 'description' field of the osv if a summary isn't present.
|
||||
Description string `json:"description,omitempty"`
|
||||
|
||||
// Aliases a list of identifiers that other systems are using to track the vulnerability.
|
||||
// I.E. GHSA or CVE ids.
|
||||
Aliases []string `json:"aliases,omitempty"`
|
||||
}
|
||||
|
||||
// Product identifies the products associated with the given vuln.
|
||||
type Product struct {
|
||||
// The main product ID will remian default for now.
|
||||
Component
|
||||
// The subcomponent ID will be a PURL to the vulnerable dependency.
|
||||
Subcomponents []Component `json:"subcomponents,omitempty"`
|
||||
}
|
||||
|
||||
type Component struct {
|
||||
ID string `json:"@id,omitempty"`
|
||||
}
|
||||
240
vendor/golang.org/x/vuln/internal/osv/osv.go
generated
vendored
Normal file
240
vendor/golang.org/x/vuln/internal/osv/osv.go
generated
vendored
Normal file
@@ -0,0 +1,240 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
// Package osv implements the Go OSV vulnerability format
|
||||
// (https://go.dev/security/vuln/database#schema), which is a subset of
|
||||
// the OSV shared vulnerability format
|
||||
// (https://ossf.github.io/osv-schema), with database and
|
||||
// ecosystem-specific meanings and fields.
|
||||
//
|
||||
// As this package is intended for use with the Go vulnerability
|
||||
// database, only the subset of features which are used by that
|
||||
// database are implemented (for instance, only the SEMVER affected
|
||||
// range type is implemented).
|
||||
package osv
|
||||
|
||||
import "time"
|
||||
|
||||
// RangeType specifies the type of version range being recorded and
|
||||
// defines the interpretation of the RangeEvent object's Introduced
|
||||
// and Fixed fields.
|
||||
//
|
||||
// In this implementation, only the "SEMVER" type is supported.
|
||||
//
|
||||
// See https://ossf.github.io/osv-schema/#affectedrangestype-field.
|
||||
type RangeType string
|
||||
|
||||
// RangeTypeSemver indicates a semantic version as defined by
|
||||
// SemVer 2.0.0, with no leading "v" prefix.
|
||||
const RangeTypeSemver RangeType = "SEMVER"
|
||||
|
||||
// Ecosystem identifies the overall library ecosystem.
|
||||
// In this implementation, only the "Go" ecosystem is supported.
|
||||
type Ecosystem string
|
||||
|
||||
// GoEcosystem indicates the Go ecosystem.
|
||||
const GoEcosystem Ecosystem = "Go"
|
||||
|
||||
// Pseudo-module paths used to describe vulnerabilities
|
||||
// in the Go standard library and toolchain.
|
||||
const (
|
||||
// GoStdModulePath is the pseudo-module path string used
|
||||
// to describe vulnerabilities in the Go standard library.
|
||||
GoStdModulePath = "stdlib"
|
||||
// GoCmdModulePath is the pseudo-module path string used
|
||||
// to describe vulnerabilities in the go command.
|
||||
GoCmdModulePath = "toolchain"
|
||||
)
|
||||
|
||||
// Module identifies the Go module containing the vulnerability.
|
||||
// Note that this field is called "package" in the OSV specification.
|
||||
//
|
||||
// See https://ossf.github.io/osv-schema/#affectedpackage-field.
|
||||
type Module struct {
|
||||
// The Go module path. Required.
|
||||
// For the Go standard library, this is "stdlib".
|
||||
// For the Go toolchain, this is "toolchain."
|
||||
Path string `json:"name"`
|
||||
// The ecosystem containing the module. Required.
|
||||
// This should always be "Go".
|
||||
Ecosystem Ecosystem `json:"ecosystem"`
|
||||
}
|
||||
|
||||
// RangeEvent describes a single module version that either
|
||||
// introduces or fixes a vulnerability.
|
||||
//
|
||||
// Exactly one of Introduced and Fixed must be present. Other range
|
||||
// event types (e.g, "last_affected" and "limit") are not supported in
|
||||
// this implementation.
|
||||
//
|
||||
// See https://ossf.github.io/osv-schema/#affectedrangesevents-fields.
|
||||
type RangeEvent struct {
|
||||
// Introduced is a version that introduces the vulnerability.
|
||||
// A special value, "0", represents a version that sorts before
|
||||
// any other version, and should be used to indicate that the
|
||||
// vulnerability exists from the "beginning of time".
|
||||
Introduced string `json:"introduced,omitempty"`
|
||||
// Fixed is a version that fixes the vulnerability.
|
||||
Fixed string `json:"fixed,omitempty"`
|
||||
}
|
||||
|
||||
// Range describes the affected versions of the vulnerable module.
|
||||
//
|
||||
// See https://ossf.github.io/osv-schema/#affectedranges-field.
|
||||
type Range struct {
|
||||
// Type is the version type that should be used to interpret the
|
||||
// versions in Events. Required.
|
||||
// In this implementation, only the "SEMVER" type is supported.
|
||||
Type RangeType `json:"type"`
|
||||
// Events is a list of versions representing the ranges in which
|
||||
// the module is vulnerable. Required.
|
||||
// The events should be sorted, and MUST represent non-overlapping
|
||||
// ranges.
|
||||
// There must be at least one RangeEvent containing a value for
|
||||
// Introduced.
|
||||
// See https://ossf.github.io/osv-schema/#examples for examples.
|
||||
Events []RangeEvent `json:"events"`
|
||||
}
|
||||
|
||||
// ReferenceType is a reference (link) type.
|
||||
type ReferenceType string
|
||||
|
||||
const (
|
||||
// ReferenceTypeAdvisory is a published security advisory for
|
||||
// the vulnerability.
|
||||
ReferenceTypeAdvisory = ReferenceType("ADVISORY")
|
||||
// ReferenceTypeArticle is an article or blog post describing the vulnerability.
|
||||
ReferenceTypeArticle = ReferenceType("ARTICLE")
|
||||
// ReferenceTypeReport is a report, typically on a bug or issue tracker, of
|
||||
// the vulnerability.
|
||||
ReferenceTypeReport = ReferenceType("REPORT")
|
||||
// ReferenceTypeFix is a source code browser link to the fix (e.g., a GitHub commit).
|
||||
ReferenceTypeFix = ReferenceType("FIX")
|
||||
// ReferenceTypePackage is a home web page for the package.
|
||||
ReferenceTypePackage = ReferenceType("PACKAGE")
|
||||
// ReferenceTypeEvidence is a demonstration of the validity of a vulnerability claim.
|
||||
ReferenceTypeEvidence = ReferenceType("EVIDENCE")
|
||||
// ReferenceTypeWeb is a web page of some unspecified kind.
|
||||
ReferenceTypeWeb = ReferenceType("WEB")
|
||||
)
|
||||
|
||||
// Reference is a reference URL containing additional information,
|
||||
// advisories, issue tracker entries, etc., about the vulnerability.
|
||||
//
|
||||
// See https://ossf.github.io/osv-schema/#references-field.
|
||||
type Reference struct {
|
||||
// The type of reference. Required.
|
||||
Type ReferenceType `json:"type"`
|
||||
// The fully-qualified URL of the reference. Required.
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// Affected gives details about a module affected by the vulnerability.
|
||||
//
|
||||
// See https://ossf.github.io/osv-schema/#affected-fields.
|
||||
type Affected struct {
|
||||
// The affected Go module. Required.
|
||||
// Note that this field is called "package" in the OSV specification.
|
||||
Module Module `json:"package"`
|
||||
// The module version ranges affected by the vulnerability.
|
||||
Ranges []Range `json:"ranges,omitempty"`
|
||||
// Details on the affected packages and symbols within the module.
|
||||
EcosystemSpecific EcosystemSpecific `json:"ecosystem_specific"`
|
||||
}
|
||||
|
||||
// Package contains additional information about an affected package.
|
||||
// This is an ecosystem-specific field for the Go ecosystem.
|
||||
type Package struct {
|
||||
// Path is the package import path. Required.
|
||||
Path string `json:"path,omitempty"`
|
||||
// GOOS is the execution operating system where the symbols appear, if
|
||||
// known.
|
||||
GOOS []string `json:"goos,omitempty"`
|
||||
// GOARCH specifies the execution architecture where the symbols appear, if
|
||||
// known.
|
||||
GOARCH []string `json:"goarch,omitempty"`
|
||||
// Symbols is a list of function and method names affected by
|
||||
// this vulnerability. Methods are listed as <recv>.<method>.
|
||||
//
|
||||
// If included, only programs which use these symbols will be marked as
|
||||
// vulnerable by `govulncheck`. If omitted, any program which imports this
|
||||
// package will be marked vulnerable.
|
||||
Symbols []string `json:"symbols,omitempty"`
|
||||
}
|
||||
|
||||
// EcosystemSpecific contains additional information about the vulnerable
|
||||
// module for the Go ecosystem.
|
||||
//
|
||||
// See https://go.dev/security/vuln/database#schema.
|
||||
type EcosystemSpecific struct {
|
||||
// Packages is the list of affected packages within the module.
|
||||
Packages []Package `json:"imports,omitempty"`
|
||||
}
|
||||
|
||||
// Entry represents a vulnerability in the Go OSV format, documented
|
||||
// in https://go.dev/security/vuln/database#schema.
|
||||
// It is a subset of the OSV schema (https://ossf.github.io/osv-schema).
|
||||
// Only fields that are published in the Go Vulnerability Database
|
||||
// are supported.
|
||||
type Entry struct {
|
||||
// SchemaVersion is the OSV schema version used to encode this
|
||||
// vulnerability.
|
||||
SchemaVersion string `json:"schema_version,omitempty"`
|
||||
// ID is a unique identifier for the vulnerability. Required.
|
||||
// The Go vulnerability database issues IDs of the form
|
||||
// GO-<YEAR>-<ENTRYID>.
|
||||
ID string `json:"id"`
|
||||
// Modified is the time the entry was last modified. Required.
|
||||
Modified time.Time `json:"modified,omitempty"`
|
||||
// Published is the time the entry should be considered to have
|
||||
// been published.
|
||||
Published time.Time `json:"published,omitempty"`
|
||||
// Withdrawn is the time the entry should be considered to have
|
||||
// been withdrawn. If the field is missing, then the entry has
|
||||
// not been withdrawn.
|
||||
Withdrawn *time.Time `json:"withdrawn,omitempty"`
|
||||
// Aliases is a list of IDs for the same vulnerability in other
|
||||
// databases.
|
||||
Aliases []string `json:"aliases,omitempty"`
|
||||
// Summary gives a one-line, English textual summary of the vulnerability.
|
||||
// It is recommended that this field be kept short, on the order of no more
|
||||
// than 120 characters.
|
||||
Summary string `json:"summary,omitempty"`
|
||||
// Details contains additional English textual details about the vulnerability.
|
||||
Details string `json:"details"`
|
||||
// Affected contains information on the modules and versions
|
||||
// affected by the vulnerability.
|
||||
Affected []Affected `json:"affected"`
|
||||
// References contains links to more information about the
|
||||
// vulnerability.
|
||||
References []Reference `json:"references,omitempty"`
|
||||
// Credits contains credits to entities that helped find or fix the
|
||||
// vulnerability.
|
||||
Credits []Credit `json:"credits,omitempty"`
|
||||
// DatabaseSpecific contains additional information about the
|
||||
// vulnerability, specific to the Go vulnerability database.
|
||||
DatabaseSpecific *DatabaseSpecific `json:"database_specific,omitempty"`
|
||||
}
|
||||
|
||||
// Credit represents a credit for the discovery, confirmation, patch, or
|
||||
// other event in the life cycle of a vulnerability.
|
||||
//
|
||||
// See https://ossf.github.io/osv-schema/#credits-fields.
|
||||
type Credit struct {
|
||||
// Name is the name, label, or other identifier of the individual or
|
||||
// entity being credited. Required.
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// DatabaseSpecific contains additional information about the
|
||||
// vulnerability, specific to the Go vulnerability database.
|
||||
//
|
||||
// See https://go.dev/security/vuln/database#schema.
|
||||
type DatabaseSpecific struct {
|
||||
// The URL of the Go advisory for this vulnerability, of the form
|
||||
// "https://pkg.go.dev/GO-YYYY-XXXX".
|
||||
URL string `json:"url,omitempty"`
|
||||
// The review status of this report (UNREVIEWED or REVIEWED).
|
||||
ReviewStatus ReviewStatus `json:"review_status,omitempty"`
|
||||
}
|
||||
67
vendor/golang.org/x/vuln/internal/osv/review_status.go
generated
vendored
Normal file
67
vendor/golang.org/x/vuln/internal/osv/review_status.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
package osv
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ReviewStatus int
|
||||
|
||||
const (
|
||||
ReviewStatusUnknown ReviewStatus = iota
|
||||
ReviewStatusUnreviewed
|
||||
ReviewStatusReviewed
|
||||
)
|
||||
|
||||
var statusStrs = []string{
|
||||
ReviewStatusUnknown: "",
|
||||
ReviewStatusUnreviewed: "UNREVIEWED",
|
||||
ReviewStatusReviewed: "REVIEWED",
|
||||
}
|
||||
|
||||
func (r ReviewStatus) String() string {
|
||||
if !r.IsValid() {
|
||||
return fmt.Sprintf("INVALID(%d)", r)
|
||||
}
|
||||
return statusStrs[r]
|
||||
}
|
||||
|
||||
func ReviewStatusValues() []string {
|
||||
return statusStrs[1:]
|
||||
}
|
||||
|
||||
func (r ReviewStatus) IsValid() bool {
|
||||
return int(r) >= 0 && int(r) < len(statusStrs)
|
||||
}
|
||||
|
||||
func ToReviewStatus(s string) (ReviewStatus, bool) {
|
||||
for stat, str := range statusStrs {
|
||||
if s == str {
|
||||
return ReviewStatus(stat), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (r ReviewStatus) MarshalJSON() ([]byte, error) {
|
||||
if !r.IsValid() {
|
||||
return nil, fmt.Errorf("MarshalJSON: unrecognized review status: %d", r)
|
||||
}
|
||||
return json.Marshal(r.String())
|
||||
}
|
||||
|
||||
func (r *ReviewStatus) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if rs, ok := ToReviewStatus(s); ok {
|
||||
*r = rs
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("UnmarshalJSON: unrecognized review status: %s", s)
|
||||
}
|
||||
409
vendor/golang.org/x/vuln/internal/sarif/handler.go
generated
vendored
Normal file
409
vendor/golang.org/x/vuln/internal/sarif/handler.go
generated
vendored
Normal file
@@ -0,0 +1,409 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
package sarif
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/vuln/internal"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
"golang.org/x/vuln/internal/traces"
|
||||
)
|
||||
|
||||
// handler for sarif output.
|
||||
type handler struct {
|
||||
w io.Writer
|
||||
cfg *govulncheck.Config
|
||||
osvs map[string]*osv.Entry
|
||||
// findings contains same-level findings for an
|
||||
// OSV at the most precise level of granularity
|
||||
// available. This means, for instance, that if
|
||||
// an osv is indeed called, then all findings for
|
||||
// the osv will have call stack info.
|
||||
findings map[string][]*govulncheck.Finding
|
||||
}
|
||||
|
||||
func NewHandler(w io.Writer) *handler {
|
||||
return &handler{
|
||||
w: w,
|
||||
osvs: make(map[string]*osv.Entry),
|
||||
findings: make(map[string][]*govulncheck.Finding),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) Config(c *govulncheck.Config) error {
|
||||
h.cfg = c
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handler) Progress(p *govulncheck.Progress) error {
|
||||
return nil // not needed by sarif
|
||||
}
|
||||
|
||||
func (h *handler) SBOM(s *govulncheck.SBOM) error {
|
||||
return nil // not needed by sarif
|
||||
}
|
||||
|
||||
func (h *handler) OSV(e *osv.Entry) error {
|
||||
h.osvs[e.ID] = e
|
||||
return nil
|
||||
}
|
||||
|
||||
// moreSpecific favors a call finding over a non-call
|
||||
// finding and a package finding over a module finding.
|
||||
func moreSpecific(f1, f2 *govulncheck.Finding) int {
|
||||
if len(f1.Trace) > 1 && len(f2.Trace) > 1 {
|
||||
// Both are call stack findings.
|
||||
return 0
|
||||
}
|
||||
if len(f1.Trace) > 1 {
|
||||
return -1
|
||||
}
|
||||
if len(f2.Trace) > 1 {
|
||||
return 1
|
||||
}
|
||||
|
||||
fr1, fr2 := f1.Trace[0], f2.Trace[0]
|
||||
if fr1.Function != "" && fr2.Function == "" {
|
||||
return -1
|
||||
}
|
||||
if fr1.Function == "" && fr2.Function != "" {
|
||||
return 1
|
||||
}
|
||||
if fr1.Package != "" && fr2.Package == "" {
|
||||
return -1
|
||||
}
|
||||
if fr1.Package == "" && fr2.Package != "" {
|
||||
return -1
|
||||
}
|
||||
return 0 // findings always have module info
|
||||
}
|
||||
|
||||
func (h *handler) Finding(f *govulncheck.Finding) error {
|
||||
fs := h.findings[f.OSV]
|
||||
if len(fs) == 0 {
|
||||
fs = []*govulncheck.Finding{f}
|
||||
} else {
|
||||
if ms := moreSpecific(f, fs[0]); ms == -1 {
|
||||
// The new finding is more specific, so we need
|
||||
// to erase existing findings and add the new one.
|
||||
fs = []*govulncheck.Finding{f}
|
||||
} else if ms == 0 {
|
||||
// The new finding is equal to an existing one and
|
||||
// because of the invariant on h.findings, it is
|
||||
// also equal to all existing ones.
|
||||
fs = append(fs, f)
|
||||
}
|
||||
// Otherwise, the new finding is at a less precise level.
|
||||
}
|
||||
h.findings[f.OSV] = fs
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush is used to print out to w the sarif json output.
|
||||
// This is needed as sarif is not streamed.
|
||||
func (h *handler) Flush() error {
|
||||
sLog := toSarif(h)
|
||||
s, err := json.MarshalIndent(sLog, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.w.Write(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func toSarif(h *handler) Log {
|
||||
cfg := h.cfg
|
||||
r := Run{
|
||||
Tool: Tool{
|
||||
Driver: Driver{
|
||||
Name: cfg.ScannerName,
|
||||
Version: cfg.ScannerVersion,
|
||||
InformationURI: "https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck",
|
||||
Properties: *cfg,
|
||||
Rules: rules(h),
|
||||
},
|
||||
},
|
||||
Results: results(h),
|
||||
}
|
||||
|
||||
return Log{
|
||||
Version: "2.1.0",
|
||||
Schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
||||
Runs: []Run{r},
|
||||
}
|
||||
}
|
||||
|
||||
func rules(h *handler) []Rule {
|
||||
rs := make([]Rule, 0, len(h.findings)) // must not be nil
|
||||
for id := range h.findings {
|
||||
osv := h.osvs[id]
|
||||
// s is either summary if it exists, or details
|
||||
// otherwise. Govulncheck text does the same.
|
||||
s := osv.Summary
|
||||
if s == "" {
|
||||
s = osv.Details
|
||||
}
|
||||
rs = append(rs, Rule{
|
||||
ID: osv.ID,
|
||||
ShortDescription: Description{Text: fmt.Sprintf("[%s] %s", osv.ID, s)},
|
||||
FullDescription: Description{Text: s},
|
||||
HelpURI: fmt.Sprintf("https://pkg.go.dev/vuln/%s", osv.ID),
|
||||
Help: Description{Text: osv.Details},
|
||||
Properties: RuleTags{Tags: tags(osv)},
|
||||
})
|
||||
}
|
||||
sort.SliceStable(rs, func(i, j int) bool { return rs[i].ID < rs[j].ID })
|
||||
return rs
|
||||
}
|
||||
|
||||
// tags returns an slice of zero or
|
||||
// more aliases of o.
|
||||
func tags(o *osv.Entry) []string {
|
||||
if len(o.Aliases) > 0 {
|
||||
return o.Aliases
|
||||
}
|
||||
return []string{} // must not be nil
|
||||
}
|
||||
|
||||
func results(h *handler) []Result {
|
||||
results := make([]Result, 0, len(h.findings)) // must not be nil
|
||||
for osv, fs := range h.findings {
|
||||
var locs []Location
|
||||
if h.cfg.ScanMode != govulncheck.ScanModeBinary {
|
||||
// Attach result to the go.mod file for source analysis.
|
||||
// But there is no such place for binaries.
|
||||
locs = []Location{{PhysicalLocation: PhysicalLocation{
|
||||
ArtifactLocation: ArtifactLocation{
|
||||
URI: "go.mod",
|
||||
URIBaseID: SrcRootID,
|
||||
},
|
||||
Region: Region{StartLine: 1}, // for now, point to the first line
|
||||
},
|
||||
Message: Description{Text: fmt.Sprintf("Findings for vulnerability %s", osv)}, // not having a message here results in an invalid sarif
|
||||
}}
|
||||
}
|
||||
|
||||
res := Result{
|
||||
RuleID: osv,
|
||||
Level: level(fs[0], h.cfg),
|
||||
Message: Description{Text: resultMessage(fs, h.cfg)},
|
||||
Stacks: stacks(h, fs),
|
||||
CodeFlows: codeFlows(h, fs),
|
||||
Locations: locs,
|
||||
}
|
||||
results = append(results, res)
|
||||
}
|
||||
sort.SliceStable(results, func(i, j int) bool { return results[i].RuleID < results[j].RuleID }) // for deterministic output
|
||||
return results
|
||||
}
|
||||
|
||||
func resultMessage(findings []*govulncheck.Finding, cfg *govulncheck.Config) string {
|
||||
// We can infer the findings' level by just looking at the
|
||||
// top trace frame of any finding.
|
||||
frame := findings[0].Trace[0]
|
||||
uniqueElems := make(map[string]bool)
|
||||
if frame.Function == "" && frame.Package == "" { // module level findings
|
||||
for _, f := range findings {
|
||||
uniqueElems[f.Trace[0].Module] = true
|
||||
}
|
||||
} else { // symbol and package level findings
|
||||
for _, f := range findings {
|
||||
uniqueElems[f.Trace[0].Package] = true
|
||||
}
|
||||
}
|
||||
var elems []string
|
||||
for e := range uniqueElems {
|
||||
elems = append(elems, e)
|
||||
}
|
||||
sort.Strings(elems)
|
||||
|
||||
l := len(elems)
|
||||
elemList := list(elems)
|
||||
main, addition := "", ""
|
||||
const runCallAnalysis = "Run the call-level analysis to understand whether your code actually calls the vulnerabilities."
|
||||
switch {
|
||||
case frame.Function != "":
|
||||
main = fmt.Sprintf("calls vulnerable functions in %d package%s (%s).", l, choose("", "s", l == 1), elemList)
|
||||
case frame.Package != "":
|
||||
main = fmt.Sprintf("imports %d vulnerable package%s (%s)", l, choose("", "s", l == 1), elemList)
|
||||
addition = choose(", but doesn’t appear to call any of the vulnerable symbols.", ". "+runCallAnalysis, cfg.ScanLevel.WantSymbols())
|
||||
default:
|
||||
main = fmt.Sprintf("depends on %d vulnerable module%s (%s)", l, choose("", "s", l == 1), elemList)
|
||||
informational := ", but doesn't appear to " + choose("call", "import", cfg.ScanLevel.WantSymbols()) + " any of the vulnerable symbols."
|
||||
addition = choose(informational, ". "+runCallAnalysis, cfg.ScanLevel.WantPackages())
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Your code %s%s", main, addition)
|
||||
}
|
||||
|
||||
const (
|
||||
errorLevel = "error"
|
||||
warningLevel = "warning"
|
||||
informationalLevel = "note"
|
||||
)
|
||||
|
||||
func level(f *govulncheck.Finding, cfg *govulncheck.Config) string {
|
||||
fr := f.Trace[0]
|
||||
switch {
|
||||
case cfg.ScanLevel.WantSymbols():
|
||||
if fr.Function != "" {
|
||||
return errorLevel
|
||||
}
|
||||
if fr.Package != "" {
|
||||
return warningLevel
|
||||
}
|
||||
return informationalLevel
|
||||
case cfg.ScanLevel.WantPackages():
|
||||
if fr.Package != "" {
|
||||
return errorLevel
|
||||
}
|
||||
return warningLevel
|
||||
default:
|
||||
return errorLevel
|
||||
}
|
||||
}
|
||||
|
||||
func stacks(h *handler, fs []*govulncheck.Finding) []Stack {
|
||||
if fs[0].Trace[0].Function == "" { // not call level findings
|
||||
return nil
|
||||
}
|
||||
|
||||
var stacks []Stack
|
||||
for _, f := range fs {
|
||||
stacks = append(stacks, stack(h, f))
|
||||
}
|
||||
// Sort stacks for deterministic output. We sort by message
|
||||
// which is effectively sorting by full symbol name. The
|
||||
// performance should not be an issue here.
|
||||
sort.SliceStable(stacks, func(i, j int) bool { return stacks[i].Message.Text < stacks[j].Message.Text })
|
||||
return stacks
|
||||
}
|
||||
|
||||
// stack transforms call stack in f to a sarif stack.
|
||||
func stack(h *handler, f *govulncheck.Finding) Stack {
|
||||
trace := f.Trace
|
||||
top := trace[len(trace)-1] // belongs to top level module
|
||||
|
||||
frames := make([]Frame, 0, len(trace)) // must not be nil
|
||||
for i := len(trace) - 1; i >= 0; i-- { // vulnerable symbol is at the top frame
|
||||
frame := trace[i]
|
||||
pos := govulncheck.Position{Line: 1, Column: 1}
|
||||
if frame.Position != nil {
|
||||
pos = *frame.Position
|
||||
}
|
||||
|
||||
sf := Frame{
|
||||
Module: frame.Module + "@" + frame.Version,
|
||||
Location: Location{Message: Description{Text: symbol(frame)}}, // show the (full) symbol name
|
||||
}
|
||||
file, base := fileURIInfo(pos.Filename, top.Module, frame.Module, frame.Version)
|
||||
if h.cfg.ScanMode != govulncheck.ScanModeBinary {
|
||||
sf.Location.PhysicalLocation = PhysicalLocation{
|
||||
ArtifactLocation: ArtifactLocation{
|
||||
URI: file,
|
||||
URIBaseID: base,
|
||||
},
|
||||
Region: Region{
|
||||
StartLine: pos.Line,
|
||||
StartColumn: pos.Column,
|
||||
},
|
||||
}
|
||||
}
|
||||
frames = append(frames, sf)
|
||||
}
|
||||
|
||||
return Stack{
|
||||
Frames: frames,
|
||||
Message: Description{Text: fmt.Sprintf("A call stack for vulnerable function %s", symbol(trace[0]))},
|
||||
}
|
||||
}
|
||||
|
||||
func codeFlows(h *handler, fs []*govulncheck.Finding) []CodeFlow {
|
||||
if fs[0].Trace[0].Function == "" { // not call level findings
|
||||
return nil
|
||||
}
|
||||
|
||||
// group call stacks per symbol. There should
|
||||
// be one call stack currently per symbol, but
|
||||
// this might change in the future.
|
||||
m := make(map[govulncheck.Frame][]*govulncheck.Finding)
|
||||
for _, f := range fs {
|
||||
// fr.Position is currently the position
|
||||
// of the definition of the vuln symbol
|
||||
fr := *f.Trace[0]
|
||||
m[fr] = append(m[fr], f)
|
||||
}
|
||||
|
||||
var codeFlows []CodeFlow
|
||||
for fr, fs := range m {
|
||||
tfs := threadFlows(h, fs)
|
||||
codeFlows = append(codeFlows, CodeFlow{
|
||||
ThreadFlows: tfs,
|
||||
// TODO: should we instead show the message from govulncheck text output?
|
||||
Message: Description{Text: fmt.Sprintf("A summarized code flow for vulnerable function %s", symbol(&fr))},
|
||||
})
|
||||
}
|
||||
// Sort flows for deterministic output. We sort by message
|
||||
// which is effectively sorting by full symbol name. The
|
||||
// performance should not be an issue here.
|
||||
sort.SliceStable(codeFlows, func(i, j int) bool { return codeFlows[i].Message.Text < codeFlows[j].Message.Text })
|
||||
return codeFlows
|
||||
}
|
||||
|
||||
func threadFlows(h *handler, fs []*govulncheck.Finding) []ThreadFlow {
|
||||
tfs := make([]ThreadFlow, 0, len(fs)) // must not be nil
|
||||
for _, f := range fs {
|
||||
trace := traces.Compact(f)
|
||||
top := trace[len(trace)-1] // belongs to top level module
|
||||
|
||||
var tf []ThreadFlowLocation
|
||||
for i := len(trace) - 1; i >= 0; i-- { // vulnerable symbol is at the top frame
|
||||
// TODO: should we, similar to govulncheck text output, only
|
||||
// mention three elements of the compact trace?
|
||||
frame := trace[i]
|
||||
pos := govulncheck.Position{Line: 1, Column: 1}
|
||||
if frame.Position != nil {
|
||||
pos = *frame.Position
|
||||
}
|
||||
|
||||
tfl := ThreadFlowLocation{
|
||||
Module: frame.Module + "@" + frame.Version,
|
||||
Location: Location{Message: Description{Text: symbol(frame)}}, // show the (full) symbol name
|
||||
}
|
||||
file, base := fileURIInfo(pos.Filename, top.Module, frame.Module, frame.Version)
|
||||
if h.cfg.ScanMode != govulncheck.ScanModeBinary {
|
||||
tfl.Location.PhysicalLocation = PhysicalLocation{
|
||||
ArtifactLocation: ArtifactLocation{
|
||||
URI: file,
|
||||
URIBaseID: base,
|
||||
},
|
||||
Region: Region{
|
||||
StartLine: pos.Line,
|
||||
StartColumn: pos.Column,
|
||||
},
|
||||
}
|
||||
}
|
||||
tf = append(tf, tfl)
|
||||
}
|
||||
tfs = append(tfs, ThreadFlow{Locations: tf})
|
||||
}
|
||||
return tfs
|
||||
}
|
||||
|
||||
func fileURIInfo(filename, top, module, version string) (string, string) {
|
||||
if top == module {
|
||||
return filename, SrcRootID
|
||||
}
|
||||
if module == internal.GoStdModulePath {
|
||||
return filename, GoRootID
|
||||
}
|
||||
return filepath.ToSlash(filepath.Join(module+"@"+version, filename)), GoModCacheID
|
||||
}
|
||||
213
vendor/golang.org/x/vuln/internal/sarif/sarif.go
generated
vendored
Normal file
213
vendor/golang.org/x/vuln/internal/sarif/sarif.go
generated
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
// Package sarif defines Static Analysis Results Interchange Format
|
||||
// (SARIF) types supported by govulncheck.
|
||||
//
|
||||
// The implementation covers the subset of the specification available
|
||||
// at https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=sarif.
|
||||
//
|
||||
// The sarif encoding models govulncheck findings as Results. Each
|
||||
// Result encodes findings for a unique OSV entry at the most precise
|
||||
// detected level only. CodeFlows summarize call stacks, similar to govulncheck
|
||||
// textual output, while Stacks contain call stack information verbatim.
|
||||
//
|
||||
// The result Levels are defined by the govulncheck.ScanLevel and the most
|
||||
// precise level at which the finding was detected. Result error is produced
|
||||
// when the finding level matches the user desired level of scan precision;
|
||||
// all other finding levels are then classified as progressively weaker.
|
||||
// For instance, if the user specified symbol scan level and govulncheck
|
||||
// detected a use of a vulnerable symbol, then the Result will have error
|
||||
// Level. If the symbol was not used but its package was imported, then the
|
||||
// Result Level is warning, and so on.
|
||||
//
|
||||
// Each Result is attached to the first line of the go.mod file. Other
|
||||
// ArtifactLocations are paths relative to their enclosing modules.
|
||||
// Similar to JSON output format, this makes govulncheck sarif locations
|
||||
// portable.
|
||||
//
|
||||
// The relative paths in PhysicalLocations also come with a URIBaseID offset.
|
||||
// Paths for the source module analyzed, the Go standard library, and third-party
|
||||
// dependencies are relative to %SRCROOT%, %GOROOT%, and %GOMODCACHE% offsets,
|
||||
// resp. We note that the URIBaseID offsets are not explicitly defined in
|
||||
// the sarif output. It is the clients responsibility to set them to resolve
|
||||
// paths at their local machines.
|
||||
//
|
||||
// All paths use "/" delimiter for portability.
|
||||
//
|
||||
// Properties field of a Tool.Driver is a govulncheck.Config used for the
|
||||
// invocation of govulncheck producing the Results. Properties field of
|
||||
// a Rule contains information on CVE and GHSA aliases for the corresponding
|
||||
// rule OSV. Clients can use this information to, say, suppress and filter
|
||||
// vulnerabilities.
|
||||
//
|
||||
// Please see the definition of types below for more information.
|
||||
package sarif
|
||||
|
||||
import "golang.org/x/vuln/internal/govulncheck"
|
||||
|
||||
// Log is the top-level SARIF object encoded in UTF-8.
|
||||
type Log struct {
|
||||
// Version should always be "2.1.0"
|
||||
Version string `json:"version,omitempty"`
|
||||
|
||||
// Schema should always be "https://json.schemastore.org/sarif-2.1.0.json"
|
||||
Schema string `json:"$schema,omitempty"`
|
||||
|
||||
// Runs describes executions of static analysis tools. For govulncheck,
|
||||
// there will be only one run object.
|
||||
Runs []Run `json:"runs,omitempty"`
|
||||
}
|
||||
|
||||
// Run summarizes results of a single invocation of a static analysis tool,
|
||||
// in this case govulncheck.
|
||||
type Run struct {
|
||||
Tool Tool `json:"tool,omitempty"`
|
||||
// Results contain govulncheck findings. There should be exactly one
|
||||
// Result per a detected use of an OSV.
|
||||
Results []Result `json:"results"`
|
||||
}
|
||||
|
||||
// Tool captures information about govulncheck analysis that was run.
|
||||
type Tool struct {
|
||||
Driver Driver `json:"driver,omitempty"`
|
||||
}
|
||||
|
||||
// Driver provides details about the govulncheck binary being executed.
|
||||
type Driver struct {
|
||||
// Name is "govulncheck"
|
||||
Name string `json:"name,omitempty"`
|
||||
// Version is the govulncheck version
|
||||
Version string `json:"semanticVersion,omitempty"`
|
||||
// InformationURI points to the description of govulncheck tool
|
||||
InformationURI string `json:"informationUri,omitempty"`
|
||||
// Properties are govulncheck run metadata, such as vuln db, Go version, etc.
|
||||
Properties govulncheck.Config `json:"properties,omitempty"`
|
||||
|
||||
Rules []Rule `json:"rules"`
|
||||
}
|
||||
|
||||
// Rule corresponds to the static analysis rule/analyzer that
|
||||
// produces findings. For govulncheck, rules are OSVs.
|
||||
type Rule struct {
|
||||
// ID is OSV.ID
|
||||
ID string `json:"id,omitempty"`
|
||||
ShortDescription Description `json:"shortDescription,omitempty"`
|
||||
FullDescription Description `json:"fullDescription,omitempty"`
|
||||
Help Description `json:"help,omitempty"`
|
||||
HelpURI string `json:"helpUri,omitempty"`
|
||||
// Properties contain OSV.Aliases (CVEs and GHSAs) as tags.
|
||||
// Consumers of govulncheck SARIF can use these tags to filter
|
||||
// results.
|
||||
Properties RuleTags `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// RuleTags defines properties.tags.
|
||||
type RuleTags struct {
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
// Description is a text in its raw or markdown form.
|
||||
type Description struct {
|
||||
Text string `json:"text,omitempty"`
|
||||
Markdown string `json:"markdown,omitempty"`
|
||||
}
|
||||
|
||||
// Result is a set of govulncheck findings for an OSV. For call stack
|
||||
// mode, it will contain call stacks for the OSV. There is exactly
|
||||
// one Result per detected OSV. Only findings at the most precise
|
||||
// detected level appear in the Result. For instance, if there are
|
||||
// symbol findings for an OSV, those findings will be in the Result,
|
||||
// but not the package and module level findings for the same OSV.
|
||||
type Result struct {
|
||||
// RuleID is the Rule.ID/OSV producing the finding.
|
||||
RuleID string `json:"ruleId,omitempty"`
|
||||
// Level is one of "error", "warning", and "note".
|
||||
Level string `json:"level,omitempty"`
|
||||
// Message explains the overall findings.
|
||||
Message Description `json:"message,omitempty"`
|
||||
// Locations to which the findings are associated. Always
|
||||
// a single location pointing to the first line of the go.mod
|
||||
// file. The path to the file is "go.mod".
|
||||
Locations []Location `json:"locations,omitempty"`
|
||||
// CodeFlows summarize call stacks produced by govulncheck.
|
||||
CodeFlows []CodeFlow `json:"codeFlows,omitempty"`
|
||||
// Stacks encode call stacks produced by govulncheck.
|
||||
Stacks []Stack `json:"stacks,omitempty"`
|
||||
}
|
||||
|
||||
// CodeFlow summarizes a detected offending flow of information in terms of
|
||||
// code locations. More precisely, it can contain several related information
|
||||
// flows, keeping them together. In govulncheck, those can be all call stacks
|
||||
// for, say, a particular symbol or package.
|
||||
type CodeFlow struct {
|
||||
// ThreadFlows is effectively a set of related information flows.
|
||||
ThreadFlows []ThreadFlow `json:"threadFlows"`
|
||||
Message Description `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// ThreadFlow encodes an information flow as a sequence of locations.
|
||||
// For govulncheck, it can encode a call stack.
|
||||
type ThreadFlow struct {
|
||||
Locations []ThreadFlowLocation `json:"locations,omitempty"`
|
||||
}
|
||||
|
||||
type ThreadFlowLocation struct {
|
||||
// Module is module information in the form <module-path>@<version>.
|
||||
// <version> can be empty when the module version is not known as
|
||||
// with, say, the source module analyzed.
|
||||
Module string `json:"module,omitempty"`
|
||||
// Location also contains a Message field.
|
||||
Location Location `json:"location,omitempty"`
|
||||
}
|
||||
|
||||
// Stack is a sequence of frames and can encode a govulncheck call stack.
|
||||
type Stack struct {
|
||||
Message Description `json:"message,omitempty"`
|
||||
Frames []Frame `json:"frames"`
|
||||
}
|
||||
|
||||
// Frame is effectively a module location. It can also contain thread and
|
||||
// parameter info, but those are not needed for govulncheck.
|
||||
type Frame struct {
|
||||
// Module is module information in the form <module-path>@<version>.
|
||||
// <version> can be empty when the module version is not known as
|
||||
// with, say, the source module analyzed.
|
||||
Module string `json:"module,omitempty"`
|
||||
Location Location `json:"location,omitempty"`
|
||||
}
|
||||
|
||||
// Location is currently a physical location annotated with a message.
|
||||
type Location struct {
|
||||
PhysicalLocation PhysicalLocation `json:"physicalLocation,omitempty"`
|
||||
Message Description `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type PhysicalLocation struct {
|
||||
ArtifactLocation ArtifactLocation `json:"artifactLocation,omitempty"`
|
||||
Region Region `json:"region,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
SrcRootID = "%SRCROOT%"
|
||||
GoRootID = "%GOROOT%"
|
||||
GoModCacheID = "%GOMODCACHE%"
|
||||
)
|
||||
|
||||
// ArtifactLocation is a path to an offending file.
|
||||
type ArtifactLocation struct {
|
||||
// URI is a path relative to URIBaseID.
|
||||
URI string `json:"uri,omitempty"`
|
||||
// URIBaseID is offset for URI, one of %SRCROOT%, %GOROOT%,
|
||||
// and %GOMODCACHE%.
|
||||
URIBaseID string `json:"uriBaseId,omitempty"`
|
||||
}
|
||||
|
||||
// Region is a target region within a file.
|
||||
type Region struct {
|
||||
StartLine int `json:"startLine,omitempty"`
|
||||
StartColumn int `json:"startColumn,omitempty"`
|
||||
EndLine int `json:"endLine,omitempty"`
|
||||
EndColumn int `json:"endColumn,omitempty"`
|
||||
}
|
||||
46
vendor/golang.org/x/vuln/internal/sarif/utils.go
generated
vendored
Normal file
46
vendor/golang.org/x/vuln/internal/sarif/utils.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
package sarif
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
func choose(s1, s2 string, cond bool) string {
|
||||
if cond {
|
||||
return s1
|
||||
}
|
||||
return s2
|
||||
}
|
||||
|
||||
func list(elems []string) string {
|
||||
l := len(elems)
|
||||
if l == 0 {
|
||||
return ""
|
||||
}
|
||||
if l == 1 {
|
||||
return elems[0]
|
||||
}
|
||||
|
||||
cList := strings.Join(elems[:l-1], ", ")
|
||||
return cList + choose("", ",", l == 2) + " and " + elems[l-1]
|
||||
}
|
||||
|
||||
// symbol is simplified adaptation of internal/scan/symbol.
|
||||
func symbol(fr *govulncheck.Frame) string {
|
||||
if fr.Function == "" {
|
||||
return ""
|
||||
}
|
||||
sym := strings.Split(fr.Function, "$")[0]
|
||||
if fr.Receiver != "" {
|
||||
sym = fr.Receiver + "." + sym
|
||||
}
|
||||
if fr.Package != "" {
|
||||
sym = fr.Package + "." + sym
|
||||
}
|
||||
return sym
|
||||
}
|
||||
110
vendor/golang.org/x/vuln/internal/scan/binary.go
generated
vendored
Normal file
110
vendor/golang.org/x/vuln/internal/scan/binary.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/vuln/internal/buildinfo"
|
||||
"golang.org/x/vuln/internal/client"
|
||||
"golang.org/x/vuln/internal/derrors"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/vulncheck"
|
||||
)
|
||||
|
||||
// runBinary detects presence of vulnerable symbols in an executable or its minimal blob representation.
|
||||
func runBinary(ctx context.Context, handler govulncheck.Handler, cfg *config, client *client.Client) (err error) {
|
||||
defer derrors.Wrap(&err, "govulncheck")
|
||||
|
||||
bin, err := createBin(cfg.patterns[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := &govulncheck.Progress{Message: binaryProgressMessage}
|
||||
if err := handler.Progress(p); err != nil {
|
||||
return err
|
||||
}
|
||||
return vulncheck.Binary(ctx, handler, bin, &cfg.Config, client)
|
||||
}
|
||||
|
||||
func createBin(path string) (*vulncheck.Bin, error) {
|
||||
// First check if the path points to a Go binary. Otherwise, blob
|
||||
// parsing might json decode a Go binary which takes time.
|
||||
//
|
||||
// TODO(#64716): use fingerprinting to make this precise, clean, and fast.
|
||||
mods, packageSymbols, bi, err := buildinfo.ExtractPackagesAndSymbols(path)
|
||||
if err == nil {
|
||||
var main *packages.Module
|
||||
if bi.Main.Path != "" {
|
||||
main = &packages.Module{
|
||||
Path: bi.Main.Path,
|
||||
Version: bi.Main.Version,
|
||||
}
|
||||
}
|
||||
|
||||
return &vulncheck.Bin{
|
||||
Path: bi.Path,
|
||||
Main: main,
|
||||
Modules: mods,
|
||||
PkgSymbols: packageSymbols,
|
||||
GoVersion: bi.GoVersion,
|
||||
GOOS: findSetting("GOOS", bi),
|
||||
GOARCH: findSetting("GOARCH", bi),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Otherwise, see if the path points to a valid blob.
|
||||
bin := parseBlob(path)
|
||||
if bin != nil {
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("unrecognized binary format")
|
||||
}
|
||||
|
||||
// parseBlob extracts vulncheck.Bin from a valid blob at path.
|
||||
// If it cannot recognize a valid blob, returns nil.
|
||||
func parseBlob(path string) *vulncheck.Bin {
|
||||
from, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer from.Close()
|
||||
|
||||
dec := json.NewDecoder(from)
|
||||
|
||||
var h header
|
||||
if err := dec.Decode(&h); err != nil {
|
||||
return nil // no header
|
||||
} else if h.Name != extractModeID || h.Version != extractModeVersion {
|
||||
return nil // invalid header
|
||||
}
|
||||
|
||||
var b vulncheck.Bin
|
||||
if err := dec.Decode(&b); err != nil {
|
||||
return nil // no body
|
||||
}
|
||||
if dec.More() {
|
||||
return nil // we want just header and body, nothing else
|
||||
}
|
||||
return &b
|
||||
}
|
||||
|
||||
// findSetting returns value of setting from bi if present.
|
||||
// Otherwise, returns "".
|
||||
func findSetting(setting string, bi *debug.BuildInfo) string {
|
||||
for _, s := range bi.Settings {
|
||||
if s.Key == setting {
|
||||
return s.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
97
vendor/golang.org/x/vuln/internal/scan/color.go
generated
vendored
Normal file
97
vendor/golang.org/x/vuln/internal/scan/color.go
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2022 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.
|
||||
package scan
|
||||
|
||||
const (
|
||||
// These are all the constants for the terminal escape strings
|
||||
|
||||
colorEscape = "\033["
|
||||
colorEnd = "m"
|
||||
|
||||
colorReset = colorEscape + "0" + colorEnd
|
||||
colorBold = colorEscape + "1" + colorEnd
|
||||
colorFaint = colorEscape + "2" + colorEnd
|
||||
colorUnderline = colorEscape + "4" + colorEnd
|
||||
colorBlink = colorEscape + "5" + colorEnd
|
||||
|
||||
fgBlack = colorEscape + "30" + colorEnd
|
||||
fgRed = colorEscape + "31" + colorEnd
|
||||
fgGreen = colorEscape + "32" + colorEnd
|
||||
fgYellow = colorEscape + "33" + colorEnd
|
||||
fgBlue = colorEscape + "34" + colorEnd
|
||||
fgMagenta = colorEscape + "35" + colorEnd
|
||||
fgCyan = colorEscape + "36" + colorEnd
|
||||
fgWhite = colorEscape + "37" + colorEnd
|
||||
|
||||
bgBlack = colorEscape + "40" + colorEnd
|
||||
bgRed = colorEscape + "41" + colorEnd
|
||||
bgGreen = colorEscape + "42" + colorEnd
|
||||
bgYellow = colorEscape + "43" + colorEnd
|
||||
bgBlue = colorEscape + "44" + colorEnd
|
||||
bgMagenta = colorEscape + "45" + colorEnd
|
||||
bgCyan = colorEscape + "46" + colorEnd
|
||||
bgWhite = colorEscape + "47" + colorEnd
|
||||
|
||||
fgBlackHi = colorEscape + "90" + colorEnd
|
||||
fgRedHi = colorEscape + "91" + colorEnd
|
||||
fgGreenHi = colorEscape + "92" + colorEnd
|
||||
fgYellowHi = colorEscape + "93" + colorEnd
|
||||
fgBlueHi = colorEscape + "94" + colorEnd
|
||||
fgMagentaHi = colorEscape + "95" + colorEnd
|
||||
fgCyanHi = colorEscape + "96" + colorEnd
|
||||
fgWhiteHi = colorEscape + "97" + colorEnd
|
||||
|
||||
bgBlackHi = colorEscape + "100" + colorEnd
|
||||
bgRedHi = colorEscape + "101" + colorEnd
|
||||
bgGreenHi = colorEscape + "102" + colorEnd
|
||||
bgYellowHi = colorEscape + "103" + colorEnd
|
||||
bgBlueHi = colorEscape + "104" + colorEnd
|
||||
bgMagentaHi = colorEscape + "105" + colorEnd
|
||||
bgCyanHi = colorEscape + "106" + colorEnd
|
||||
bgWhiteHi = colorEscape + "107" + colorEnd
|
||||
)
|
||||
|
||||
const (
|
||||
_ = colorReset
|
||||
_ = colorBold
|
||||
_ = colorFaint
|
||||
_ = colorUnderline
|
||||
_ = colorBlink
|
||||
|
||||
_ = fgBlack
|
||||
_ = fgRed
|
||||
_ = fgGreen
|
||||
_ = fgYellow
|
||||
_ = fgBlue
|
||||
_ = fgMagenta
|
||||
_ = fgCyan
|
||||
_ = fgWhite
|
||||
|
||||
_ = fgBlackHi
|
||||
_ = fgRedHi
|
||||
_ = fgGreenHi
|
||||
_ = fgYellowHi
|
||||
_ = fgBlueHi
|
||||
_ = fgMagentaHi
|
||||
_ = fgCyanHi
|
||||
_ = fgWhiteHi
|
||||
|
||||
_ = bgBlack
|
||||
_ = bgRed
|
||||
_ = bgGreen
|
||||
_ = bgYellow
|
||||
_ = bgBlue
|
||||
_ = bgMagenta
|
||||
_ = bgCyan
|
||||
_ = bgWhite
|
||||
|
||||
_ = bgBlackHi
|
||||
_ = bgRedHi
|
||||
_ = bgGreenHi
|
||||
_ = bgYellowHi
|
||||
_ = bgBlueHi
|
||||
_ = bgMagentaHi
|
||||
_ = bgCyanHi
|
||||
_ = bgWhiteHi
|
||||
)
|
||||
67
vendor/golang.org/x/vuln/internal/scan/errors.go
generated
vendored
Normal file
67
vendor/golang.org/x/vuln/internal/scan/errors.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//lint:file-ignore ST1005 Ignore staticcheck message about error formatting
|
||||
var (
|
||||
// ErrVulnerabilitiesFound indicates that vulnerabilities were detected
|
||||
// when running govulncheck. This returns exit status 3 when running
|
||||
// without the -json flag.
|
||||
errVulnerabilitiesFound = &exitCodeError{message: "vulnerabilities found", code: 3}
|
||||
|
||||
// errHelp indicates that usage help was requested.
|
||||
errHelp = &exitCodeError{message: "help requested", code: 0}
|
||||
|
||||
// errUsage indicates that there was a usage error on the command line.
|
||||
//
|
||||
// In this case, we assume that the user does not know how to run
|
||||
// govulncheck and exit with status 2.
|
||||
errUsage = &exitCodeError{message: "invalid usage", code: 2}
|
||||
|
||||
// errGoVersionMismatch is used to indicate that there is a mismatch between
|
||||
// the Go version used to build govulncheck and the one currently on PATH.
|
||||
errGoVersionMismatch = errors.New(`Loading packages failed, possibly due to a mismatch between the Go version
|
||||
used to build govulncheck and the Go version on PATH. Consider rebuilding
|
||||
govulncheck with the current Go version.`)
|
||||
|
||||
// errNoGoMod indicates that a go.mod file was not found in this module.
|
||||
errNoGoMod = errors.New(`no go.mod file
|
||||
|
||||
govulncheck only works with Go modules. Try navigating to your module directory.
|
||||
Otherwise, run go mod init to make your project a module.
|
||||
|
||||
See https://go.dev/doc/modules/managing-dependencies for more information.`)
|
||||
|
||||
// errNoBinaryFlag indicates that govulncheck was run on a file, without
|
||||
// the -mode=binary flag.
|
||||
errNoBinaryFlag = errors.New(`By default, govulncheck runs source analysis on Go modules.
|
||||
|
||||
Did you mean to run govulncheck with -mode=binary?
|
||||
|
||||
For details, run govulncheck -h.`)
|
||||
)
|
||||
|
||||
type exitCodeError struct {
|
||||
message string
|
||||
code int
|
||||
}
|
||||
|
||||
func (e *exitCodeError) Error() string { return e.message }
|
||||
func (e *exitCodeError) ExitCode() int { return e.code }
|
||||
|
||||
// isGoVersionMismatchError checks if err is due to mismatch between
|
||||
// the Go version used to build govulncheck and the one currently
|
||||
// on PATH.
|
||||
func isGoVersionMismatchError(err error) bool {
|
||||
msg := err.Error()
|
||||
// See golang.org/x/tools/go/packages/packages.go.
|
||||
return strings.Contains(msg, "This application uses version go") &&
|
||||
strings.Contains(msg, "It may fail to process source files")
|
||||
}
|
||||
60
vendor/golang.org/x/vuln/internal/scan/extract.go
generated
vendored
Normal file
60
vendor/golang.org/x/vuln/internal/scan/extract.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/vuln/internal/derrors"
|
||||
"golang.org/x/vuln/internal/vulncheck"
|
||||
)
|
||||
|
||||
const (
|
||||
// extractModeID is the unique name of the extract mode protocol
|
||||
extractModeID = "govulncheck-extract"
|
||||
extractModeVersion = "0.1.0"
|
||||
)
|
||||
|
||||
// header information for the blob output.
|
||||
type header struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// runExtract dumps the extracted abstraction of binary at cfg.patterns to out.
|
||||
// It prints out exactly two blob messages, one with the header and one with
|
||||
// the vulncheck.Bin as the body.
|
||||
func runExtract(cfg *config, out io.Writer) (err error) {
|
||||
defer derrors.Wrap(&err, "govulncheck")
|
||||
|
||||
bin, err := createBin(cfg.patterns[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sortBin(bin) // sort for easier testing and validation
|
||||
header := header{
|
||||
Name: extractModeID,
|
||||
Version: extractModeVersion,
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(out)
|
||||
|
||||
if err := enc.Encode(header); err != nil {
|
||||
return fmt.Errorf("marshaling blob header: %v", err)
|
||||
}
|
||||
if err := enc.Encode(bin); err != nil {
|
||||
return fmt.Errorf("marshaling blob body: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sortBin(bin *vulncheck.Bin) {
|
||||
sort.SliceStable(bin.PkgSymbols, func(i, j int) bool {
|
||||
return bin.PkgSymbols[i].Pkg+"."+bin.PkgSymbols[i].Name < bin.PkgSymbols[j].Pkg+"."+bin.PkgSymbols[j].Name
|
||||
})
|
||||
}
|
||||
35
vendor/golang.org/x/vuln/internal/scan/filepath.go
generated
vendored
Normal file
35
vendor/golang.org/x/vuln/internal/scan/filepath.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AbsRelShorter takes path and returns its path relative
|
||||
// to the current directory, if shorter. Returns path
|
||||
// when path is an empty string or upon any error.
|
||||
func AbsRelShorter(path string) string {
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
c, err := filepath.Abs(".")
|
||||
if err != nil {
|
||||
return path
|
||||
}
|
||||
r, err := filepath.Rel(c, path)
|
||||
if err != nil {
|
||||
return path
|
||||
}
|
||||
|
||||
rSegments := strings.Split(r, string(filepath.Separator))
|
||||
pathSegments := strings.Split(path, string(filepath.Separator))
|
||||
if len(rSegments) < len(pathSegments) {
|
||||
return r
|
||||
}
|
||||
return path
|
||||
}
|
||||
303
vendor/golang.org/x/vuln/internal/scan/flags.go
generated
vendored
Normal file
303
vendor/golang.org/x/vuln/internal/scan/flags.go
generated
vendored
Normal file
@@ -0,0 +1,303 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
govulncheck.Config
|
||||
patterns []string
|
||||
db string
|
||||
dir string
|
||||
tags buildutil.TagsFlag
|
||||
test bool
|
||||
show ShowFlag
|
||||
format FormatFlag
|
||||
env []string
|
||||
}
|
||||
|
||||
func parseFlags(cfg *config, stderr io.Writer, args []string) error {
|
||||
var version bool
|
||||
var json bool
|
||||
var scanFlag ScanFlag
|
||||
var modeFlag ModeFlag
|
||||
flags := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
flags.SetOutput(stderr)
|
||||
flags.BoolVar(&json, "json", false, "output JSON (Go compatible legacy flag, see format flag)")
|
||||
flags.BoolVar(&cfg.test, "test", false, "analyze test files (only valid for source mode, default false)")
|
||||
flags.StringVar(&cfg.dir, "C", "", "change to `dir` before running govulncheck")
|
||||
flags.StringVar(&cfg.db, "db", "https://vuln.go.dev", "vulnerability database `url`")
|
||||
flags.Var(&modeFlag, "mode", "supports 'source', 'binary', and 'extract' (default 'source')")
|
||||
flags.Var(&cfg.tags, "tags", "comma-separated `list` of build tags")
|
||||
flags.Var(&cfg.show, "show", "enable display of additional information specified by the comma separated `list`\nThe supported values are 'traces','color', 'version', and 'verbose'")
|
||||
flags.Var(&cfg.format, "format", "specify format output\nThe supported values are 'text', 'json', 'sarif', and 'openvex' (default 'text')")
|
||||
flags.BoolVar(&version, "version", false, "print the version information")
|
||||
flags.Var(&scanFlag, "scan", "set the scanning level desired, one of 'module', 'package', or 'symbol' (default 'symbol')")
|
||||
|
||||
// We don't want to print the whole usage message on each flags
|
||||
// error, so we set to a no-op and do the printing ourselves.
|
||||
flags.Usage = func() {}
|
||||
usage := func() {
|
||||
fmt.Fprint(flags.Output(), `Govulncheck reports known vulnerabilities in dependencies.
|
||||
|
||||
Usage:
|
||||
|
||||
govulncheck [flags] [patterns]
|
||||
govulncheck -mode=binary [flags] [binary]
|
||||
|
||||
`)
|
||||
flags.PrintDefaults()
|
||||
fmt.Fprintf(flags.Output(), "\n%s\n", detailsMessage)
|
||||
}
|
||||
|
||||
if err := flags.Parse(args); err != nil {
|
||||
if err == flag.ErrHelp {
|
||||
usage() // print usage only on help
|
||||
return errHelp
|
||||
}
|
||||
return errUsage
|
||||
}
|
||||
cfg.patterns = flags.Args()
|
||||
if version {
|
||||
cfg.show = append(cfg.show, "version")
|
||||
}
|
||||
cfg.ScanLevel = govulncheck.ScanLevel(scanFlag)
|
||||
cfg.ScanMode = govulncheck.ScanMode(modeFlag)
|
||||
if err := validateConfig(cfg, json); err != nil {
|
||||
fmt.Fprintln(flags.Output(), err)
|
||||
return errUsage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateConfig(cfg *config, json bool) error {
|
||||
// take care of default values
|
||||
if cfg.ScanMode == "" {
|
||||
cfg.ScanMode = govulncheck.ScanModeSource
|
||||
}
|
||||
if cfg.ScanLevel == "" {
|
||||
cfg.ScanLevel = govulncheck.ScanLevelSymbol
|
||||
}
|
||||
if json {
|
||||
if cfg.format != formatUnset {
|
||||
return fmt.Errorf("the -json flag cannot be used with -format flag")
|
||||
}
|
||||
cfg.format = formatJSON
|
||||
} else {
|
||||
if cfg.format == formatUnset {
|
||||
cfg.format = formatText
|
||||
}
|
||||
}
|
||||
|
||||
// show flag is only supported with text output
|
||||
if cfg.format != formatText && len(cfg.show) > 0 {
|
||||
return fmt.Errorf("the -show flag is not supported for %s output", cfg.format)
|
||||
}
|
||||
|
||||
switch cfg.ScanMode {
|
||||
case govulncheck.ScanModeSource:
|
||||
if len(cfg.patterns) == 1 && isFile(cfg.patterns[0]) {
|
||||
return fmt.Errorf("%q is a file.\n\n%v", cfg.patterns[0], errNoBinaryFlag)
|
||||
}
|
||||
if cfg.ScanLevel == govulncheck.ScanLevelModule && len(cfg.patterns) != 0 {
|
||||
return fmt.Errorf("patterns are not accepted for module only scanning")
|
||||
}
|
||||
case govulncheck.ScanModeBinary:
|
||||
if cfg.test {
|
||||
return fmt.Errorf("the -test flag is not supported in binary mode")
|
||||
}
|
||||
if len(cfg.tags) > 0 {
|
||||
return fmt.Errorf("the -tags flag is not supported in binary mode")
|
||||
}
|
||||
if len(cfg.patterns) != 1 {
|
||||
return fmt.Errorf("only 1 binary can be analyzed at a time")
|
||||
}
|
||||
if !isFile(cfg.patterns[0]) {
|
||||
return fmt.Errorf("%q is not a file", cfg.patterns[0])
|
||||
}
|
||||
case govulncheck.ScanModeExtract:
|
||||
if cfg.test {
|
||||
return fmt.Errorf("the -test flag is not supported in extract mode")
|
||||
}
|
||||
if len(cfg.tags) > 0 {
|
||||
return fmt.Errorf("the -tags flag is not supported in extract mode")
|
||||
}
|
||||
if len(cfg.patterns) != 1 {
|
||||
return fmt.Errorf("only 1 binary can be extracted at a time")
|
||||
}
|
||||
if cfg.format == formatJSON {
|
||||
return fmt.Errorf("the json format must be off in extract mode")
|
||||
}
|
||||
if !isFile(cfg.patterns[0]) {
|
||||
return fmt.Errorf("%q is not a file (source extraction is not supported)", cfg.patterns[0])
|
||||
}
|
||||
case govulncheck.ScanModeConvert:
|
||||
if len(cfg.patterns) != 0 {
|
||||
return fmt.Errorf("patterns are not accepted in convert mode")
|
||||
}
|
||||
if cfg.dir != "" {
|
||||
return fmt.Errorf("the -C flag is not supported in convert mode")
|
||||
}
|
||||
if cfg.test {
|
||||
return fmt.Errorf("the -test flag is not supported in convert mode")
|
||||
}
|
||||
if len(cfg.tags) > 0 {
|
||||
return fmt.Errorf("the -tags flag is not supported in convert mode")
|
||||
}
|
||||
case govulncheck.ScanModeQuery:
|
||||
if cfg.test {
|
||||
return fmt.Errorf("the -test flag is not supported in query mode")
|
||||
}
|
||||
if len(cfg.tags) > 0 {
|
||||
return fmt.Errorf("the -tags flag is not supported in query mode")
|
||||
}
|
||||
if cfg.format != formatJSON {
|
||||
return fmt.Errorf("the json format must be set in query mode")
|
||||
}
|
||||
for _, pattern := range cfg.patterns {
|
||||
// Parse the input here so that we can catch errors before
|
||||
// outputting the Config.
|
||||
if _, _, err := parseModuleQuery(pattern); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isFile(path string) bool {
|
||||
s, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return !s.IsDir()
|
||||
}
|
||||
|
||||
var errFlagParse = errors.New("see -help for details")
|
||||
|
||||
// ShowFlag is used for parsing and validation of
|
||||
// govulncheck -show flag.
|
||||
type ShowFlag []string
|
||||
|
||||
var supportedShows = map[string]bool{
|
||||
"traces": true,
|
||||
"color": true,
|
||||
"verbose": true,
|
||||
"version": true,
|
||||
}
|
||||
|
||||
func (v *ShowFlag) Set(s string) error {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
for _, show := range strings.Split(s, ",") {
|
||||
sh := strings.TrimSpace(show)
|
||||
if _, ok := supportedShows[sh]; !ok {
|
||||
return errFlagParse
|
||||
}
|
||||
*v = append(*v, sh)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *ShowFlag) Get() interface{} { return *v }
|
||||
func (v *ShowFlag) String() string { return "" }
|
||||
|
||||
// Update the text handler h with values of the flag.
|
||||
func (v ShowFlag) Update(h *TextHandler) {
|
||||
for _, show := range v {
|
||||
switch show {
|
||||
case "traces":
|
||||
h.showTraces = true
|
||||
case "color":
|
||||
h.showColor = true
|
||||
case "version":
|
||||
h.showVersion = true
|
||||
case "verbose":
|
||||
h.showVerbose = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FormatFlag is used for parsing and validation of
|
||||
// govulncheck -format flag.
|
||||
type FormatFlag string
|
||||
|
||||
const (
|
||||
formatUnset = ""
|
||||
formatJSON = "json"
|
||||
formatText = "text"
|
||||
formatSarif = "sarif"
|
||||
formatOpenVEX = "openvex"
|
||||
)
|
||||
|
||||
var supportedFormats = map[string]bool{
|
||||
formatJSON: true,
|
||||
formatText: true,
|
||||
formatSarif: true,
|
||||
formatOpenVEX: true,
|
||||
}
|
||||
|
||||
func (f *FormatFlag) Get() interface{} { return *f }
|
||||
func (f *FormatFlag) Set(s string) error {
|
||||
if _, ok := supportedFormats[s]; !ok {
|
||||
return errFlagParse
|
||||
}
|
||||
*f = FormatFlag(s)
|
||||
return nil
|
||||
}
|
||||
func (f *FormatFlag) String() string { return "" }
|
||||
|
||||
// ModeFlag is used for parsing and validation of
|
||||
// govulncheck -mode flag.
|
||||
type ModeFlag string
|
||||
|
||||
var supportedModes = map[string]bool{
|
||||
govulncheck.ScanModeSource: true,
|
||||
govulncheck.ScanModeBinary: true,
|
||||
govulncheck.ScanModeConvert: true,
|
||||
govulncheck.ScanModeQuery: true,
|
||||
govulncheck.ScanModeExtract: true,
|
||||
}
|
||||
|
||||
func (f *ModeFlag) Get() interface{} { return *f }
|
||||
func (f *ModeFlag) Set(s string) error {
|
||||
if _, ok := supportedModes[s]; !ok {
|
||||
return errFlagParse
|
||||
}
|
||||
*f = ModeFlag(s)
|
||||
return nil
|
||||
}
|
||||
func (f *ModeFlag) String() string { return "" }
|
||||
|
||||
// ScanFlag is used for parsing and validation of
|
||||
// govulncheck -scan flag.
|
||||
type ScanFlag string
|
||||
|
||||
var supportedLevels = map[string]bool{
|
||||
govulncheck.ScanLevelModule: true,
|
||||
govulncheck.ScanLevelPackage: true,
|
||||
govulncheck.ScanLevelSymbol: true,
|
||||
}
|
||||
|
||||
func (f *ScanFlag) Get() interface{} { return *f }
|
||||
func (f *ScanFlag) Set(s string) error {
|
||||
if _, ok := supportedLevels[s]; !ok {
|
||||
return errFlagParse
|
||||
}
|
||||
*f = ScanFlag(s)
|
||||
return nil
|
||||
}
|
||||
func (f *ScanFlag) String() string { return "" }
|
||||
74
vendor/golang.org/x/vuln/internal/scan/query.go
generated
vendored
Normal file
74
vendor/golang.org/x/vuln/internal/scan/query.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"golang.org/x/vuln/internal/client"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
isem "golang.org/x/vuln/internal/semver"
|
||||
)
|
||||
|
||||
// runQuery reports vulnerabilities that apply to the queries in the config.
|
||||
func runQuery(ctx context.Context, handler govulncheck.Handler, cfg *config, c *client.Client) error {
|
||||
reqs := make([]*client.ModuleRequest, len(cfg.patterns))
|
||||
for i, query := range cfg.patterns {
|
||||
mod, ver, err := parseModuleQuery(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := handler.Progress(queryProgressMessage(mod, ver)); err != nil {
|
||||
return err
|
||||
}
|
||||
reqs[i] = &client.ModuleRequest{
|
||||
Path: mod, Version: ver,
|
||||
}
|
||||
}
|
||||
|
||||
resps, err := c.ByModules(ctx, reqs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ids := make(map[string]bool)
|
||||
for _, resp := range resps {
|
||||
for _, entry := range resp.Entries {
|
||||
if _, ok := ids[entry.ID]; !ok {
|
||||
err := handler.OSV(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ids[entry.ID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func queryProgressMessage(module, version string) *govulncheck.Progress {
|
||||
return &govulncheck.Progress{
|
||||
Message: fmt.Sprintf("Looking up vulnerabilities in %s at %s...", module, version),
|
||||
}
|
||||
}
|
||||
|
||||
var modQueryRegex = regexp.MustCompile(`(.+)@(.+)`)
|
||||
|
||||
func parseModuleQuery(pattern string) (_ string, _ string, err error) {
|
||||
matches := modQueryRegex.FindStringSubmatch(pattern)
|
||||
// matches should be [module@version, module, version]
|
||||
if len(matches) != 3 {
|
||||
return "", "", fmt.Errorf("invalid query %s: must be of the form module@version", pattern)
|
||||
}
|
||||
mod, ver := matches[1], matches[2]
|
||||
if !isem.Valid(ver) {
|
||||
return "", "", fmt.Errorf("version %s is not valid semver", ver)
|
||||
}
|
||||
|
||||
return mod, ver, nil
|
||||
}
|
||||
160
vendor/golang.org/x/vuln/internal/scan/run.go
generated
vendored
Normal file
160
vendor/golang.org/x/vuln/internal/scan/run.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/telemetry/counter"
|
||||
"golang.org/x/vuln/internal/client"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/openvex"
|
||||
"golang.org/x/vuln/internal/sarif"
|
||||
)
|
||||
|
||||
// RunGovulncheck performs main govulncheck functionality and exits the
|
||||
// program upon success with an appropriate exit status. Otherwise,
|
||||
// returns an error.
|
||||
func RunGovulncheck(ctx context.Context, env []string, r io.Reader, stdout io.Writer, stderr io.Writer, args []string) error {
|
||||
cfg := &config{env: env}
|
||||
if err := parseFlags(cfg, stderr, args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := client.NewClient(cfg.db, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating client: %w", err)
|
||||
}
|
||||
|
||||
prepareConfig(ctx, cfg, client)
|
||||
var handler govulncheck.Handler
|
||||
switch cfg.format {
|
||||
case formatJSON:
|
||||
handler = govulncheck.NewJSONHandler(stdout)
|
||||
case formatSarif:
|
||||
handler = sarif.NewHandler(stdout)
|
||||
case formatOpenVEX:
|
||||
handler = openvex.NewHandler(stdout)
|
||||
default:
|
||||
th := NewTextHandler(stdout)
|
||||
cfg.show.Update(th)
|
||||
handler = th
|
||||
}
|
||||
|
||||
if err := handler.Config(&cfg.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
incTelemetryFlagCounters(cfg)
|
||||
|
||||
switch cfg.ScanMode {
|
||||
case govulncheck.ScanModeSource:
|
||||
dir := filepath.FromSlash(cfg.dir)
|
||||
err = runSource(ctx, handler, cfg, client, dir)
|
||||
case govulncheck.ScanModeBinary:
|
||||
err = runBinary(ctx, handler, cfg, client)
|
||||
case govulncheck.ScanModeExtract:
|
||||
return runExtract(cfg, stdout)
|
||||
case govulncheck.ScanModeQuery:
|
||||
err = runQuery(ctx, handler, cfg, client)
|
||||
case govulncheck.ScanModeConvert:
|
||||
err = govulncheck.HandleJSON(r, handler)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Flush(handler)
|
||||
}
|
||||
|
||||
func prepareConfig(ctx context.Context, cfg *config, client *client.Client) {
|
||||
cfg.ProtocolVersion = govulncheck.ProtocolVersion
|
||||
cfg.DB = cfg.db
|
||||
if cfg.ScanMode == govulncheck.ScanModeSource && cfg.GoVersion == "" {
|
||||
const goverPrefix = "GOVERSION="
|
||||
for _, env := range cfg.env {
|
||||
if val := strings.TrimPrefix(env, goverPrefix); val != env {
|
||||
cfg.GoVersion = val
|
||||
}
|
||||
}
|
||||
if cfg.GoVersion == "" {
|
||||
if out, err := exec.Command("go", "env", "GOVERSION").Output(); err == nil {
|
||||
cfg.GoVersion = strings.TrimSpace(string(out))
|
||||
}
|
||||
}
|
||||
}
|
||||
if bi, ok := debug.ReadBuildInfo(); ok {
|
||||
scannerVersion(cfg, bi)
|
||||
}
|
||||
if mod, err := client.LastModifiedTime(ctx); err == nil {
|
||||
cfg.DBLastModified = &mod
|
||||
}
|
||||
}
|
||||
|
||||
// scannerVersion reconstructs the current version of
|
||||
// this binary used from the build info.
|
||||
func scannerVersion(cfg *config, bi *debug.BuildInfo) {
|
||||
if bi.Path != "" {
|
||||
cfg.ScannerName = path.Base(bi.Path)
|
||||
}
|
||||
if bi.Main.Version != "" && bi.Main.Version != "(devel)" {
|
||||
cfg.ScannerVersion = bi.Main.Version
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(https://go.dev/issue/29228): we need to manually construct the
|
||||
// version string when it is "(devel)" until #29228 is resolved.
|
||||
var revision, at string
|
||||
for _, s := range bi.Settings {
|
||||
if s.Key == "vcs.revision" {
|
||||
revision = s.Value
|
||||
}
|
||||
if s.Key == "vcs.time" {
|
||||
at = s.Value
|
||||
}
|
||||
}
|
||||
buf := strings.Builder{}
|
||||
buf.WriteString("v0.0.0")
|
||||
if revision != "" {
|
||||
buf.WriteString("-")
|
||||
buf.WriteString(revision[:12])
|
||||
}
|
||||
if at != "" {
|
||||
// commit time is of the form 2023-01-25T19:57:54Z
|
||||
p, err := time.Parse(time.RFC3339, at)
|
||||
if err == nil {
|
||||
buf.WriteString("-")
|
||||
buf.WriteString(p.Format("20060102150405"))
|
||||
}
|
||||
}
|
||||
cfg.ScannerVersion = buf.String()
|
||||
}
|
||||
|
||||
func incTelemetryFlagCounters(cfg *config) {
|
||||
counter.Inc(fmt.Sprintf("govulncheck/mode:%s", cfg.ScanMode))
|
||||
counter.Inc(fmt.Sprintf("govulncheck/scan:%s", cfg.ScanLevel))
|
||||
counter.Inc(fmt.Sprintf("govulncheck/format:%s", cfg.format))
|
||||
|
||||
if len(cfg.show) == 0 {
|
||||
counter.Inc("govulncheck/show:none")
|
||||
}
|
||||
for _, s := range cfg.show {
|
||||
counter.Inc(fmt.Sprintf("govulncheck/show:%s", s))
|
||||
}
|
||||
}
|
||||
|
||||
func Flush(h govulncheck.Handler) error {
|
||||
if th, ok := h.(interface{ Flush() error }); ok {
|
||||
return th.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
49
vendor/golang.org/x/vuln/internal/scan/source.go
generated
vendored
Normal file
49
vendor/golang.org/x/vuln/internal/scan/source.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/vuln/internal/client"
|
||||
"golang.org/x/vuln/internal/derrors"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/vulncheck"
|
||||
)
|
||||
|
||||
// runSource reports vulnerabilities that affect the analyzed packages.
|
||||
//
|
||||
// Vulnerabilities can be called (affecting the package, because a vulnerable
|
||||
// symbol is actually exercised) or just imported by the package
|
||||
// (likely having a non-affecting outcome).
|
||||
func runSource(ctx context.Context, handler govulncheck.Handler, cfg *config, client *client.Client, dir string) (err error) {
|
||||
defer derrors.Wrap(&err, "govulncheck")
|
||||
|
||||
if cfg.ScanLevel.WantPackages() && len(cfg.patterns) == 0 {
|
||||
return nil // don't throw an error here
|
||||
}
|
||||
if !gomodExists(dir) {
|
||||
return errNoGoMod
|
||||
}
|
||||
graph := vulncheck.NewPackageGraph(cfg.GoVersion)
|
||||
pkgConfig := &packages.Config{
|
||||
Dir: dir,
|
||||
Tests: cfg.test,
|
||||
Env: cfg.env,
|
||||
}
|
||||
if err := graph.LoadPackagesAndMods(pkgConfig, cfg.tags, cfg.patterns, cfg.ScanLevel == govulncheck.ScanLevelSymbol); err != nil {
|
||||
if isGoVersionMismatchError(err) {
|
||||
return fmt.Errorf("%v\n\n%v", errGoVersionMismatch, err)
|
||||
}
|
||||
return fmt.Errorf("loading packages: %w", err)
|
||||
}
|
||||
|
||||
if cfg.ScanLevel.WantPackages() && len(graph.TopPkgs()) == 0 {
|
||||
return nil // early exit
|
||||
}
|
||||
return vulncheck.Source(ctx, handler, &cfg.Config, client, graph)
|
||||
}
|
||||
71
vendor/golang.org/x/vuln/internal/scan/stdlib.go
generated
vendored
Normal file
71
vendor/golang.org/x/vuln/internal/scan/stdlib.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// Support functions for standard library packages.
|
||||
// These are copied from the internal/stdlib package in the pkgsite repo.
|
||||
|
||||
// semverToGoTag returns the Go standard library repository tag corresponding
|
||||
// to semver, a version string without the initial "v".
|
||||
// Go tags differ from standard semantic versions in a few ways,
|
||||
// such as beginning with "go" instead of "v".
|
||||
func semverToGoTag(v string) string {
|
||||
if strings.HasPrefix(v, "v0.0.0") {
|
||||
return "master"
|
||||
}
|
||||
// Special case: v1.0.0 => go1.
|
||||
if v == "v1.0.0" {
|
||||
return "go1"
|
||||
}
|
||||
if !semver.IsValid(v) {
|
||||
return fmt.Sprintf("<!%s:invalid semver>", v)
|
||||
}
|
||||
goVersion := semver.Canonical(v)
|
||||
prerelease := semver.Prerelease(goVersion)
|
||||
versionWithoutPrerelease := strings.TrimSuffix(goVersion, prerelease)
|
||||
patch := strings.TrimPrefix(versionWithoutPrerelease, semver.MajorMinor(goVersion)+".")
|
||||
if patch == "0" {
|
||||
versionWithoutPrerelease = strings.TrimSuffix(versionWithoutPrerelease, ".0")
|
||||
}
|
||||
goVersion = fmt.Sprintf("go%s", strings.TrimPrefix(versionWithoutPrerelease, "v"))
|
||||
if prerelease != "" {
|
||||
// Go prereleases look like "beta1" instead of "beta.1".
|
||||
// "beta1" is bad for sorting (since beta10 comes before beta9), so
|
||||
// require the dot form.
|
||||
i := finalDigitsIndex(prerelease)
|
||||
if i >= 1 {
|
||||
if prerelease[i-1] != '.' {
|
||||
return fmt.Sprintf("<!%s:final digits in a prerelease must follow a period>", v)
|
||||
}
|
||||
// Remove the dot.
|
||||
prerelease = prerelease[:i-1] + prerelease[i:]
|
||||
}
|
||||
goVersion += strings.TrimPrefix(prerelease, "-")
|
||||
}
|
||||
return goVersion
|
||||
}
|
||||
|
||||
// finalDigitsIndex returns the index of the first digit in the sequence of digits ending s.
|
||||
// If s doesn't end in digits, it returns -1.
|
||||
func finalDigitsIndex(s string) int {
|
||||
// Assume ASCII (since the semver package does anyway).
|
||||
var i int
|
||||
for i = len(s) - 1; i >= 0; i-- {
|
||||
if s[i] < '0' || s[i] > '9' {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i == len(s)-1 {
|
||||
return -1
|
||||
}
|
||||
return i + 1
|
||||
}
|
||||
290
vendor/golang.org/x/vuln/internal/scan/template.go
generated
vendored
Normal file
290
vendor/golang.org/x/vuln/internal/scan/template.go
generated
vendored
Normal file
@@ -0,0 +1,290 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"io"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
"golang.org/x/vuln/internal/traces"
|
||||
)
|
||||
|
||||
type findingSummary struct {
|
||||
*govulncheck.Finding
|
||||
Compact string
|
||||
OSV *osv.Entry
|
||||
}
|
||||
|
||||
type summaryCounters struct {
|
||||
VulnerabilitiesCalled int
|
||||
ModulesCalled int
|
||||
VulnerabilitiesImported int
|
||||
VulnerabilitiesRequired int
|
||||
StdlibCalled bool
|
||||
}
|
||||
|
||||
func fixupFindings(osvs []*osv.Entry, findings []*findingSummary) {
|
||||
for _, f := range findings {
|
||||
f.OSV = getOSV(osvs, f.Finding.OSV)
|
||||
}
|
||||
}
|
||||
|
||||
func groupByVuln(findings []*findingSummary) [][]*findingSummary {
|
||||
return groupBy(findings, func(left, right *findingSummary) int {
|
||||
return -strings.Compare(left.OSV.ID, right.OSV.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func groupByModule(findings []*findingSummary) [][]*findingSummary {
|
||||
return groupBy(findings, func(left, right *findingSummary) int {
|
||||
return strings.Compare(left.Trace[0].Module, right.Trace[0].Module)
|
||||
})
|
||||
}
|
||||
|
||||
func groupBy(findings []*findingSummary, compare func(left, right *findingSummary) int) [][]*findingSummary {
|
||||
switch len(findings) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return [][]*findingSummary{findings}
|
||||
}
|
||||
sort.SliceStable(findings, func(i, j int) bool {
|
||||
return compare(findings[i], findings[j]) < 0
|
||||
})
|
||||
result := [][]*findingSummary{}
|
||||
first := 0
|
||||
for i, next := range findings {
|
||||
if i == first {
|
||||
continue
|
||||
}
|
||||
if compare(findings[first], next) != 0 {
|
||||
result = append(result, findings[first:i])
|
||||
first = i
|
||||
}
|
||||
}
|
||||
result = append(result, findings[first:])
|
||||
return result
|
||||
}
|
||||
|
||||
func isRequired(findings []*findingSummary) bool {
|
||||
for _, f := range findings {
|
||||
if f.Trace[0].Module != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isImported(findings []*findingSummary) bool {
|
||||
for _, f := range findings {
|
||||
if f.Trace[0].Package != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isCalled(findings []*findingSummary) bool {
|
||||
for _, f := range findings {
|
||||
if f.Trace[0].Function != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getOSV(osvs []*osv.Entry, id string) *osv.Entry {
|
||||
for _, entry := range osvs {
|
||||
if entry.ID == id {
|
||||
return entry
|
||||
}
|
||||
}
|
||||
return &osv.Entry{
|
||||
ID: id,
|
||||
DatabaseSpecific: &osv.DatabaseSpecific{},
|
||||
}
|
||||
}
|
||||
|
||||
func newFindingSummary(f *govulncheck.Finding) *findingSummary {
|
||||
return &findingSummary{
|
||||
Finding: f,
|
||||
Compact: compactTrace(f),
|
||||
}
|
||||
}
|
||||
|
||||
// platforms returns a string describing the GOOS, GOARCH,
|
||||
// or GOOS/GOARCH pairs that the vuln affects for a particular
|
||||
// module mod. If it affects all of them, it returns the empty
|
||||
// string.
|
||||
//
|
||||
// When mod is an empty string, returns platform information for
|
||||
// all modules of e.
|
||||
func platforms(mod string, e *osv.Entry) []string {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
platforms := map[string]bool{}
|
||||
for _, a := range e.Affected {
|
||||
if mod != "" && a.Module.Path != mod {
|
||||
continue
|
||||
}
|
||||
for _, p := range a.EcosystemSpecific.Packages {
|
||||
for _, os := range p.GOOS {
|
||||
// In case there are no specific architectures,
|
||||
// just list the os entries.
|
||||
if len(p.GOARCH) == 0 {
|
||||
platforms[os] = true
|
||||
continue
|
||||
}
|
||||
// Otherwise, list all the os+arch combinations.
|
||||
for _, arch := range p.GOARCH {
|
||||
platforms[os+"/"+arch] = true
|
||||
}
|
||||
}
|
||||
// Cover the case where there are no specific
|
||||
// operating systems listed.
|
||||
if len(p.GOOS) == 0 {
|
||||
for _, arch := range p.GOARCH {
|
||||
platforms[arch] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var keys []string
|
||||
for k := range platforms {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
func posToString(p *govulncheck.Position) string {
|
||||
if p == nil || p.Line <= 0 {
|
||||
return ""
|
||||
}
|
||||
return token.Position{
|
||||
Filename: AbsRelShorter(p.Filename),
|
||||
Offset: p.Offset,
|
||||
Line: p.Line,
|
||||
Column: p.Column,
|
||||
}.String()
|
||||
}
|
||||
|
||||
func symbol(frame *govulncheck.Frame, short bool) string {
|
||||
buf := &strings.Builder{}
|
||||
addSymbol(buf, frame, short)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func symbolName(frame *govulncheck.Frame) string {
|
||||
buf := &strings.Builder{}
|
||||
addSymbolName(buf, frame)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// compactTrace returns a short description of the call stack.
|
||||
// It prefers to show you the edge from the top module to other code, along with
|
||||
// the vulnerable symbol.
|
||||
// Where the vulnerable symbol directly called by the users code, it will only
|
||||
// show those two points.
|
||||
// If the vulnerable symbol is in the users code, it will show the entry point
|
||||
// and the vulnerable symbol.
|
||||
func compactTrace(finding *govulncheck.Finding) string {
|
||||
compact := traces.Compact(finding)
|
||||
if len(compact) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
l := len(compact)
|
||||
iTop := l - 1
|
||||
buf := &strings.Builder{}
|
||||
topPos := posToString(compact[iTop].Position)
|
||||
if topPos != "" {
|
||||
buf.WriteString(topPos)
|
||||
buf.WriteString(": ")
|
||||
}
|
||||
|
||||
if l > 1 {
|
||||
// print the root of the compact trace
|
||||
addSymbol(buf, compact[iTop], true)
|
||||
buf.WriteString(" calls ")
|
||||
}
|
||||
if l > 2 {
|
||||
// print next element of the trace, if any
|
||||
addSymbol(buf, compact[iTop-1], true)
|
||||
buf.WriteString(", which")
|
||||
if l > 3 {
|
||||
// don't print the third element, just acknowledge it
|
||||
buf.WriteString(" eventually")
|
||||
}
|
||||
buf.WriteString(" calls ")
|
||||
}
|
||||
addSymbol(buf, compact[0], true) // print the vulnerable symbol
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// notIdentifier reports whether ch is an invalid identifier character.
|
||||
func notIdentifier(ch rune) bool {
|
||||
return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' ||
|
||||
'0' <= ch && ch <= '9' ||
|
||||
ch == '_' ||
|
||||
ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch)))
|
||||
}
|
||||
|
||||
// importPathToAssumedName is taken from goimports, it works out the natural imported name
|
||||
// for a package.
|
||||
// This is used to get a shorter identifier in the compact stack trace
|
||||
func importPathToAssumedName(importPath string) string {
|
||||
base := path.Base(importPath)
|
||||
if strings.HasPrefix(base, "v") {
|
||||
if _, err := strconv.Atoi(base[1:]); err == nil {
|
||||
dir := path.Dir(importPath)
|
||||
if dir != "." {
|
||||
base = path.Base(dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
base = strings.TrimPrefix(base, "go-")
|
||||
if i := strings.IndexFunc(base, notIdentifier); i >= 0 {
|
||||
base = base[:i]
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func addSymbol(w io.Writer, frame *govulncheck.Frame, short bool) {
|
||||
if frame.Function == "" {
|
||||
return
|
||||
}
|
||||
if frame.Package != "" {
|
||||
pkg := frame.Package
|
||||
if short {
|
||||
pkg = importPathToAssumedName(frame.Package)
|
||||
}
|
||||
io.WriteString(w, pkg)
|
||||
io.WriteString(w, ".")
|
||||
}
|
||||
addSymbolName(w, frame)
|
||||
}
|
||||
|
||||
func addSymbolName(w io.Writer, frame *govulncheck.Frame) {
|
||||
if frame.Receiver != "" {
|
||||
if frame.Receiver[0] == '*' {
|
||||
io.WriteString(w, frame.Receiver[1:])
|
||||
} else {
|
||||
io.WriteString(w, frame.Receiver)
|
||||
}
|
||||
io.WriteString(w, ".")
|
||||
}
|
||||
funcname := strings.Split(frame.Function, "$")[0]
|
||||
io.WriteString(w, funcname)
|
||||
}
|
||||
575
vendor/golang.org/x/vuln/internal/scan/text.go
generated
vendored
Normal file
575
vendor/golang.org/x/vuln/internal/scan/text.go
generated
vendored
Normal file
@@ -0,0 +1,575 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/vuln/internal"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
"golang.org/x/vuln/internal/vulncheck"
|
||||
)
|
||||
|
||||
type style int
|
||||
|
||||
const (
|
||||
defaultStyle = style(iota)
|
||||
osvCalledStyle
|
||||
osvImportedStyle
|
||||
detailsStyle
|
||||
sectionStyle
|
||||
keyStyle
|
||||
valueStyle
|
||||
)
|
||||
|
||||
// NewtextHandler returns a handler that writes govulncheck output as text.
|
||||
func NewTextHandler(w io.Writer) *TextHandler {
|
||||
return &TextHandler{w: w}
|
||||
}
|
||||
|
||||
type TextHandler struct {
|
||||
w io.Writer
|
||||
sbom *govulncheck.SBOM
|
||||
osvs []*osv.Entry
|
||||
findings []*findingSummary
|
||||
scanLevel govulncheck.ScanLevel
|
||||
scanMode govulncheck.ScanMode
|
||||
|
||||
err error
|
||||
|
||||
showColor bool
|
||||
showTraces bool
|
||||
showVersion bool
|
||||
showVerbose bool
|
||||
}
|
||||
|
||||
const (
|
||||
detailsMessage = `For details, see https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck.`
|
||||
|
||||
binaryProgressMessage = `Scanning your binary for known vulnerabilities...`
|
||||
|
||||
noVulnsMessage = `No vulnerabilities found.`
|
||||
|
||||
noOtherVulnsMessage = `No other vulnerabilities found.`
|
||||
|
||||
verboseMessage = `'-show verbose' for more details`
|
||||
|
||||
symbolMessage = `'-scan symbol' for more fine grained vulnerability detection`
|
||||
)
|
||||
|
||||
func (h *TextHandler) Flush() error {
|
||||
if h.showVerbose {
|
||||
h.printSBOM()
|
||||
}
|
||||
if len(h.findings) == 0 {
|
||||
h.print(noVulnsMessage + "\n")
|
||||
} else {
|
||||
fixupFindings(h.osvs, h.findings)
|
||||
counters := h.allVulns(h.findings)
|
||||
h.summary(counters)
|
||||
}
|
||||
if h.err != nil {
|
||||
return h.err
|
||||
}
|
||||
// We found vulnerabilities when the findings' level matches the scan level.
|
||||
if (isCalled(h.findings) && h.scanLevel == govulncheck.ScanLevelSymbol) ||
|
||||
(isImported(h.findings) && h.scanLevel == govulncheck.ScanLevelPackage) ||
|
||||
(isRequired(h.findings) && h.scanLevel == govulncheck.ScanLevelModule) {
|
||||
return errVulnerabilitiesFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config writes version information only if --version was set.
|
||||
func (h *TextHandler) Config(config *govulncheck.Config) error {
|
||||
h.scanLevel = config.ScanLevel
|
||||
h.scanMode = config.ScanMode
|
||||
|
||||
if !h.showVersion {
|
||||
return nil
|
||||
}
|
||||
if config.GoVersion != "" {
|
||||
h.style(keyStyle, "Go: ")
|
||||
h.print(config.GoVersion, "\n")
|
||||
}
|
||||
if config.ScannerName != "" {
|
||||
h.style(keyStyle, "Scanner: ")
|
||||
h.print(config.ScannerName)
|
||||
if config.ScannerVersion != "" {
|
||||
h.print(`@`, config.ScannerVersion)
|
||||
}
|
||||
h.print("\n")
|
||||
}
|
||||
if config.DB != "" {
|
||||
h.style(keyStyle, "DB: ")
|
||||
h.print(config.DB, "\n")
|
||||
if config.DBLastModified != nil {
|
||||
h.style(keyStyle, "DB updated: ")
|
||||
h.print(*config.DBLastModified, "\n")
|
||||
}
|
||||
}
|
||||
h.print("\n")
|
||||
return h.err
|
||||
}
|
||||
|
||||
func (h *TextHandler) SBOM(sbom *govulncheck.SBOM) error {
|
||||
h.sbom = sbom
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TextHandler) printSBOM() error {
|
||||
if h.sbom == nil {
|
||||
h.print("No packages matched the provided pattern.\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
printed := false
|
||||
|
||||
for i, root := range h.sbom.Roots {
|
||||
if i == 0 {
|
||||
if len(h.sbom.Roots) > 1 {
|
||||
h.print("The package pattern matched the following ", len(h.sbom.Roots), " root packages:\n")
|
||||
} else {
|
||||
h.print("The package pattern matched the following root package:\n")
|
||||
}
|
||||
}
|
||||
|
||||
h.print(" ", root, "\n")
|
||||
printed = true
|
||||
}
|
||||
for i, mod := range h.sbom.Modules {
|
||||
if i == 0 && mod.Path != "stdlib" {
|
||||
h.print("Govulncheck scanned the following ", len(h.sbom.Modules)-1, " modules and the ", h.sbom.GoVersion, " standard library:\n")
|
||||
}
|
||||
|
||||
if mod.Path == "stdlib" {
|
||||
continue
|
||||
}
|
||||
|
||||
h.print(" ", mod.Path)
|
||||
if mod.Version != "" {
|
||||
h.print("@", mod.Version)
|
||||
}
|
||||
h.print("\n")
|
||||
printed = true
|
||||
}
|
||||
if printed {
|
||||
h.print("\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Progress writes progress updates during govulncheck execution.
|
||||
func (h *TextHandler) Progress(progress *govulncheck.Progress) error {
|
||||
if h.showVerbose {
|
||||
h.print(progress.Message, "\n\n")
|
||||
}
|
||||
return h.err
|
||||
}
|
||||
|
||||
// OSV gathers osv entries to be written.
|
||||
func (h *TextHandler) OSV(entry *osv.Entry) error {
|
||||
h.osvs = append(h.osvs, entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finding gathers vulnerability findings to be written.
|
||||
func (h *TextHandler) Finding(finding *govulncheck.Finding) error {
|
||||
if err := validateFindings(finding); err != nil {
|
||||
return err
|
||||
}
|
||||
h.findings = append(h.findings, newFindingSummary(finding))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TextHandler) allVulns(findings []*findingSummary) summaryCounters {
|
||||
byVuln := groupByVuln(findings)
|
||||
var called, imported, required [][]*findingSummary
|
||||
mods := map[string]struct{}{}
|
||||
stdlibCalled := false
|
||||
for _, findings := range byVuln {
|
||||
switch {
|
||||
case isCalled(findings):
|
||||
called = append(called, findings)
|
||||
if isStdFindings(findings) {
|
||||
stdlibCalled = true
|
||||
} else {
|
||||
mods[findings[0].Trace[0].Module] = struct{}{}
|
||||
}
|
||||
case isImported(findings):
|
||||
imported = append(imported, findings)
|
||||
default:
|
||||
required = append(required, findings)
|
||||
}
|
||||
}
|
||||
|
||||
if h.scanLevel.WantSymbols() {
|
||||
h.style(sectionStyle, "=== Symbol Results ===\n\n")
|
||||
if len(called) == 0 {
|
||||
h.print(noVulnsMessage, "\n\n")
|
||||
}
|
||||
for index, findings := range called {
|
||||
h.vulnerability(index, findings)
|
||||
}
|
||||
}
|
||||
|
||||
if h.scanLevel == govulncheck.ScanLevelPackage || (h.scanLevel.WantPackages() && h.showVerbose) {
|
||||
h.style(sectionStyle, "=== Package Results ===\n\n")
|
||||
if len(imported) == 0 {
|
||||
h.print(choose(!h.scanLevel.WantSymbols(), noVulnsMessage, noOtherVulnsMessage), "\n\n")
|
||||
}
|
||||
for index, findings := range imported {
|
||||
h.vulnerability(index, findings)
|
||||
}
|
||||
}
|
||||
|
||||
if h.showVerbose || h.scanLevel == govulncheck.ScanLevelModule {
|
||||
h.style(sectionStyle, "=== Module Results ===\n\n")
|
||||
if len(required) == 0 {
|
||||
h.print(choose(!h.scanLevel.WantPackages(), noVulnsMessage, noOtherVulnsMessage), "\n\n")
|
||||
}
|
||||
for index, findings := range required {
|
||||
h.vulnerability(index, findings)
|
||||
}
|
||||
}
|
||||
|
||||
return summaryCounters{
|
||||
VulnerabilitiesCalled: len(called),
|
||||
VulnerabilitiesImported: len(imported),
|
||||
VulnerabilitiesRequired: len(required),
|
||||
ModulesCalled: len(mods),
|
||||
StdlibCalled: stdlibCalled,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *TextHandler) vulnerability(index int, findings []*findingSummary) {
|
||||
h.style(keyStyle, "Vulnerability")
|
||||
h.print(" #", index+1, ": ")
|
||||
if isCalled(findings) {
|
||||
h.style(osvCalledStyle, findings[0].OSV.ID)
|
||||
} else {
|
||||
h.style(osvImportedStyle, findings[0].OSV.ID)
|
||||
}
|
||||
h.print("\n")
|
||||
h.style(detailsStyle)
|
||||
description := findings[0].OSV.Summary
|
||||
if description == "" {
|
||||
description = findings[0].OSV.Details
|
||||
}
|
||||
h.wrap(" ", description, 80)
|
||||
h.style(defaultStyle)
|
||||
h.print("\n")
|
||||
h.style(keyStyle, " More info:")
|
||||
h.print(" ", findings[0].OSV.DatabaseSpecific.URL, "\n")
|
||||
|
||||
byModule := groupByModule(findings)
|
||||
first := true
|
||||
for _, module := range byModule {
|
||||
// Note: there can be several findingSummaries for the same vulnerability
|
||||
// emitted during streaming for different scan levels.
|
||||
|
||||
// The module is same for all finding summaries.
|
||||
lastFrame := module[0].Trace[0]
|
||||
mod := lastFrame.Module
|
||||
// For stdlib, try to show package path as module name where
|
||||
// the scan level allows it.
|
||||
// TODO: should this be done in byModule as well?
|
||||
path := lastFrame.Module
|
||||
if stdPkg := h.pkg(module); path == internal.GoStdModulePath && stdPkg != "" {
|
||||
path = stdPkg
|
||||
}
|
||||
// All findings on a module are found and fixed at the same version
|
||||
foundVersion := moduleVersionString(lastFrame.Module, lastFrame.Version)
|
||||
fixedVersion := moduleVersionString(lastFrame.Module, module[0].FixedVersion)
|
||||
if !first {
|
||||
h.print("\n")
|
||||
}
|
||||
first = false
|
||||
h.print(" ")
|
||||
if mod == internal.GoStdModulePath {
|
||||
h.print("Standard library")
|
||||
} else {
|
||||
h.style(keyStyle, "Module: ")
|
||||
h.print(mod)
|
||||
}
|
||||
h.print("\n ")
|
||||
h.style(keyStyle, "Found in: ")
|
||||
h.print(path, "@", foundVersion, "\n ")
|
||||
h.style(keyStyle, "Fixed in: ")
|
||||
if fixedVersion != "" {
|
||||
h.print(path, "@", fixedVersion)
|
||||
} else {
|
||||
h.print("N/A")
|
||||
}
|
||||
h.print("\n")
|
||||
platforms := platforms(mod, module[0].OSV)
|
||||
if len(platforms) > 0 {
|
||||
h.style(keyStyle, " Platforms: ")
|
||||
for ip, p := range platforms {
|
||||
if ip > 0 {
|
||||
h.print(", ")
|
||||
}
|
||||
h.print(p)
|
||||
}
|
||||
h.print("\n")
|
||||
}
|
||||
h.traces(module)
|
||||
}
|
||||
h.print("\n")
|
||||
}
|
||||
|
||||
// pkg gives the package information for findings summaries
|
||||
// if one exists. This is only used to print package path
|
||||
// instead of a module for stdlib vulnerabilities at symbol
|
||||
// and package scan level.
|
||||
func (h *TextHandler) pkg(summaries []*findingSummary) string {
|
||||
for _, f := range summaries {
|
||||
if pkg := f.Trace[0].Package; pkg != "" {
|
||||
return pkg
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// traces prints out the most precise trace information
|
||||
// found in the given summaries.
|
||||
func (h *TextHandler) traces(traces []*findingSummary) {
|
||||
// Sort the traces by the vulnerable symbol. This
|
||||
// guarantees determinism since we are currently
|
||||
// showing only one trace per symbol.
|
||||
sort.SliceStable(traces, func(i, j int) bool {
|
||||
return symbol(traces[i].Trace[0], true) < symbol(traces[j].Trace[0], true)
|
||||
})
|
||||
|
||||
// compacts are finding summaries with compact traces
|
||||
// suitable for non-verbose textual output. Currently,
|
||||
// only traces produced by symbol analysis.
|
||||
var compacts []*findingSummary
|
||||
for _, t := range traces {
|
||||
if t.Compact != "" {
|
||||
compacts = append(compacts, t)
|
||||
}
|
||||
}
|
||||
|
||||
// binLimit is a limit on the number of binary traces
|
||||
// to show. Traces for binaries are less interesting
|
||||
// as users cannot act on them and they can hence
|
||||
// spam users.
|
||||
const binLimit = 5
|
||||
binary := h.scanMode == govulncheck.ScanModeBinary
|
||||
for i, entry := range compacts {
|
||||
if i == 0 {
|
||||
if binary {
|
||||
h.style(keyStyle, " Vulnerable symbols found:\n")
|
||||
} else {
|
||||
h.style(keyStyle, " Example traces found:\n")
|
||||
}
|
||||
}
|
||||
|
||||
// skip showing all symbols in binary mode unless '-show traces' is on.
|
||||
if binary && (i+1) > binLimit && !h.showTraces {
|
||||
h.print(" Use '-show traces' to see the other ", len(compacts)-binLimit, " found symbols\n")
|
||||
break
|
||||
}
|
||||
|
||||
h.print(" #", i+1, ": ")
|
||||
|
||||
if !h.showTraces { // show summarized traces
|
||||
h.print(entry.Compact, "\n")
|
||||
continue
|
||||
}
|
||||
|
||||
if binary {
|
||||
// There are no call stacks in binary mode
|
||||
// so just show the full symbol name.
|
||||
h.print(symbol(entry.Trace[0], false), "\n")
|
||||
} else {
|
||||
h.print("for function ", symbol(entry.Trace[0], false), "\n")
|
||||
for i := len(entry.Trace) - 1; i >= 0; i-- {
|
||||
t := entry.Trace[i]
|
||||
h.print(" ")
|
||||
h.print(symbolName(t))
|
||||
if t.Position != nil {
|
||||
h.print(" @ ", symbolPath(t))
|
||||
}
|
||||
h.print("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// symbolPath returns a user-friendly path to a symbol.
|
||||
func symbolPath(t *govulncheck.Frame) string {
|
||||
// Add module path prefix to symbol paths to be more
|
||||
// explicit to which module the symbols belong to.
|
||||
return t.Module + "/" + posToString(t.Position)
|
||||
}
|
||||
|
||||
func (h *TextHandler) summary(c summaryCounters) {
|
||||
// print short summary of findings identified at the desired level of scan precision
|
||||
var vulnCount int
|
||||
h.print("Your code ", choose(h.scanLevel.WantSymbols(), "is", "may be"), " affected by ")
|
||||
switch h.scanLevel {
|
||||
case govulncheck.ScanLevelSymbol:
|
||||
vulnCount = c.VulnerabilitiesCalled
|
||||
case govulncheck.ScanLevelPackage:
|
||||
vulnCount = c.VulnerabilitiesImported
|
||||
case govulncheck.ScanLevelModule:
|
||||
vulnCount = c.VulnerabilitiesRequired
|
||||
}
|
||||
h.style(valueStyle, vulnCount)
|
||||
h.print(choose(vulnCount == 1, ` vulnerability`, ` vulnerabilities`))
|
||||
if h.scanLevel.WantSymbols() {
|
||||
h.print(choose(c.ModulesCalled > 0 || c.StdlibCalled, ` from `, ``))
|
||||
if c.ModulesCalled > 0 {
|
||||
h.style(valueStyle, c.ModulesCalled)
|
||||
h.print(choose(c.ModulesCalled == 1, ` module`, ` modules`))
|
||||
}
|
||||
if c.StdlibCalled {
|
||||
if c.ModulesCalled != 0 {
|
||||
h.print(` and `)
|
||||
}
|
||||
h.print(`the Go standard library`)
|
||||
}
|
||||
}
|
||||
h.print(".\n")
|
||||
|
||||
// print summary for vulnerabilities found at other levels of scan precision
|
||||
if other := h.summaryOtherVulns(c); other != "" {
|
||||
h.wrap("", other, 80)
|
||||
h.print("\n")
|
||||
}
|
||||
|
||||
// print suggested flags for more/better info depending on scan level and if in verbose mode
|
||||
if sugg := h.summarySuggestion(); sugg != "" {
|
||||
h.wrap("", sugg, 80)
|
||||
h.print("\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *TextHandler) summaryOtherVulns(c summaryCounters) string {
|
||||
var summary strings.Builder
|
||||
if c.VulnerabilitiesRequired+c.VulnerabilitiesImported == 0 {
|
||||
summary.WriteString("This scan found no other vulnerabilities in ")
|
||||
if h.scanLevel.WantSymbols() {
|
||||
summary.WriteString("packages you import or ")
|
||||
}
|
||||
summary.WriteString("modules you require.")
|
||||
} else {
|
||||
summary.WriteString(choose(h.scanLevel.WantPackages(), "This scan also found ", ""))
|
||||
if h.scanLevel.WantSymbols() {
|
||||
summary.WriteString(fmt.Sprint(c.VulnerabilitiesImported))
|
||||
summary.WriteString(choose(c.VulnerabilitiesImported == 1, ` vulnerability `, ` vulnerabilities `))
|
||||
summary.WriteString("in packages you import and ")
|
||||
}
|
||||
if h.scanLevel.WantPackages() {
|
||||
summary.WriteString(fmt.Sprint(c.VulnerabilitiesRequired))
|
||||
summary.WriteString(choose(c.VulnerabilitiesRequired == 1, ` vulnerability `, ` vulnerabilities `))
|
||||
summary.WriteString("in modules you require")
|
||||
summary.WriteString(choose(h.scanLevel.WantSymbols(), ", but your code doesn't appear to call these vulnerabilities.", "."))
|
||||
}
|
||||
}
|
||||
return summary.String()
|
||||
}
|
||||
|
||||
func (h *TextHandler) summarySuggestion() string {
|
||||
var sugg strings.Builder
|
||||
switch h.scanLevel {
|
||||
case govulncheck.ScanLevelSymbol:
|
||||
if !h.showVerbose {
|
||||
sugg.WriteString("Use " + verboseMessage + ".")
|
||||
}
|
||||
case govulncheck.ScanLevelPackage:
|
||||
sugg.WriteString("Use " + symbolMessage)
|
||||
if !h.showVerbose {
|
||||
sugg.WriteString(" and " + verboseMessage)
|
||||
}
|
||||
sugg.WriteString(".")
|
||||
case govulncheck.ScanLevelModule:
|
||||
sugg.WriteString("Use " + symbolMessage + ".")
|
||||
}
|
||||
return sugg.String()
|
||||
}
|
||||
|
||||
func (h *TextHandler) style(style style, values ...any) {
|
||||
if h.showColor {
|
||||
switch style {
|
||||
default:
|
||||
h.print(colorReset)
|
||||
case osvCalledStyle:
|
||||
h.print(colorBold, fgRed)
|
||||
case osvImportedStyle:
|
||||
h.print(colorBold, fgGreen)
|
||||
case detailsStyle:
|
||||
h.print(colorFaint)
|
||||
case sectionStyle:
|
||||
h.print(fgBlue)
|
||||
case keyStyle:
|
||||
h.print(colorFaint, fgYellow)
|
||||
case valueStyle:
|
||||
h.print(colorBold, fgCyan)
|
||||
}
|
||||
}
|
||||
h.print(values...)
|
||||
if h.showColor && len(values) > 0 {
|
||||
h.print(colorReset)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *TextHandler) print(values ...any) int {
|
||||
total, w := 0, 0
|
||||
for _, v := range values {
|
||||
if h.err != nil {
|
||||
return total
|
||||
}
|
||||
// do we need to specialize for some types, like time?
|
||||
w, h.err = fmt.Fprint(h.w, v)
|
||||
total += w
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// wrap wraps s to fit in maxWidth by breaking it into lines at whitespace. If a
|
||||
// single word is longer than maxWidth, it is retained as its own line.
|
||||
func (h *TextHandler) wrap(indent string, s string, maxWidth int) {
|
||||
w := 0
|
||||
for _, f := range strings.Fields(s) {
|
||||
if w > 0 && w+len(f)+1 > maxWidth {
|
||||
// line would be too long with this word
|
||||
h.print("\n")
|
||||
w = 0
|
||||
}
|
||||
if w == 0 {
|
||||
// first field on line, indent
|
||||
w = h.print(indent)
|
||||
} else {
|
||||
// not first word, space separate
|
||||
w += h.print(" ")
|
||||
}
|
||||
// now write the word
|
||||
w += h.print(f)
|
||||
}
|
||||
}
|
||||
|
||||
func choose[t any](b bool, yes, no t) t {
|
||||
if b {
|
||||
return yes
|
||||
}
|
||||
return no
|
||||
}
|
||||
|
||||
func isStdFindings(findings []*findingSummary) bool {
|
||||
for _, f := range findings {
|
||||
if vulncheck.IsStdPackage(f.Trace[0].Package) || f.Trace[0].Module == internal.GoStdModulePath {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
60
vendor/golang.org/x/vuln/internal/scan/util.go
generated
vendored
Normal file
60
vendor/golang.org/x/vuln/internal/scan/util.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/vuln/internal"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
// validateFindings checks that the supplied findings all obey the protocol
|
||||
// rules.
|
||||
func validateFindings(findings ...*govulncheck.Finding) error {
|
||||
for _, f := range findings {
|
||||
if f.OSV == "" {
|
||||
return fmt.Errorf("invalid finding: all findings must have an associated OSV")
|
||||
}
|
||||
if len(f.Trace) < 1 {
|
||||
return fmt.Errorf("invalid finding: all callstacks must have at least one frame")
|
||||
}
|
||||
for _, frame := range f.Trace {
|
||||
if frame.Version != "" && frame.Module == "" {
|
||||
return fmt.Errorf("invalid finding: if Frame.Version (%s) is set, Frame.Module must also be", frame.Version)
|
||||
}
|
||||
if frame.Package != "" && frame.Module == "" {
|
||||
return fmt.Errorf("invalid finding: if Frame.Package (%s) is set, Frame.Module must also be", frame.Package)
|
||||
}
|
||||
if frame.Function != "" && frame.Package == "" {
|
||||
return fmt.Errorf("invalid finding: if Frame.Function (%s) is set, Frame.Package must also be", frame.Function)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func moduleVersionString(modulePath, version string) string {
|
||||
if version == "" {
|
||||
return ""
|
||||
}
|
||||
if modulePath == internal.GoStdModulePath || modulePath == internal.GoCmdModulePath {
|
||||
version = semverToGoTag(version)
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
func gomodExists(dir string) bool {
|
||||
cmd := exec.Command("go", "env", "GOMOD")
|
||||
cmd.Dir = dir
|
||||
out, err := cmd.Output()
|
||||
output := strings.TrimSpace(string(out))
|
||||
// If module-aware mode is enabled, but there is no go.mod, GOMOD will be os.DevNull
|
||||
// If module-aware mode is disabled, GOMOD will be the empty string.
|
||||
return err == nil && !(output == os.DevNull || output == "")
|
||||
}
|
||||
94
vendor/golang.org/x/vuln/internal/semver/affects.go
generated
vendored
Normal file
94
vendor/golang.org/x/vuln/internal/semver/affects.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
package semver
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
)
|
||||
|
||||
func Affects(a []osv.Range, v string) bool {
|
||||
if len(a) == 0 {
|
||||
// No ranges implies all versions are affected
|
||||
return true
|
||||
}
|
||||
var semverRangePresent bool
|
||||
for _, r := range a {
|
||||
if r.Type != osv.RangeTypeSemver {
|
||||
continue
|
||||
}
|
||||
semverRangePresent = true
|
||||
if ContainsSemver(r, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// If there were no semver ranges present we
|
||||
// assume that all semvers are affected, similarly
|
||||
// to how to we assume all semvers are affected
|
||||
// if there are no ranges at all.
|
||||
return !semverRangePresent
|
||||
}
|
||||
|
||||
// ContainsSemver checks if semver version v is in the
|
||||
// range encoded by ar. If ar is not a semver range,
|
||||
// returns false. A range is interpreted as a left-closed
|
||||
// and right-open interval.
|
||||
//
|
||||
// Assumes that
|
||||
// - exactly one of Introduced or Fixed fields is set
|
||||
// - ranges in ar are not overlapping
|
||||
// - beginning of time is encoded with .Introduced="0"
|
||||
// - no-fix is not an event, as opposed to being an
|
||||
// event where Introduced="" and Fixed=""
|
||||
func ContainsSemver(ar osv.Range, v string) bool {
|
||||
if ar.Type != osv.RangeTypeSemver {
|
||||
return false
|
||||
}
|
||||
if len(ar.Events) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Strip and then add the semver prefix so we can support bare versions,
|
||||
// versions prefixed with 'v', and versions prefixed with 'go'.
|
||||
v = canonicalizeSemverPrefix(v)
|
||||
|
||||
// Sort events by semver versions. Event for beginning
|
||||
// of time, if present, always comes first.
|
||||
sort.SliceStable(ar.Events, func(i, j int) bool {
|
||||
e1 := ar.Events[i]
|
||||
v1 := e1.Introduced
|
||||
if v1 == "0" {
|
||||
// -inf case.
|
||||
return true
|
||||
}
|
||||
if e1.Fixed != "" {
|
||||
v1 = e1.Fixed
|
||||
}
|
||||
|
||||
e2 := ar.Events[j]
|
||||
v2 := e2.Introduced
|
||||
if v2 == "0" {
|
||||
// -inf case.
|
||||
return false
|
||||
}
|
||||
if e2.Fixed != "" {
|
||||
v2 = e2.Fixed
|
||||
}
|
||||
|
||||
return Less(v1, v2)
|
||||
})
|
||||
|
||||
var affected bool
|
||||
for _, e := range ar.Events {
|
||||
if !affected && e.Introduced != "" {
|
||||
affected = e.Introduced == "0" || !Less(v, e.Introduced)
|
||||
} else if affected && e.Fixed != "" {
|
||||
affected = Less(v, e.Fixed)
|
||||
}
|
||||
}
|
||||
|
||||
return affected
|
||||
}
|
||||
36
vendor/golang.org/x/vuln/internal/semver/fixed.go
generated
vendored
Normal file
36
vendor/golang.org/x/vuln/internal/semver/fixed.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
package semver
|
||||
|
||||
import "golang.org/x/vuln/internal/osv"
|
||||
|
||||
// NonSupersededFix returns a fixed version from ranges
|
||||
// that is not superseded by any other fix or any other
|
||||
// introduction of a vulnerability. Returns "" in case
|
||||
// there is no such fixed version.
|
||||
func NonSupersededFix(ranges []osv.Range) string {
|
||||
var latestFixed string
|
||||
for _, r := range ranges {
|
||||
if r.Type == "SEMVER" {
|
||||
for _, e := range r.Events {
|
||||
fixed := e.Fixed
|
||||
if fixed != "" && Less(latestFixed, fixed) {
|
||||
latestFixed = fixed
|
||||
}
|
||||
}
|
||||
|
||||
// If the vulnerability was re-introduced after the latest fix
|
||||
// we found, there is no latest fix for this range.
|
||||
for _, e := range r.Events {
|
||||
introduced := e.Introduced
|
||||
if introduced != "" && introduced != "0" && Less(latestFixed, introduced) {
|
||||
latestFixed = ""
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return latestFixed
|
||||
}
|
||||
140
vendor/golang.org/x/vuln/internal/semver/semver.go
generated
vendored
Normal file
140
vendor/golang.org/x/vuln/internal/semver/semver.go
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
// Package semver provides shared utilities for manipulating
|
||||
// Go semantic versions.
|
||||
package semver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// addSemverPrefix adds a 'v' prefix to s if it isn't already prefixed
|
||||
// with 'v' or 'go'. This allows us to easily test go-style SEMVER
|
||||
// strings against normal SEMVER strings.
|
||||
func addSemverPrefix(s string) string {
|
||||
if !strings.HasPrefix(s, "v") && !strings.HasPrefix(s, "go") {
|
||||
return "v" + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// removeSemverPrefix removes the 'v' or 'go' prefixes from go-style
|
||||
// SEMVER strings, for usage in the public vulnerability format.
|
||||
func removeSemverPrefix(s string) string {
|
||||
s = strings.TrimPrefix(s, "v")
|
||||
s = strings.TrimPrefix(s, "go")
|
||||
return s
|
||||
}
|
||||
|
||||
// canonicalizeSemverPrefix turns a SEMVER string into the canonical
|
||||
// representation using the 'v' prefix, as used by the OSV format.
|
||||
// Input may be a bare SEMVER ("1.2.3"), Go prefixed SEMVER ("go1.2.3"),
|
||||
// or already canonical SEMVER ("v1.2.3").
|
||||
func canonicalizeSemverPrefix(s string) string {
|
||||
return addSemverPrefix(removeSemverPrefix(s))
|
||||
}
|
||||
|
||||
// Less returns whether v1 < v2, where v1 and v2 are
|
||||
// semver versions with either a "v", "go" or no prefix.
|
||||
func Less(v1, v2 string) bool {
|
||||
return semver.Compare(canonicalizeSemverPrefix(v1), canonicalizeSemverPrefix(v2)) < 0
|
||||
}
|
||||
|
||||
// Valid returns whether v is valid semver, allowing
|
||||
// either a "v", "go" or no prefix.
|
||||
func Valid(v string) bool {
|
||||
return semver.IsValid(canonicalizeSemverPrefix(v))
|
||||
}
|
||||
|
||||
var (
|
||||
// Regexp for matching go tags. The groups are:
|
||||
// 1 the major.minor version
|
||||
// 2 the patch version, or empty if none
|
||||
// 3 the entire prerelease, if present
|
||||
// 4 the prerelease type ("beta" or "rc")
|
||||
// 5 the prerelease number
|
||||
tagRegexp = regexp.MustCompile(`^go(\d+\.\d+)(\.\d+|)((beta|rc|-pre)(\d+))?$`)
|
||||
)
|
||||
|
||||
// This is a modified copy of pkgsite/internal/stdlib:VersionForTag.
|
||||
func GoTagToSemver(tag string) string {
|
||||
if tag == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
tag = strings.Fields(tag)[0]
|
||||
// Special cases for go1.
|
||||
if tag == "go1" {
|
||||
return "v1.0.0"
|
||||
}
|
||||
if tag == "go1.0" {
|
||||
return ""
|
||||
}
|
||||
m := tagRegexp.FindStringSubmatch(tag)
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
version := "v" + m[1]
|
||||
if m[2] != "" {
|
||||
version += m[2]
|
||||
} else {
|
||||
version += ".0"
|
||||
}
|
||||
if m[3] != "" {
|
||||
if !strings.HasPrefix(m[4], "-") {
|
||||
version += "-"
|
||||
}
|
||||
version += m[4] + "." + m[5]
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
// This is a modified copy of pkgsite/internal/stlib:TagForVersion
|
||||
func SemverToGoTag(v string) string {
|
||||
// Special case: v1.0.0 => go1.
|
||||
if v == "v1.0.0" {
|
||||
return "go1"
|
||||
}
|
||||
|
||||
goVersion := semver.Canonical(v)
|
||||
prerelease := semver.Prerelease(goVersion)
|
||||
versionWithoutPrerelease := strings.TrimSuffix(goVersion, prerelease)
|
||||
patch := strings.TrimPrefix(versionWithoutPrerelease, semver.MajorMinor(goVersion)+".")
|
||||
if patch == "0" && (semver.Compare(v, "v1.21.0") < 0 || prerelease != "") {
|
||||
// Starting with go1.21.0, the first patch version includes .0.
|
||||
// Prereleases do not include .0 (we don't do prereleases for other patch releases).
|
||||
versionWithoutPrerelease = strings.TrimSuffix(versionWithoutPrerelease, ".0")
|
||||
}
|
||||
goVersion = fmt.Sprintf("go%s", strings.TrimPrefix(versionWithoutPrerelease, "v"))
|
||||
if prerelease != "" {
|
||||
i := finalDigitsIndex(prerelease)
|
||||
if i >= 1 {
|
||||
// Remove the dot.
|
||||
prerelease = prerelease[:i-1] + prerelease[i:]
|
||||
}
|
||||
goVersion += prerelease
|
||||
}
|
||||
return goVersion
|
||||
}
|
||||
|
||||
// finalDigitsIndex returns the index of the first digit in the sequence of digits ending s.
|
||||
// If s doesn't end in digits, it returns -1.
|
||||
func finalDigitsIndex(s string) int {
|
||||
// Assume ASCII (since the semver package does anyway).
|
||||
var i int
|
||||
for i = len(s) - 1; i >= 0; i-- {
|
||||
if s[i] < '0' || s[i] > '9' {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i == len(s)-1 {
|
||||
return -1
|
||||
}
|
||||
return i + 1
|
||||
}
|
||||
46
vendor/golang.org/x/vuln/internal/traces/traces.go
generated
vendored
Normal file
46
vendor/golang.org/x/vuln/internal/traces/traces.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
package traces
|
||||
|
||||
import (
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
// Compact returns a summarization of finding.Trace. The first
|
||||
// returned element is the vulnerable symbol and the last element
|
||||
// is the exit point of the user module. There can also be two
|
||||
// elements in between, if applicable, which are the two elements
|
||||
// preceding the user module exit point.
|
||||
func Compact(finding *govulncheck.Finding) []*govulncheck.Frame {
|
||||
if len(finding.Trace) < 1 {
|
||||
return nil
|
||||
}
|
||||
iTop := len(finding.Trace) - 1
|
||||
topModule := finding.Trace[iTop].Module
|
||||
// search for the exit point of the top module
|
||||
for i, frame := range finding.Trace {
|
||||
if frame.Module == topModule {
|
||||
iTop = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if iTop == 0 {
|
||||
// all in one module, reset to the end
|
||||
iTop = len(finding.Trace) - 1
|
||||
}
|
||||
|
||||
compact := []*govulncheck.Frame{finding.Trace[0]}
|
||||
if iTop > 1 {
|
||||
if iTop > 2 {
|
||||
compact = append(compact, finding.Trace[iTop-2])
|
||||
}
|
||||
compact = append(compact, finding.Trace[iTop-1])
|
||||
}
|
||||
if iTop > 0 {
|
||||
compact = append(compact, finding.Trace[iTop])
|
||||
}
|
||||
return compact
|
||||
}
|
||||
237
vendor/golang.org/x/vuln/internal/vulncheck/binary.go
generated
vendored
Normal file
237
vendor/golang.org/x/vuln/internal/vulncheck/binary.go
generated
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
// 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.
|
||||
|
||||
package vulncheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
"golang.org/x/vuln/internal/semver"
|
||||
)
|
||||
|
||||
// Bin is an abstraction of Go binary containing
|
||||
// minimal information needed by govulncheck.
|
||||
type Bin struct {
|
||||
// Path of the main package.
|
||||
Path string `json:"path,omitempty"`
|
||||
// Main module. When present, it never has empty information.
|
||||
Main *packages.Module `json:"main,omitempty"`
|
||||
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)
|
||||
mods := append(bin.Modules, graph.GetModule(internal.GoStdModulePath))
|
||||
|
||||
if bin.Main != nil {
|
||||
mods = append(mods, bin.Main)
|
||||
}
|
||||
|
||||
graph.AddModules(mods...)
|
||||
|
||||
if err := handler.SBOM(bin.SBOM()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := handler.Progress(&govulncheck.Progress{Message: fetchingVulnsMessage}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 err := handler.Progress(&govulncheck.Progress{Message: checkingBinVulnsMessage}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Emit warning message for ancient Go binaries, defined as binaries
|
||||
// built with Go version without support for debug.BuildInfo (< go1.18).
|
||||
if semver.Valid(bin.GoVersion) && semver.Less(bin.GoVersion, "go1.18") {
|
||||
p := &govulncheck.Progress{Message: fmt.Sprintf("warning: binary built with Go version %s, only standard library vulnerabilities will be checked", bin.GoVersion)}
|
||||
if err := handler.Progress(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if bin.GOOS == "" || bin.GOARCH == "" {
|
||||
p := &govulncheck.Progress{Message: fmt.Sprintf("warning: failed to extract build system specification GOOS: %s GOARCH: %s\n", bin.GOOS, bin.GOARCH)}
|
||||
if err := handler.Progress(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
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 = packagesAndSymbols(bin)
|
||||
}
|
||||
|
||||
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 packagesAndSymbols(bin *Bin) map[string][]string {
|
||||
pkgSymbols := make(map[string][]string)
|
||||
for _, sym := range bin.PkgSymbols {
|
||||
// If the name of the package is main, we need to expand
|
||||
// it to its full path as that is what vuln db uses.
|
||||
if sym.Pkg == "main" && bin.Path != "" {
|
||||
pkgSymbols[bin.Path] = append(pkgSymbols[bin.Path], sym.Name)
|
||||
} else {
|
||||
pkgSymbols[sym.Pkg] = append(pkgSymbols[sym.Pkg], sym.Name)
|
||||
}
|
||||
}
|
||||
return pkgSymbols
|
||||
}
|
||||
|
||||
func binImportedVulnPackages(graph *PackageGraph, pkgSymbols map[string][]string, affVulns affectingVulns) []*Vuln {
|
||||
var vulns []*Vuln
|
||||
for pkg := range pkgSymbols {
|
||||
for _, osv := range affVulns.ForPackage(internal.UnknownModulePath, 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 {
|
||||
for _, symbol := range symbols {
|
||||
for _, osv := range affVulns.ForSymbol(internal.UnknownModulePath, 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
|
||||
}
|
||||
|
||||
func (bin *Bin) SBOM() (sbom *govulncheck.SBOM) {
|
||||
sbom = &govulncheck.SBOM{}
|
||||
if bin.Main != nil {
|
||||
sbom.Roots = []string{bin.Main.Path}
|
||||
sbom.Modules = append(sbom.Modules, &govulncheck.Module{
|
||||
Path: bin.Main.Path,
|
||||
Version: bin.Main.Version,
|
||||
})
|
||||
}
|
||||
|
||||
sbom.GoVersion = bin.GoVersion
|
||||
for _, mod := range bin.Modules {
|
||||
if mod.Replace != nil {
|
||||
mod = mod.Replace
|
||||
}
|
||||
sbom.Modules = append(sbom.Modules, &govulncheck.Module{
|
||||
Path: mod.Path,
|
||||
Version: mod.Version,
|
||||
})
|
||||
}
|
||||
|
||||
// add stdlib to mirror source mode output
|
||||
sbom.Modules = append(sbom.Modules, &govulncheck.Module{
|
||||
Path: internal.GoStdModulePath,
|
||||
Version: bin.GoVersion,
|
||||
})
|
||||
|
||||
return sbom
|
||||
}
|
||||
54
vendor/golang.org/x/vuln/internal/vulncheck/doc.go
generated
vendored
Normal file
54
vendor/golang.org/x/vuln/internal/vulncheck/doc.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
/*
|
||||
Package vulncheck detects uses of known vulnerabilities
|
||||
in Go programs.
|
||||
|
||||
Vulncheck identifies vulnerability uses in Go programs
|
||||
at the level of call graph, package import graph, and module
|
||||
requires graph. For instance, vulncheck identifies which
|
||||
vulnerable functions and methods are transitively called
|
||||
from the program entry points. vulncheck also detects
|
||||
transitively imported packages and required modules that
|
||||
contain known vulnerable functions and methods.
|
||||
|
||||
We recommend using the command line tool [govulncheck] to
|
||||
detect vulnerabilities in your code.
|
||||
|
||||
# Usage
|
||||
|
||||
The two main APIs of vulncheck, [Source] and [Binary], allow vulnerability
|
||||
detection in Go source code and binaries, respectively.
|
||||
|
||||
[Source] accepts a list of [Package] objects, which
|
||||
are a trimmed version of [golang.org/x/tools/go/packages.Package] objects to
|
||||
reduce memory consumption. [Binary] accepts a path to a Go binary file.
|
||||
|
||||
Both [Source] and [Binary] require information about known
|
||||
vulnerabilities in the form of a vulnerability database,
|
||||
specifically a [golang.org/x/vuln/internal/client.Client].
|
||||
The vulnerabilities
|
||||
are modeled using the [golang.org/x/vuln/internal/osv] format.
|
||||
|
||||
# Results
|
||||
|
||||
The results of vulncheck are slices of the call graph, package imports graph,
|
||||
and module requires graph leading to the use of an identified vulnerability.
|
||||
The parts of these graphs not related to any vulnerabilities are omitted.
|
||||
|
||||
The [CallStacks] and [ImportChains] functions search the returned slices for
|
||||
user-friendly representative call stacks and import chains. These call stacks
|
||||
and import chains are provided as examples of vulnerability uses in the client
|
||||
code.
|
||||
|
||||
# Limitations
|
||||
|
||||
There are some limitations with vulncheck. Please see the
|
||||
[documented limitations] for more information.
|
||||
|
||||
[govulncheck]: https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck
|
||||
[documented limitations]: https://go.dev/security/vulncheck#limitations.
|
||||
*/
|
||||
package vulncheck
|
||||
198
vendor/golang.org/x/vuln/internal/vulncheck/emit.go
generated
vendored
Normal file
198
vendor/golang.org/x/vuln/internal/vulncheck/emit.go
generated
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
package vulncheck
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
// emitOSVs emits all OSV vuln entries in modVulns to handler.
|
||||
func emitOSVs(handler govulncheck.Handler, modVulns []*ModVulns) error {
|
||||
for _, mv := range modVulns {
|
||||
for _, v := range mv.Vulns {
|
||||
if err := handler.OSV(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// emitModuleFindings emits module-level findings for vulnerabilities in modVulns.
|
||||
func emitModuleFindings(handler govulncheck.Handler, affVulns affectingVulns) error {
|
||||
for _, vuln := range affVulns {
|
||||
for _, osv := range vuln.Vulns {
|
||||
if err := handler.Finding(&govulncheck.Finding{
|
||||
OSV: osv.ID,
|
||||
FixedVersion: FixedVersion(modPath(vuln.Module), modVersion(vuln.Module), osv.Affected),
|
||||
Trace: []*govulncheck.Frame{frameFromModule(vuln.Module)},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// emitPackageFinding emits package-level findings fod vulnerabilities in vulns.
|
||||
func emitPackageFindings(handler govulncheck.Handler, vulns []*Vuln) error {
|
||||
for _, v := range vulns {
|
||||
if err := handler.Finding(&govulncheck.Finding{
|
||||
OSV: v.OSV.ID,
|
||||
FixedVersion: FixedVersion(modPath(v.Package.Module), modVersion(v.Package.Module), v.OSV.Affected),
|
||||
Trace: []*govulncheck.Frame{frameFromPackage(v.Package)},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// emitCallFindings emits call-level findings for vulnerabilities
|
||||
// that have a call stack in callstacks.
|
||||
func emitCallFindings(handler govulncheck.Handler, callstacks map[*Vuln]CallStack) error {
|
||||
var vulns []*Vuln
|
||||
for v := range callstacks {
|
||||
vulns = append(vulns, v)
|
||||
}
|
||||
|
||||
for _, vuln := range vulns {
|
||||
stack := callstacks[vuln]
|
||||
if stack == nil {
|
||||
continue
|
||||
}
|
||||
fixed := FixedVersion(modPath(vuln.Package.Module), modVersion(vuln.Package.Module), vuln.OSV.Affected)
|
||||
if err := handler.Finding(&govulncheck.Finding{
|
||||
OSV: vuln.OSV.ID,
|
||||
FixedVersion: fixed,
|
||||
Trace: traceFromEntries(stack),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// traceFromEntries creates a sequence of
|
||||
// frames from vcs. Position of a Frame is the
|
||||
// call position of the corresponding stack entry.
|
||||
func traceFromEntries(vcs CallStack) []*govulncheck.Frame {
|
||||
var frames []*govulncheck.Frame
|
||||
for i := len(vcs) - 1; i >= 0; i-- {
|
||||
e := vcs[i]
|
||||
fr := frameFromPackage(e.Function.Package)
|
||||
fr.Function = e.Function.Name
|
||||
fr.Receiver = e.Function.Receiver()
|
||||
isSink := i == (len(vcs) - 1)
|
||||
fr.Position = posFromStackEntry(e, isSink)
|
||||
frames = append(frames, fr)
|
||||
}
|
||||
return frames
|
||||
}
|
||||
|
||||
func posFromStackEntry(e StackEntry, sink bool) *govulncheck.Position {
|
||||
var p *token.Position
|
||||
var f *FuncNode
|
||||
if sink && e.Function != nil && e.Function.Pos != nil {
|
||||
// For sinks, i.e., vulns we take the position
|
||||
// of the symbol.
|
||||
p = e.Function.Pos
|
||||
f = e.Function
|
||||
} else if e.Call != nil && e.Call.Pos != nil {
|
||||
// Otherwise, we take the position of
|
||||
// the call statement.
|
||||
p = e.Call.Pos
|
||||
f = e.Call.Parent
|
||||
}
|
||||
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return &govulncheck.Position{
|
||||
Filename: pathRelativeToMod(p.Filename, f),
|
||||
Offset: p.Offset,
|
||||
Line: p.Line,
|
||||
Column: p.Column,
|
||||
}
|
||||
}
|
||||
|
||||
// pathRelativeToMod computes a version of path
|
||||
// relative to the module of f. If it does not
|
||||
// have all the necessary information, returns
|
||||
// an empty string.
|
||||
//
|
||||
// The returned paths always use slash as separator
|
||||
// so they can work across different platforms.
|
||||
func pathRelativeToMod(path string, f *FuncNode) string {
|
||||
if path == "" || f == nil || f.Package == nil { // sanity
|
||||
return ""
|
||||
}
|
||||
|
||||
mod := f.Package.Module
|
||||
if mod.Replace != nil {
|
||||
mod = mod.Replace // for replace directive
|
||||
}
|
||||
|
||||
modDir := modDirWithVendor(mod.Dir, path, mod.Path)
|
||||
p, err := filepath.Rel(modDir, path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// make sure paths are portable.
|
||||
return filepath.ToSlash(p)
|
||||
}
|
||||
|
||||
// modDirWithVendor returns modDir if modDir is not empty.
|
||||
// Otherwise, the module might be located in the vendor
|
||||
// directory. This function attempts to reconstruct the
|
||||
// vendored module directory from path and module. It
|
||||
// returns an empty string if reconstruction fails.
|
||||
func modDirWithVendor(modDir, path, module string) string {
|
||||
if modDir != "" {
|
||||
return modDir
|
||||
}
|
||||
|
||||
sep := string(os.PathSeparator)
|
||||
vendor := sep + "vendor" + sep
|
||||
vendorIndex := strings.Index(path, vendor)
|
||||
if vendorIndex == -1 {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(path[:vendorIndex], "vendor", filepath.FromSlash(module))
|
||||
}
|
||||
|
||||
func frameFromPackage(pkg *packages.Package) *govulncheck.Frame {
|
||||
fr := &govulncheck.Frame{}
|
||||
if pkg != nil {
|
||||
fr.Module = pkg.Module.Path
|
||||
fr.Version = pkg.Module.Version
|
||||
fr.Package = pkg.PkgPath
|
||||
}
|
||||
if pkg.Module.Replace != nil {
|
||||
fr.Module = pkg.Module.Replace.Path
|
||||
fr.Version = pkg.Module.Replace.Version
|
||||
}
|
||||
return fr
|
||||
}
|
||||
|
||||
func frameFromModule(mod *packages.Module) *govulncheck.Frame {
|
||||
fr := &govulncheck.Frame{
|
||||
Module: mod.Path,
|
||||
Version: mod.Version,
|
||||
}
|
||||
|
||||
if mod.Replace != nil {
|
||||
fr.Module = mod.Replace.Path
|
||||
fr.Version = mod.Replace.Version
|
||||
}
|
||||
|
||||
return fr
|
||||
}
|
||||
56
vendor/golang.org/x/vuln/internal/vulncheck/entries.go
generated
vendored
Normal file
56
vendor/golang.org/x/vuln/internal/vulncheck/entries.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
// 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.
|
||||
|
||||
package vulncheck
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// entryPoints returns functions of topPackages considered entry
|
||||
// points of govulncheck analysis: main, inits, and exported methods
|
||||
// and functions.
|
||||
//
|
||||
// TODO(https://go.dev/issue/57221): currently, entry functions
|
||||
// that are generics are not considered an entry point.
|
||||
func entryPoints(topPackages []*ssa.Package) []*ssa.Function {
|
||||
var entries []*ssa.Function
|
||||
for _, pkg := range topPackages {
|
||||
if pkg.Pkg.Name() == "main" {
|
||||
// for "main" packages the only valid entry points are the "main"
|
||||
// function and any "init#" functions, even if there are other
|
||||
// exported functions or types. similarly to isEntry it should be
|
||||
// safe to ignore the validity of the main or init# signatures,
|
||||
// since the compiler will reject malformed definitions,
|
||||
// and the init function is synthetic
|
||||
entries = append(entries, memberFuncs(pkg.Members["main"], pkg.Prog)...)
|
||||
for name, member := range pkg.Members {
|
||||
if strings.HasPrefix(name, "init#") || name == "init" {
|
||||
entries = append(entries, memberFuncs(member, pkg.Prog)...)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
for _, member := range pkg.Members {
|
||||
for _, f := range memberFuncs(member, pkg.Prog) {
|
||||
if isEntry(f) {
|
||||
entries = append(entries, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
func isEntry(f *ssa.Function) bool {
|
||||
// it should be safe to ignore checking that the signature of the "init" function
|
||||
// is valid, since it is synthetic
|
||||
if f.Name() == "init" && f.Synthetic == "package initializer" {
|
||||
return true
|
||||
}
|
||||
|
||||
return f.Synthetic == "" && f.Object() != nil && f.Object().Exported()
|
||||
}
|
||||
42
vendor/golang.org/x/vuln/internal/vulncheck/fetch.go
generated
vendored
Normal file
42
vendor/golang.org/x/vuln/internal/vulncheck/fetch.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// 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.
|
||||
|
||||
package vulncheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/vuln/internal/client"
|
||||
)
|
||||
|
||||
// FetchVulnerabilities fetches vulnerabilities that affect the supplied modules.
|
||||
func FetchVulnerabilities(ctx context.Context, c *client.Client, modules []*packages.Module) ([]*ModVulns, error) {
|
||||
mreqs := make([]*client.ModuleRequest, len(modules))
|
||||
for i, mod := range modules {
|
||||
modPath := mod.Path
|
||||
if mod.Replace != nil {
|
||||
modPath = mod.Replace.Path
|
||||
}
|
||||
mreqs[i] = &client.ModuleRequest{
|
||||
Path: modPath,
|
||||
}
|
||||
}
|
||||
resps, err := c.ByModules(ctx, mreqs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching vulnerabilities: %v", err)
|
||||
}
|
||||
var mv []*ModVulns
|
||||
for i, resp := range resps {
|
||||
if len(resp.Entries) == 0 {
|
||||
continue
|
||||
}
|
||||
mv = append(mv, &ModVulns{
|
||||
Module: modules[i],
|
||||
Vulns: resp.Entries,
|
||||
})
|
||||
}
|
||||
return mv, nil
|
||||
}
|
||||
318
vendor/golang.org/x/vuln/internal/vulncheck/packages.go
generated
vendored
Normal file
318
vendor/golang.org/x/vuln/internal/vulncheck/packages.go
generated
vendored
Normal file
@@ -0,0 +1,318 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
package vulncheck
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/vuln/internal"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/semver"
|
||||
)
|
||||
|
||||
// PackageGraph holds a complete module and package graph.
|
||||
// Its primary purpose is to allow fast access to the nodes
|
||||
// by path and make sure all(stdlib) packages have a module.
|
||||
type PackageGraph struct {
|
||||
// topPkgs are top-level packages specified by the user.
|
||||
// Empty in binary mode.
|
||||
topPkgs []*packages.Package
|
||||
modules map[string]*packages.Module // all modules (even replacing ones)
|
||||
packages map[string]*packages.Package // all packages (even dependencies)
|
||||
}
|
||||
|
||||
func NewPackageGraph(goVersion string) *PackageGraph {
|
||||
graph := &PackageGraph{
|
||||
modules: map[string]*packages.Module{},
|
||||
packages: map[string]*packages.Package{},
|
||||
}
|
||||
|
||||
goRoot := ""
|
||||
if out, err := exec.Command("go", "env", "GOROOT").Output(); err == nil {
|
||||
goRoot = strings.TrimSpace(string(out))
|
||||
}
|
||||
stdlibModule := &packages.Module{
|
||||
Path: internal.GoStdModulePath,
|
||||
Version: semver.GoTagToSemver(goVersion),
|
||||
Dir: goRoot,
|
||||
}
|
||||
graph.AddModules(stdlibModule)
|
||||
return graph
|
||||
}
|
||||
|
||||
func (g *PackageGraph) TopPkgs() []*packages.Package {
|
||||
return g.topPkgs
|
||||
}
|
||||
|
||||
// DepPkgs returns the number of packages that graph.TopPkgs()
|
||||
// strictly depend on. This does not include topPkgs even if
|
||||
// they are dependency of each other.
|
||||
func (g *PackageGraph) DepPkgs() []*packages.Package {
|
||||
topPkgs := g.TopPkgs()
|
||||
tops := make(map[string]bool)
|
||||
depPkgs := make(map[string]*packages.Package)
|
||||
|
||||
for _, t := range topPkgs {
|
||||
tops[t.PkgPath] = true
|
||||
}
|
||||
|
||||
var visit func(*packages.Package, bool)
|
||||
visit = func(p *packages.Package, top bool) {
|
||||
path := p.PkgPath
|
||||
if _, ok := depPkgs[path]; ok {
|
||||
return
|
||||
}
|
||||
if tops[path] && !top {
|
||||
// A top package that is a dependency
|
||||
// will not be in depPkgs, so we skip
|
||||
// reiterating on it here.
|
||||
return
|
||||
}
|
||||
|
||||
// We don't count a top-level package as
|
||||
// a dependency even when they are used
|
||||
// as a dependent package.
|
||||
if !tops[path] {
|
||||
depPkgs[path] = p
|
||||
}
|
||||
|
||||
for _, d := range p.Imports {
|
||||
visit(d, false)
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range topPkgs {
|
||||
visit(t, true)
|
||||
}
|
||||
|
||||
var deps []*packages.Package
|
||||
for _, d := range depPkgs {
|
||||
deps = append(deps, g.GetPackage(d.PkgPath))
|
||||
}
|
||||
return deps
|
||||
}
|
||||
|
||||
func (g *PackageGraph) Modules() []*packages.Module {
|
||||
var mods []*packages.Module
|
||||
for _, m := range g.modules {
|
||||
mods = append(mods, m)
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
// AddModules adds the modules and any replace modules provided.
|
||||
// It will ignore modules that have duplicate paths to ones the
|
||||
// graph already holds.
|
||||
func (g *PackageGraph) AddModules(mods ...*packages.Module) {
|
||||
for _, mod := range mods {
|
||||
if _, found := g.modules[mod.Path]; found {
|
||||
//TODO: check duplicates are okay?
|
||||
continue
|
||||
}
|
||||
g.modules[mod.Path] = mod
|
||||
if mod.Replace != nil {
|
||||
g.AddModules(mod.Replace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetModule gets module at path if one exists. Otherwise,
|
||||
// it creates a module and returns it.
|
||||
func (g *PackageGraph) GetModule(path string) *packages.Module {
|
||||
if mod, ok := g.modules[path]; ok {
|
||||
return mod
|
||||
}
|
||||
mod := &packages.Module{
|
||||
Path: path,
|
||||
Version: "",
|
||||
}
|
||||
g.AddModules(mod)
|
||||
return mod
|
||||
}
|
||||
|
||||
// AddPackages adds the packages and their full graph of imported packages.
|
||||
// It also adds the modules of the added packages. It will ignore packages
|
||||
// that have duplicate paths to ones the graph already holds.
|
||||
func (g *PackageGraph) AddPackages(pkgs ...*packages.Package) {
|
||||
for _, pkg := range pkgs {
|
||||
if _, found := g.packages[pkg.PkgPath]; found {
|
||||
//TODO: check duplicates are okay?
|
||||
continue
|
||||
}
|
||||
g.packages[pkg.PkgPath] = pkg
|
||||
g.fixupPackage(pkg)
|
||||
for _, child := range pkg.Imports {
|
||||
g.AddPackages(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fixupPackage adds the module of pkg, if any, to the set
|
||||
// of all modules in g. If packages is not assigned a module
|
||||
// (likely stdlib package), a module set for pkg.
|
||||
func (g *PackageGraph) fixupPackage(pkg *packages.Package) {
|
||||
if pkg.Module != nil {
|
||||
g.AddModules(pkg.Module)
|
||||
return
|
||||
}
|
||||
pkg.Module = g.findModule(pkg.PkgPath)
|
||||
}
|
||||
|
||||
// findModule finds a module for package.
|
||||
// It does a longest prefix search amongst the existing modules, if that does
|
||||
// not find anything, it returns the "unknown" module.
|
||||
func (g *PackageGraph) findModule(pkgPath string) *packages.Module {
|
||||
//TODO: better stdlib test
|
||||
if IsStdPackage(pkgPath) {
|
||||
return g.GetModule(internal.GoStdModulePath)
|
||||
}
|
||||
for _, m := range g.modules {
|
||||
//TODO: not first match, best match...
|
||||
if pkgPath == m.Path || strings.HasPrefix(pkgPath, m.Path+"/") {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return g.GetModule(internal.UnknownModulePath)
|
||||
}
|
||||
|
||||
// GetPackage returns the package matching the path.
|
||||
// If the graph does not already know about the package, a new one is added.
|
||||
func (g *PackageGraph) GetPackage(path string) *packages.Package {
|
||||
if pkg, ok := g.packages[path]; ok {
|
||||
return pkg
|
||||
}
|
||||
pkg := &packages.Package{
|
||||
PkgPath: path,
|
||||
}
|
||||
g.AddPackages(pkg)
|
||||
return pkg
|
||||
}
|
||||
|
||||
// LoadPackages loads the packages specified by the patterns into the graph.
|
||||
// See golang.org/x/tools/go/packages.Load for details of how it works.
|
||||
func (g *PackageGraph) LoadPackagesAndMods(cfg *packages.Config, tags []string, patterns []string, wantSymbols bool) error {
|
||||
if len(tags) > 0 {
|
||||
cfg.BuildFlags = []string{fmt.Sprintf("-tags=%s", strings.Join(tags, ","))}
|
||||
}
|
||||
|
||||
addLoadMode(cfg, wantSymbols)
|
||||
|
||||
pkgs, err := packages.Load(cfg, patterns...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var perrs []packages.Error
|
||||
packages.Visit(pkgs, nil, func(p *packages.Package) {
|
||||
perrs = append(perrs, p.Errors...)
|
||||
})
|
||||
if len(perrs) > 0 {
|
||||
err = &packageError{perrs}
|
||||
}
|
||||
|
||||
// Add all packages, top-level ones and their imports.
|
||||
// This will also add their respective modules.
|
||||
g.AddPackages(pkgs...)
|
||||
|
||||
// save top-level packages
|
||||
for _, p := range pkgs {
|
||||
g.topPkgs = append(g.topPkgs, g.GetPackage(p.PkgPath))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func addLoadMode(cfg *packages.Config, wantSymbols bool) {
|
||||
cfg.Mode |=
|
||||
packages.NeedModule |
|
||||
packages.NeedName |
|
||||
packages.NeedDeps |
|
||||
packages.NeedImports
|
||||
if wantSymbols {
|
||||
cfg.Mode |= packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo
|
||||
}
|
||||
}
|
||||
|
||||
// packageError contains errors from loading a set of packages.
|
||||
type packageError struct {
|
||||
Errors []packages.Error
|
||||
}
|
||||
|
||||
func (e *packageError) Error() string {
|
||||
var b strings.Builder
|
||||
fmt.Fprintln(&b, "\nThere are errors with the provided package patterns:")
|
||||
fmt.Fprintln(&b, "")
|
||||
for _, e := range e.Errors {
|
||||
fmt.Fprintln(&b, e)
|
||||
}
|
||||
fmt.Fprintln(&b, "\nFor details on package patterns, see https://pkg.go.dev/cmd/go#hdr-Package_lists_and_patterns.")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (g *PackageGraph) SBOM() *govulncheck.SBOM {
|
||||
getMod := func(mod *packages.Module) *govulncheck.Module {
|
||||
if mod.Replace != nil {
|
||||
return &govulncheck.Module{
|
||||
Path: mod.Replace.Path,
|
||||
Version: mod.Replace.Version,
|
||||
}
|
||||
}
|
||||
|
||||
return &govulncheck.Module{
|
||||
Path: mod.Path,
|
||||
Version: mod.Version,
|
||||
}
|
||||
}
|
||||
|
||||
var roots []string
|
||||
rootMods := make(map[string]*govulncheck.Module)
|
||||
for _, pkg := range g.TopPkgs() {
|
||||
roots = append(roots, pkg.PkgPath)
|
||||
mod := getMod(pkg.Module)
|
||||
rootMods[mod.Path] = mod
|
||||
}
|
||||
|
||||
// Govulncheck attempts to put the modules that correspond to the matched package patterns (i.e. the root modules)
|
||||
// at the beginning of the SBOM.Modules message.
|
||||
// Note: This does not guarantee that the first element is the root module.
|
||||
var topMods, depMods []*govulncheck.Module
|
||||
var goVersion string
|
||||
for _, mod := range g.Modules() {
|
||||
mod := getMod(mod)
|
||||
|
||||
if mod.Path == internal.GoStdModulePath {
|
||||
goVersion = semver.SemverToGoTag(mod.Version)
|
||||
}
|
||||
|
||||
// if the mod is not associated with a root package, add it to depMods
|
||||
if rootMods[mod.Path] == nil {
|
||||
depMods = append(depMods, mod)
|
||||
}
|
||||
}
|
||||
|
||||
for _, mod := range rootMods {
|
||||
topMods = append(topMods, mod)
|
||||
}
|
||||
// Sort for deterministic output
|
||||
sortMods(topMods)
|
||||
sortMods(depMods)
|
||||
|
||||
mods := append(topMods, depMods...)
|
||||
|
||||
return &govulncheck.SBOM{
|
||||
GoVersion: goVersion,
|
||||
Modules: mods,
|
||||
Roots: roots,
|
||||
}
|
||||
}
|
||||
|
||||
// Sorts modules alphabetically by path.
|
||||
func sortMods(mods []*govulncheck.Module) {
|
||||
slices.SortFunc(mods, func(a, b *govulncheck.Module) int {
|
||||
return strings.Compare(a.Path, b.Path)
|
||||
})
|
||||
}
|
||||
46
vendor/golang.org/x/vuln/internal/vulncheck/slicing.go
generated
vendored
Normal file
46
vendor/golang.org/x/vuln/internal/vulncheck/slicing.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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.
|
||||
|
||||
package vulncheck
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// forwardSlice computes the transitive closure of functions forward reachable
|
||||
// via calls in cg or referred to in an instruction starting from `sources`.
|
||||
func forwardSlice(sources map[*ssa.Function]bool, cg *callgraph.Graph) map[*ssa.Function]bool {
|
||||
seen := make(map[*ssa.Function]bool)
|
||||
var visit func(f *ssa.Function)
|
||||
visit = func(f *ssa.Function) {
|
||||
if seen[f] {
|
||||
return
|
||||
}
|
||||
seen[f] = true
|
||||
|
||||
if n := cg.Nodes[f]; n != nil {
|
||||
for _, e := range n.Out {
|
||||
if e.Site != nil {
|
||||
visit(e.Callee.Func)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var buf [10]*ssa.Value // avoid alloc in common case
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
for _, op := range instr.Operands(buf[:0]) {
|
||||
if fn, ok := (*op).(*ssa.Function); ok {
|
||||
visit(fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for source := range sources {
|
||||
visit(source)
|
||||
}
|
||||
return seen
|
||||
}
|
||||
312
vendor/golang.org/x/vuln/internal/vulncheck/source.go
generated
vendored
Normal file
312
vendor/golang.org/x/vuln/internal/vulncheck/source.go
generated
vendored
Normal file
@@ -0,0 +1,312 @@
|
||||
// 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.
|
||||
|
||||
package vulncheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/vuln/internal/client"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
)
|
||||
|
||||
// Source detects vulnerabilities in pkgs and emits the findings to handler.
|
||||
func Source(ctx context.Context, handler govulncheck.Handler, cfg *govulncheck.Config, client *client.Client, graph *PackageGraph) error {
|
||||
vr, err := source(ctx, handler, cfg, client, graph)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.ScanLevel.WantSymbols() {
|
||||
return emitCallFindings(handler, sourceCallstacks(vr))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// source detects vulnerabilities in packages. It emits findings to handler
|
||||
// and produces a Result that contains info on detected vulnerabilities.
|
||||
//
|
||||
// Assumes that pkgs are non-empty and belong to the same program.
|
||||
func source(ctx context.Context, handler govulncheck.Handler, cfg *govulncheck.Config, client *client.Client, graph *PackageGraph) (*Result, error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
// If we are building the callgraph, build ssa and the callgraph in parallel
|
||||
// with fetching vulnerabilities. If the vulns set is empty, return without
|
||||
// waiting for SSA construction or callgraph to finish.
|
||||
var (
|
||||
wg sync.WaitGroup // guards entries, cg, and buildErr
|
||||
entries []*ssa.Function
|
||||
cg *callgraph.Graph
|
||||
buildErr error
|
||||
)
|
||||
if cfg.ScanLevel.WantSymbols() {
|
||||
fset := graph.TopPkgs()[0].Fset
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
prog, ssaPkgs := buildSSA(graph.TopPkgs(), fset)
|
||||
entries = entryPoints(ssaPkgs)
|
||||
cg, buildErr = callGraph(ctx, prog, entries)
|
||||
}()
|
||||
}
|
||||
|
||||
if err := handler.SBOM(graph.SBOM()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := handler.Progress(&govulncheck.Progress{Message: fetchingVulnsMessage}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mv, err := FetchVulnerabilities(ctx, client, graph.Modules())
|
||||
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 err := handler.Progress(&govulncheck.Progress{Message: checkingSrcVulnsMessage}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
affVulns := affectingVulnerabilities(mv, "", "")
|
||||
if err := emitModuleFindings(handler, affVulns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !cfg.ScanLevel.WantPackages() || len(affVulns) == 0 {
|
||||
return &Result{}, nil
|
||||
}
|
||||
|
||||
impVulns := importedVulnPackages(affVulns, graph)
|
||||
// Emit information on imported vulnerable packages now as
|
||||
// call graph computation might take a while.
|
||||
if err := emitPackageFindings(handler, impVulns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return result immediately if not in symbol mode or
|
||||
// if there are no vulnerabilities imported.
|
||||
if !cfg.ScanLevel.WantSymbols() || len(impVulns) == 0 {
|
||||
return &Result{Vulns: impVulns}, nil
|
||||
}
|
||||
|
||||
wg.Wait() // wait for build to finish
|
||||
if buildErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entryFuncs, callVulns := calledVulnSymbols(entries, affVulns, cg, graph)
|
||||
return &Result{EntryFunctions: entryFuncs, Vulns: callVulns}, nil
|
||||
}
|
||||
|
||||
// importedVulnPackages detects imported vulnerable packages.
|
||||
func importedVulnPackages(affVulns affectingVulns, graph *PackageGraph) []*Vuln {
|
||||
var vulns []*Vuln
|
||||
analyzed := make(map[*packages.Package]bool) // skip analyzing the same package multiple times
|
||||
var vulnImports func(pkg *packages.Package)
|
||||
vulnImports = func(pkg *packages.Package) {
|
||||
if analyzed[pkg] {
|
||||
return
|
||||
}
|
||||
|
||||
osvs := affVulns.ForPackage(pkgModPath(pkg), pkg.PkgPath)
|
||||
// Create Vuln entry for each OSV entry for pkg.
|
||||
for _, osv := range osvs {
|
||||
vuln := &Vuln{
|
||||
OSV: osv,
|
||||
Package: graph.GetPackage(pkg.PkgPath),
|
||||
}
|
||||
vulns = append(vulns, vuln)
|
||||
}
|
||||
|
||||
analyzed[pkg] = true
|
||||
for _, imp := range pkg.Imports {
|
||||
vulnImports(imp)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pkg := range graph.TopPkgs() {
|
||||
vulnImports(pkg)
|
||||
}
|
||||
return vulns
|
||||
}
|
||||
|
||||
// calledVulnSymbols detects vuln symbols transitively reachable from sources
|
||||
// via call graph cg.
|
||||
//
|
||||
// A slice of call graph is computed related to the reachable vulnerabilities. Each
|
||||
// reachable Vuln has attached FuncNode that can be upward traversed to the entry points.
|
||||
// Entry points that reach the vulnerable symbols are also returned.
|
||||
func calledVulnSymbols(sources []*ssa.Function, affVulns affectingVulns, cg *callgraph.Graph, graph *PackageGraph) ([]*FuncNode, []*Vuln) {
|
||||
sinksWithVulns := vulnFuncs(cg, affVulns, graph)
|
||||
|
||||
// Compute call graph backwards reachable
|
||||
// from vulnerable functions and methods.
|
||||
var sinks []*callgraph.Node
|
||||
for n := range sinksWithVulns {
|
||||
sinks = append(sinks, n)
|
||||
}
|
||||
bcg := callGraphSlice(sinks, false)
|
||||
|
||||
// Interesect backwards call graph with forward
|
||||
// reachable graph to remove redundant edges.
|
||||
var filteredSources []*callgraph.Node
|
||||
for _, e := range sources {
|
||||
if n, ok := bcg.Nodes[e]; ok {
|
||||
filteredSources = append(filteredSources, n)
|
||||
}
|
||||
}
|
||||
fcg := callGraphSlice(filteredSources, true)
|
||||
|
||||
// Get the sinks that are in fact reachable from entry points.
|
||||
filteredSinks := make(map[*callgraph.Node][]*osv.Entry)
|
||||
for n, vs := range sinksWithVulns {
|
||||
if fn, ok := fcg.Nodes[n.Func]; ok {
|
||||
filteredSinks[fn] = vs
|
||||
}
|
||||
}
|
||||
|
||||
// Transform the resulting call graph slice into
|
||||
// vulncheck representation.
|
||||
return vulnCallGraph(filteredSources, filteredSinks, graph)
|
||||
}
|
||||
|
||||
// callGraphSlice computes a slice of callgraph beginning at starts
|
||||
// in the direction (forward/backward) controlled by forward flag.
|
||||
func callGraphSlice(starts []*callgraph.Node, forward bool) *callgraph.Graph {
|
||||
g := &callgraph.Graph{Nodes: make(map[*ssa.Function]*callgraph.Node)}
|
||||
|
||||
visited := make(map[*callgraph.Node]bool)
|
||||
var visit func(*callgraph.Node)
|
||||
visit = func(n *callgraph.Node) {
|
||||
if visited[n] {
|
||||
return
|
||||
}
|
||||
visited[n] = true
|
||||
|
||||
var edges []*callgraph.Edge
|
||||
if forward {
|
||||
edges = n.Out
|
||||
} else {
|
||||
edges = n.In
|
||||
}
|
||||
|
||||
for _, edge := range edges {
|
||||
nCallee := g.CreateNode(edge.Callee.Func)
|
||||
nCaller := g.CreateNode(edge.Caller.Func)
|
||||
callgraph.AddEdge(nCaller, edge.Site, nCallee)
|
||||
|
||||
if forward {
|
||||
visit(edge.Callee)
|
||||
} else {
|
||||
visit(edge.Caller)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range starts {
|
||||
visit(s)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
// vulnCallGraph creates vulnerability call graph in terms of sources and sinks.
|
||||
func vulnCallGraph(sources []*callgraph.Node, sinks map[*callgraph.Node][]*osv.Entry, graph *PackageGraph) ([]*FuncNode, []*Vuln) {
|
||||
var entries []*FuncNode
|
||||
var vulns []*Vuln
|
||||
nodes := make(map[*ssa.Function]*FuncNode)
|
||||
|
||||
// First create entries and sinks and store relevant information.
|
||||
for _, s := range sources {
|
||||
fn := createNode(nodes, s.Func, graph)
|
||||
entries = append(entries, fn)
|
||||
}
|
||||
|
||||
for s, osvs := range sinks {
|
||||
f := s.Func
|
||||
funNode := createNode(nodes, s.Func, graph)
|
||||
|
||||
// Populate CallSink field for each detected vuln symbol.
|
||||
for _, osv := range osvs {
|
||||
vulns = append(vulns, calledVuln(funNode, osv, dbFuncName(f), funNode.Package))
|
||||
}
|
||||
}
|
||||
|
||||
visited := make(map[*callgraph.Node]bool)
|
||||
var visit func(*callgraph.Node)
|
||||
visit = func(n *callgraph.Node) {
|
||||
if visited[n] {
|
||||
return
|
||||
}
|
||||
visited[n] = true
|
||||
|
||||
for _, edge := range n.In {
|
||||
nCallee := createNode(nodes, edge.Callee.Func, graph)
|
||||
nCaller := createNode(nodes, edge.Caller.Func, graph)
|
||||
|
||||
call := edge.Site
|
||||
cs := &CallSite{
|
||||
Parent: nCaller,
|
||||
Name: call.Common().Value.Name(),
|
||||
RecvType: callRecvType(call),
|
||||
Resolved: resolved(call),
|
||||
Pos: instrPosition(call),
|
||||
}
|
||||
nCallee.CallSites = append(nCallee.CallSites, cs)
|
||||
|
||||
visit(edge.Caller)
|
||||
}
|
||||
}
|
||||
|
||||
for s := range sinks {
|
||||
visit(s)
|
||||
}
|
||||
return entries, vulns
|
||||
}
|
||||
|
||||
// vulnFuncs returns vulnerability information for vulnerable functions in cg.
|
||||
func vulnFuncs(cg *callgraph.Graph, affVulns affectingVulns, graph *PackageGraph) map[*callgraph.Node][]*osv.Entry {
|
||||
m := make(map[*callgraph.Node][]*osv.Entry)
|
||||
for f, n := range cg.Nodes {
|
||||
p := pkgPath(f)
|
||||
vulns := affVulns.ForSymbol(pkgModPath(graph.GetPackage(p)), p, dbFuncName(f))
|
||||
if len(vulns) > 0 {
|
||||
m[n] = vulns
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func createNode(nodes map[*ssa.Function]*FuncNode, f *ssa.Function, graph *PackageGraph) *FuncNode {
|
||||
if fn, ok := nodes[f]; ok {
|
||||
return fn
|
||||
}
|
||||
fn := &FuncNode{
|
||||
Name: f.Name(),
|
||||
Package: graph.GetPackage(pkgPath(f)),
|
||||
RecvType: funcRecvType(f),
|
||||
Pos: funcPosition(f),
|
||||
}
|
||||
nodes[f] = fn
|
||||
return fn
|
||||
}
|
||||
|
||||
func calledVuln(call *FuncNode, osv *osv.Entry, symbol string, pkg *packages.Package) *Vuln {
|
||||
return &Vuln{
|
||||
Symbol: symbol,
|
||||
Package: pkg,
|
||||
OSV: osv,
|
||||
CallSink: call,
|
||||
}
|
||||
}
|
||||
346
vendor/golang.org/x/vuln/internal/vulncheck/utils.go
generated
vendored
Normal file
346
vendor/golang.org/x/vuln/internal/vulncheck/utils.go
generated
vendored
Normal file
@@ -0,0 +1,346 @@
|
||||
// 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.
|
||||
|
||||
package vulncheck
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/callgraph/cha"
|
||||
"golang.org/x/tools/go/callgraph/vta"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"golang.org/x/vuln/internal"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
"golang.org/x/vuln/internal/semver"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// buildSSA creates an ssa representation for pkgs. Returns
|
||||
// the ssa program encapsulating the packages and top level
|
||||
// ssa packages corresponding to pkgs.
|
||||
func buildSSA(pkgs []*packages.Package, fset *token.FileSet) (*ssa.Program, []*ssa.Package) {
|
||||
prog := ssa.NewProgram(fset, ssa.InstantiateGenerics)
|
||||
|
||||
imports := make(map[*packages.Package]*ssa.Package)
|
||||
var createImports func(map[string]*packages.Package)
|
||||
createImports = func(pkgs map[string]*packages.Package) {
|
||||
for _, p := range pkgs {
|
||||
if _, ok := imports[p]; !ok {
|
||||
i := prog.CreatePackage(p.Types, p.Syntax, p.TypesInfo, true)
|
||||
imports[p] = i
|
||||
createImports(p.Imports)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, tp := range pkgs {
|
||||
createImports(tp.Imports)
|
||||
}
|
||||
|
||||
var ssaPkgs []*ssa.Package
|
||||
for _, tp := range pkgs {
|
||||
if sp, ok := imports[tp]; ok {
|
||||
ssaPkgs = append(ssaPkgs, sp)
|
||||
} else {
|
||||
sp := prog.CreatePackage(tp.Types, tp.Syntax, tp.TypesInfo, false)
|
||||
ssaPkgs = append(ssaPkgs, sp)
|
||||
}
|
||||
}
|
||||
prog.Build()
|
||||
return prog, ssaPkgs
|
||||
}
|
||||
|
||||
// callGraph builds a call graph of prog based on VTA analysis.
|
||||
func callGraph(ctx context.Context, prog *ssa.Program, entries []*ssa.Function) (*callgraph.Graph, error) {
|
||||
entrySlice := make(map[*ssa.Function]bool)
|
||||
for _, e := range entries {
|
||||
entrySlice[e] = true
|
||||
}
|
||||
|
||||
if err := ctx.Err(); err != nil { // cancelled?
|
||||
return nil, err
|
||||
}
|
||||
initial := cha.CallGraph(prog)
|
||||
|
||||
fslice := forwardSlice(entrySlice, initial)
|
||||
if err := ctx.Err(); err != nil { // cancelled?
|
||||
return nil, err
|
||||
}
|
||||
vtaCg := vta.CallGraph(fslice, initial)
|
||||
|
||||
// Repeat the process once more, this time using
|
||||
// the produced VTA call graph as the base graph.
|
||||
fslice = forwardSlice(entrySlice, vtaCg)
|
||||
if err := ctx.Err(); err != nil { // cancelled?
|
||||
return nil, err
|
||||
}
|
||||
cg := vta.CallGraph(fslice, vtaCg)
|
||||
cg.DeleteSyntheticNodes()
|
||||
return cg, nil
|
||||
}
|
||||
|
||||
// dbTypeFormat formats the name of t according how types
|
||||
// are encoded in vulnerability database:
|
||||
// - pointer designation * is skipped
|
||||
// - full path prefix is skipped as well
|
||||
func dbTypeFormat(t types.Type) string {
|
||||
switch tt := t.(type) {
|
||||
case *types.Pointer:
|
||||
return dbTypeFormat(tt.Elem())
|
||||
case *types.Named:
|
||||
return tt.Obj().Name()
|
||||
default:
|
||||
return types.TypeString(t, func(p *types.Package) string { return "" })
|
||||
}
|
||||
}
|
||||
|
||||
// dbFuncName computes a function name consistent with the namings used in vulnerability
|
||||
// databases. Effectively, a qualified name of a function local to its enclosing package.
|
||||
// If a receiver is a pointer, this information is not encoded in the resulting name. If
|
||||
// a function has type argument/parameter, this information is omitted. The name of
|
||||
// anonymous functions is simply "". The function names are unique subject to the enclosing
|
||||
// package, but not globally.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// func (a A) foo (...) {...} -> A.foo
|
||||
// func foo(...) {...} -> foo
|
||||
// func (b *B) bar (...) {...} -> B.bar
|
||||
// func (c C[T]) do(...) {...} -> C.do
|
||||
func dbFuncName(f *ssa.Function) string {
|
||||
selectBound := func(f *ssa.Function) types.Type {
|
||||
// If f is a "bound" function introduced by ssa for a given type, return the type.
|
||||
// When "f" is a "bound" function, it will have 1 free variable of that type within
|
||||
// the function. This is subject to change when ssa changes.
|
||||
if len(f.FreeVars) == 1 && strings.HasPrefix(f.Synthetic, "bound ") {
|
||||
return f.FreeVars[0].Type()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
selectThunk := func(f *ssa.Function) types.Type {
|
||||
// If f is a "thunk" function introduced by ssa for a given type, return the type.
|
||||
// When "f" is a "thunk" function, the first parameter will have that type within
|
||||
// the function. This is subject to change when ssa changes.
|
||||
params := f.Signature.Params() // params.Len() == 1 then params != nil.
|
||||
if strings.HasPrefix(f.Synthetic, "thunk ") && params.Len() >= 1 {
|
||||
if first := params.At(0); first != nil {
|
||||
return first.Type()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var qprefix string
|
||||
if recv := f.Signature.Recv(); recv != nil {
|
||||
qprefix = dbTypeFormat(recv.Type())
|
||||
} else if btype := selectBound(f); btype != nil {
|
||||
qprefix = dbTypeFormat(btype)
|
||||
} else if ttype := selectThunk(f); ttype != nil {
|
||||
qprefix = dbTypeFormat(ttype)
|
||||
}
|
||||
|
||||
if qprefix == "" {
|
||||
return funcName(f)
|
||||
}
|
||||
return qprefix + "." + funcName(f)
|
||||
}
|
||||
|
||||
// funcName returns the name of the ssa function f.
|
||||
// It is f.Name() without additional type argument
|
||||
// information in case of generics.
|
||||
func funcName(f *ssa.Function) string {
|
||||
n, _, _ := strings.Cut(f.Name(), "[")
|
||||
return n
|
||||
}
|
||||
|
||||
// memberFuncs returns functions associated with the `member`:
|
||||
// 1) `member` itself if `member` is a function
|
||||
// 2) `member` methods if `member` is a type
|
||||
// 3) empty list otherwise
|
||||
func memberFuncs(member ssa.Member, prog *ssa.Program) []*ssa.Function {
|
||||
switch t := member.(type) {
|
||||
case *ssa.Type:
|
||||
methods := typeutil.IntuitiveMethodSet(t.Type(), &prog.MethodSets)
|
||||
var funcs []*ssa.Function
|
||||
for _, m := range methods {
|
||||
if f := prog.MethodValue(m); f != nil {
|
||||
funcs = append(funcs, f)
|
||||
}
|
||||
}
|
||||
return funcs
|
||||
case *ssa.Function:
|
||||
return []*ssa.Function{t}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// funcPosition gives the position of `f`. Returns empty token.Position
|
||||
// if no file information on `f` is available.
|
||||
func funcPosition(f *ssa.Function) *token.Position {
|
||||
pos := f.Prog.Fset.Position(f.Pos())
|
||||
return &pos
|
||||
}
|
||||
|
||||
// instrPosition gives the position of `instr`. Returns empty token.Position
|
||||
// if no file information on `instr` is available.
|
||||
func instrPosition(instr ssa.Instruction) *token.Position {
|
||||
pos := instr.Parent().Prog.Fset.Position(instr.Pos())
|
||||
return &pos
|
||||
}
|
||||
|
||||
func resolved(call ssa.CallInstruction) bool {
|
||||
if call == nil {
|
||||
return true
|
||||
}
|
||||
return call.Common().StaticCallee() != nil
|
||||
}
|
||||
|
||||
func callRecvType(call ssa.CallInstruction) string {
|
||||
if !call.Common().IsInvoke() {
|
||||
return ""
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
types.WriteType(buf, call.Common().Value.Type(), nil)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func funcRecvType(f *ssa.Function) string {
|
||||
v := f.Signature.Recv()
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
types.WriteType(buf, v.Type(), nil)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func FixedVersion(modulePath, version string, affected []osv.Affected) string {
|
||||
fixed := earliestValidFix(modulePath, version, affected)
|
||||
// Add "v" prefix if one does not exist. moduleVersionString
|
||||
// will later on replace it with "go" if needed.
|
||||
if fixed != "" && !strings.HasPrefix(fixed, "v") {
|
||||
fixed = "v" + fixed
|
||||
}
|
||||
return fixed
|
||||
}
|
||||
|
||||
// earliestValidFix returns the earliest fix for version of modulePath that
|
||||
// itself is not vulnerable in affected.
|
||||
//
|
||||
// Suppose we have a version "v1.0.0" and we use {...} to denote different
|
||||
// affected regions. Assume for simplicity that all affected apply to the
|
||||
// same input modulePath.
|
||||
//
|
||||
// {[v0.1.0, v0.1.9), [v1.0.0, v2.0.0)} -> v2.0.0
|
||||
// {[v1.0.0, v1.5.0), [v2.0.0, v2.1.0}, {[v1.4.0, v1.6.0)} -> v2.1.0
|
||||
func earliestValidFix(modulePath, version string, affected []osv.Affected) string {
|
||||
var moduleAffected []osv.Affected
|
||||
for _, a := range affected {
|
||||
if a.Module.Path == modulePath {
|
||||
moduleAffected = append(moduleAffected, a)
|
||||
}
|
||||
}
|
||||
|
||||
vFixes := validFixes(version, moduleAffected)
|
||||
for _, fix := range vFixes {
|
||||
if !fixNegated(fix, moduleAffected) {
|
||||
return fix
|
||||
}
|
||||
}
|
||||
return ""
|
||||
|
||||
}
|
||||
|
||||
// validFixes computes all fixes for version in affected and
|
||||
// returns them sorted increasingly. Assumes that all affected
|
||||
// apply to the same module.
|
||||
func validFixes(version string, affected []osv.Affected) []string {
|
||||
var fixes []string
|
||||
for _, a := range affected {
|
||||
for _, r := range a.Ranges {
|
||||
if r.Type != osv.RangeTypeSemver {
|
||||
continue
|
||||
}
|
||||
for _, e := range r.Events {
|
||||
fix := e.Fixed
|
||||
if fix != "" && semver.Less(version, fix) {
|
||||
fixes = append(fixes, fix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.SliceStable(fixes, func(i, j int) bool { return semver.Less(fixes[i], fixes[j]) })
|
||||
return fixes
|
||||
}
|
||||
|
||||
// fixNegated checks if fix is negated to by a re-introduction
|
||||
// of a vulnerability in affected. Assumes that all affected apply
|
||||
// to the same module.
|
||||
func fixNegated(fix string, affected []osv.Affected) bool {
|
||||
for _, a := range affected {
|
||||
for _, r := range a.Ranges {
|
||||
if semver.ContainsSemver(r, fix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func modPath(mod *packages.Module) string {
|
||||
if mod.Replace != nil {
|
||||
return mod.Replace.Path
|
||||
}
|
||||
return mod.Path
|
||||
}
|
||||
|
||||
func modVersion(mod *packages.Module) string {
|
||||
if mod.Replace != nil {
|
||||
return mod.Replace.Version
|
||||
}
|
||||
return mod.Version
|
||||
}
|
||||
|
||||
// pkgPath returns the path of the f's enclosing package, if any.
|
||||
// Otherwise, returns internal.UnknownPackagePath.
|
||||
func pkgPath(f *ssa.Function) string {
|
||||
g := f
|
||||
if f.Origin() != nil {
|
||||
// Instantiations of generics do not have
|
||||
// an associated package. We hence look up
|
||||
// the original function for the package.
|
||||
g = f.Origin()
|
||||
}
|
||||
if g.Package() != nil && g.Package().Pkg != nil {
|
||||
return g.Package().Pkg.Path()
|
||||
}
|
||||
return internal.UnknownPackagePath
|
||||
}
|
||||
|
||||
func pkgModPath(pkg *packages.Package) string {
|
||||
if pkg != nil && pkg.Module != nil {
|
||||
return pkg.Module.Path
|
||||
}
|
||||
return internal.UnknownModulePath
|
||||
}
|
||||
|
||||
func IsStdPackage(pkg string) bool {
|
||||
if pkg == "" || pkg == internal.UnknownPackagePath {
|
||||
return false
|
||||
}
|
||||
// std packages do not have a "." in their path. For instance, see
|
||||
// Contains in pkgsite/+/refs/heads/master/internal/stdlbib/stdlib.go.
|
||||
if i := strings.IndexByte(pkg, '/'); i != -1 {
|
||||
pkg = pkg[:i]
|
||||
}
|
||||
return !strings.Contains(pkg, ".")
|
||||
}
|
||||
336
vendor/golang.org/x/vuln/internal/vulncheck/vulncheck.go
generated
vendored
Normal file
336
vendor/golang.org/x/vuln/internal/vulncheck/vulncheck.go
generated
vendored
Normal file
@@ -0,0 +1,336 @@
|
||||
// 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.
|
||||
|
||||
package vulncheck
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/vuln/internal"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
"golang.org/x/vuln/internal/semver"
|
||||
)
|
||||
|
||||
const (
|
||||
fetchingVulnsMessage = "Fetching vulnerabilities from the database..."
|
||||
checkingSrcVulnsMessage = "Checking the code against the vulnerabilities..."
|
||||
checkingBinVulnsMessage = "Checking the binary against the vulnerabilities..."
|
||||
)
|
||||
|
||||
// Result contains information on detected vulnerabilities.
|
||||
// For call graph analysis, it provides information on reachability
|
||||
// of vulnerable symbols through entry points of the program.
|
||||
type Result struct {
|
||||
// EntryFunctions are a subset of Functions representing vulncheck entry points.
|
||||
EntryFunctions []*FuncNode
|
||||
|
||||
// Vulns contains information on detected vulnerabilities.
|
||||
Vulns []*Vuln
|
||||
}
|
||||
|
||||
// Vuln provides information on a detected vulnerability. For call
|
||||
// graph mode, Vuln will also contain the information on how the
|
||||
// vulnerability is reachable in the user call graph.
|
||||
type Vuln struct {
|
||||
// OSV contains information on the detected vulnerability in the shared
|
||||
// vulnerability format.
|
||||
//
|
||||
// OSV, Symbol, and Package identify a vulnerability.
|
||||
//
|
||||
// Note that *osv.Entry may describe multiple symbols from multiple
|
||||
// packages.
|
||||
OSV *osv.Entry
|
||||
|
||||
// Symbol is the name of the detected vulnerable function or method.
|
||||
Symbol string
|
||||
|
||||
// CallSink is the FuncNode corresponding to Symbol.
|
||||
//
|
||||
// When analyzing binaries, Symbol is not reachable, or cfg.ScanLevel
|
||||
// is symbol, CallSink will be unavailable and set to nil.
|
||||
CallSink *FuncNode
|
||||
|
||||
// Package of Symbol.
|
||||
//
|
||||
// When the package of symbol is not imported, Package will be
|
||||
// unavailable and set to nil.
|
||||
Package *packages.Package
|
||||
}
|
||||
|
||||
// A FuncNode describes a function in the call graph.
|
||||
type FuncNode struct {
|
||||
// Name is the name of the function.
|
||||
Name string
|
||||
|
||||
// RecvType is the receiver object type of this function, if any.
|
||||
RecvType string
|
||||
|
||||
// Package is the package the function is part of.
|
||||
Package *packages.Package
|
||||
|
||||
// Position describes the position of the function in the file.
|
||||
Pos *token.Position
|
||||
|
||||
// CallSites is a set of call sites where this function is called.
|
||||
CallSites []*CallSite
|
||||
}
|
||||
|
||||
func (fn *FuncNode) String() string {
|
||||
if fn.RecvType == "" {
|
||||
return fmt.Sprintf("%s.%s", fn.Package.PkgPath, fn.Name)
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", fn.RecvType, fn.Name)
|
||||
}
|
||||
|
||||
// Receiver returns the FuncNode's receiver, with package path removed.
|
||||
// Pointers are preserved if present.
|
||||
func (fn *FuncNode) Receiver() string {
|
||||
return strings.Replace(fn.RecvType, fmt.Sprintf("%s.", fn.Package.PkgPath), "", 1)
|
||||
}
|
||||
|
||||
// A CallSite describes a function call.
|
||||
type CallSite struct {
|
||||
// Parent is the enclosing function where the call is made.
|
||||
Parent *FuncNode
|
||||
|
||||
// Name stands for the name of the function (variable) being called.
|
||||
Name string
|
||||
|
||||
// RecvType is the full path of the receiver object type, if any.
|
||||
RecvType string
|
||||
|
||||
// Position describes the position of the function in the file.
|
||||
Pos *token.Position
|
||||
|
||||
// Resolved indicates if the called function can be statically resolved.
|
||||
Resolved bool
|
||||
}
|
||||
|
||||
// affectingVulns is an internal structure for querying
|
||||
// vulnerabilities that apply to the current program
|
||||
// and platform under consideration.
|
||||
type affectingVulns []*ModVulns
|
||||
|
||||
// ModVulns groups vulnerabilities per module.
|
||||
type ModVulns struct {
|
||||
Module *packages.Module
|
||||
Vulns []*osv.Entry
|
||||
}
|
||||
|
||||
func affectingVulnerabilities(vulns []*ModVulns, os, arch string) affectingVulns {
|
||||
now := time.Now()
|
||||
var filtered affectingVulns
|
||||
for _, mod := range vulns {
|
||||
module := mod.Module
|
||||
modVersion := module.Version
|
||||
if module.Replace != nil {
|
||||
modVersion = module.Replace.Version
|
||||
}
|
||||
// TODO(https://golang.org/issues/49264): if modVersion == "", try vcs?
|
||||
var filteredVulns []*osv.Entry
|
||||
for _, v := range mod.Vulns {
|
||||
// Ignore vulnerabilities that have been withdrawn
|
||||
if v.Withdrawn != nil && v.Withdrawn.Before(now) {
|
||||
continue
|
||||
}
|
||||
|
||||
var filteredAffected []osv.Affected
|
||||
for _, a := range v.Affected {
|
||||
// Vulnerabilities from some databases might contain
|
||||
// information on related but different modules that
|
||||
// were, say, reported in the same CVE. We filter such
|
||||
// information out as it might lead to incorrect results:
|
||||
// Computing a latest fix could consider versions of these
|
||||
// different packages.
|
||||
if a.Module.Path != module.Path {
|
||||
continue
|
||||
}
|
||||
if !affected(modVersion, a) {
|
||||
continue
|
||||
}
|
||||
|
||||
var filteredImports []osv.Package
|
||||
for _, p := range a.EcosystemSpecific.Packages {
|
||||
if matchesPlatform(os, arch, p) {
|
||||
filteredImports = append(filteredImports, p)
|
||||
}
|
||||
}
|
||||
// If we pruned all existing Packages, then the affected is
|
||||
// empty and we can filter it out. Note that Packages can
|
||||
// be empty for vulnerabilities that have no package or
|
||||
// symbol information available.
|
||||
if len(a.EcosystemSpecific.Packages) != 0 && len(filteredImports) == 0 {
|
||||
continue
|
||||
}
|
||||
a.EcosystemSpecific.Packages = filteredImports
|
||||
filteredAffected = append(filteredAffected, a)
|
||||
}
|
||||
if len(filteredAffected) == 0 {
|
||||
continue
|
||||
}
|
||||
// save the non-empty vulnerability with only
|
||||
// affected symbols.
|
||||
newV := *v
|
||||
newV.Affected = filteredAffected
|
||||
filteredVulns = append(filteredVulns, &newV)
|
||||
}
|
||||
|
||||
filtered = append(filtered, &ModVulns{
|
||||
Module: module,
|
||||
Vulns: filteredVulns,
|
||||
})
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// affected checks if modVersion is affected by a:
|
||||
// - it is included in one of the affected version ranges
|
||||
// - and module version is not "" and "(devel)"
|
||||
func affected(modVersion string, a osv.Affected) bool {
|
||||
const devel = "(devel)"
|
||||
if modVersion == "" || modVersion == devel {
|
||||
// Module version of "" means the module version is not available
|
||||
// and devel means it is in development stage. Either way, we don't
|
||||
// know the exact version so we don't want to spam users with
|
||||
// potential false alarms.
|
||||
return false
|
||||
}
|
||||
return semver.Affects(a.Ranges, modVersion)
|
||||
}
|
||||
|
||||
func matchesPlatform(os, arch string, e osv.Package) bool {
|
||||
return matchesPlatformComponent(os, e.GOOS) &&
|
||||
matchesPlatformComponent(arch, e.GOARCH)
|
||||
}
|
||||
|
||||
// matchesPlatformComponent reports whether a GOOS (or GOARCH)
|
||||
// matches a list of GOOS (or GOARCH) values from an osv.EcosystemSpecificImport.
|
||||
func matchesPlatformComponent(s string, ps []string) bool {
|
||||
// An empty input or an empty GOOS or GOARCH list means "matches everything."
|
||||
if s == "" || len(ps) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, p := range ps {
|
||||
if s == p {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// moduleVulns return vulnerabilities for module. If module is unknown,
|
||||
// it figures the module from package importPath. It returns the module
|
||||
// whose path is the longest prefix of importPath.
|
||||
func (aff affectingVulns) moduleVulns(module, importPath string) *ModVulns {
|
||||
moduleKnown := module != "" && module != internal.UnknownModulePath
|
||||
|
||||
isStd := IsStdPackage(importPath)
|
||||
var mostSpecificMod *ModVulns // for the case where !moduleKnown
|
||||
for _, mod := range aff {
|
||||
md := mod
|
||||
if isStd && mod.Module.Path == internal.GoStdModulePath {
|
||||
// Standard library packages do not have an associated module,
|
||||
// so we relate them to the artificial stdlib module.
|
||||
return md
|
||||
}
|
||||
|
||||
if moduleKnown {
|
||||
if mod.Module.Path == module {
|
||||
// If we know exactly which module we need,
|
||||
// return its vulnerabilities.
|
||||
return md
|
||||
}
|
||||
} else if strings.HasPrefix(importPath, md.Module.Path) {
|
||||
// If module is unknown, we try to figure it out from importPath.
|
||||
// We take the module whose path has the longest match to importPath.
|
||||
// TODO: do matching based on path components.
|
||||
if mostSpecificMod == nil || len(mostSpecificMod.Module.Path) < len(md.Module.Path) {
|
||||
mostSpecificMod = md
|
||||
}
|
||||
}
|
||||
}
|
||||
return mostSpecificMod
|
||||
}
|
||||
|
||||
// ForPackage returns the vulnerabilities for the importPath belonging to
|
||||
// module.
|
||||
//
|
||||
// If module is unknown, ForPackage will resolve it as the most specific
|
||||
// prefix of importPath.
|
||||
func (aff affectingVulns) ForPackage(module, importPath string) []*osv.Entry {
|
||||
mod := aff.moduleVulns(module, importPath)
|
||||
if mod == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if mod.Module.Replace != nil {
|
||||
// standard libraries do not have a module nor replace module
|
||||
importPath = fmt.Sprintf("%s%s", mod.Module.Replace.Path, strings.TrimPrefix(importPath, mod.Module.Path))
|
||||
}
|
||||
vulns := mod.Vulns
|
||||
packageVulns := []*osv.Entry{}
|
||||
Vuln:
|
||||
for _, v := range vulns {
|
||||
for _, a := range v.Affected {
|
||||
if len(a.EcosystemSpecific.Packages) == 0 {
|
||||
// no packages means all packages are vulnerable
|
||||
packageVulns = append(packageVulns, v)
|
||||
continue Vuln
|
||||
}
|
||||
|
||||
for _, p := range a.EcosystemSpecific.Packages {
|
||||
if p.Path == importPath {
|
||||
packageVulns = append(packageVulns, v)
|
||||
continue Vuln
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return packageVulns
|
||||
}
|
||||
|
||||
// ForSymbol returns vulnerabilities for symbol in aff.ForPackage(module, importPath).
|
||||
func (aff affectingVulns) ForSymbol(module, importPath, symbol string) []*osv.Entry {
|
||||
vulns := aff.ForPackage(module, importPath)
|
||||
if vulns == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
symbolVulns := []*osv.Entry{}
|
||||
vulnLoop:
|
||||
for _, v := range vulns {
|
||||
for _, a := range v.Affected {
|
||||
if len(a.EcosystemSpecific.Packages) == 0 {
|
||||
// no packages means all symbols of all packages are vulnerable
|
||||
symbolVulns = append(symbolVulns, v)
|
||||
continue vulnLoop
|
||||
}
|
||||
|
||||
for _, p := range a.EcosystemSpecific.Packages {
|
||||
if p.Path != importPath {
|
||||
continue
|
||||
}
|
||||
if len(p.Symbols) > 0 && !contains(p.Symbols, symbol) {
|
||||
continue
|
||||
}
|
||||
symbolVulns = append(symbolVulns, v)
|
||||
continue vulnLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
return symbolVulns
|
||||
}
|
||||
|
||||
func contains(symbols []string, target string) bool {
|
||||
for _, s := range symbols {
|
||||
if s == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
449
vendor/golang.org/x/vuln/internal/vulncheck/witness.go
generated
vendored
Normal file
449
vendor/golang.org/x/vuln/internal/vulncheck/witness.go
generated
vendored
Normal file
@@ -0,0 +1,449 @@
|
||||
// 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.
|
||||
|
||||
package vulncheck
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
// CallStack is a call stack starting with a client
|
||||
// function or method and ending with a call to a
|
||||
// vulnerable symbol.
|
||||
type CallStack []StackEntry
|
||||
|
||||
// StackEntry is an element of a call stack.
|
||||
type StackEntry struct {
|
||||
// Function whose frame is on the stack.
|
||||
Function *FuncNode
|
||||
|
||||
// Call is the call site inducing the next stack frame.
|
||||
// nil when the frame represents the last frame in the stack.
|
||||
Call *CallSite
|
||||
}
|
||||
|
||||
// sourceCallstacks returns representative call stacks for each
|
||||
// vulnerability in res. The returned call stacks are heuristically
|
||||
// ordered by how seemingly easy is to understand them: shorter
|
||||
// call stacks with less dynamic call sites appear earlier in the
|
||||
// returned slices.
|
||||
//
|
||||
// sourceCallstacks performs a breadth-first search of res.CallGraph
|
||||
// starting at the vulnerable symbol and going up until reaching an entry
|
||||
// function or method in res.CallGraph.Entries. During this search,
|
||||
// each function is visited at most once to avoid potential
|
||||
// exponential explosion. Hence, not all call stacks are analyzed.
|
||||
func sourceCallstacks(res *Result) map[*Vuln]CallStack {
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
)
|
||||
stackPerVuln := make(map[*Vuln]CallStack)
|
||||
for _, vuln := range res.Vulns {
|
||||
vuln := vuln
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
cs := sourceCallstack(vuln, res)
|
||||
mu.Lock()
|
||||
stackPerVuln[vuln] = cs
|
||||
mu.Unlock()
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
updateInitPositions(stackPerVuln)
|
||||
return stackPerVuln
|
||||
}
|
||||
|
||||
// sourceCallstack finds a representative call stack for vuln.
|
||||
// This is a shortest unique call stack with the least
|
||||
// number of dynamic call sites.
|
||||
func sourceCallstack(vuln *Vuln, res *Result) CallStack {
|
||||
vulnSink := vuln.CallSink
|
||||
if vulnSink == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
entries := make(map[*FuncNode]bool)
|
||||
for _, e := range res.EntryFunctions {
|
||||
entries[e] = true
|
||||
}
|
||||
|
||||
seen := make(map[*FuncNode]bool)
|
||||
|
||||
// Do a BFS from the vuln sink to the entry points
|
||||
// and find the representative call stack. This is
|
||||
// the shortest call stack that goes through the
|
||||
// least number of dynamic call sites. We first
|
||||
// collect all candidate call stacks of the shortest
|
||||
// length and then pick the best one accordingly.
|
||||
var candidates []CallStack
|
||||
candDepth := 0
|
||||
queue := list.New()
|
||||
queue.PushBack(&callChain{f: vulnSink})
|
||||
|
||||
// We want to avoid call stacks that go through
|
||||
// other vulnerable symbols of the same package
|
||||
// for the same vulnerability. In other words,
|
||||
// we want unique call stacks.
|
||||
skipSymbols := make(map[*FuncNode]bool)
|
||||
for _, v := range res.Vulns {
|
||||
if v.CallSink != nil && v != vuln &&
|
||||
v.OSV == vuln.OSV && v.Package == vuln.Package {
|
||||
skipSymbols[v.CallSink] = true
|
||||
}
|
||||
}
|
||||
|
||||
for queue.Len() > 0 {
|
||||
front := queue.Front()
|
||||
c := front.Value.(*callChain)
|
||||
queue.Remove(front)
|
||||
|
||||
f := c.f
|
||||
if seen[f] {
|
||||
continue
|
||||
}
|
||||
seen[f] = true
|
||||
|
||||
// Pick a single call site for each function in determinstic order.
|
||||
// A single call site is sufficient as we visit a function only once.
|
||||
for _, cs := range callsites(f.CallSites, seen) {
|
||||
nStack := &callChain{f: cs.Parent, call: cs, child: c}
|
||||
if !skipSymbols[cs.Parent] {
|
||||
queue.PushBack(nStack)
|
||||
}
|
||||
|
||||
if entries[cs.Parent] {
|
||||
ns := nStack.CallStack()
|
||||
if len(candidates) == 0 || len(ns) == candDepth {
|
||||
// The case where we either have not identified
|
||||
// any call stacks or just found one of the same
|
||||
// length as the previous ones.
|
||||
candidates = append(candidates, ns)
|
||||
candDepth = len(ns)
|
||||
} else {
|
||||
// We just found a candidate call stack whose
|
||||
// length is greater than what we previously
|
||||
// found. We can thus safely disregard this
|
||||
// call stack and stop searching since we won't
|
||||
// be able to find any better candidates.
|
||||
queue.Init() // clear the list, effectively exiting the outer loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort candidate call stacks by their number of dynamic call
|
||||
// sites and return the first one.
|
||||
sort.SliceStable(candidates, func(i int, j int) bool {
|
||||
s1, s2 := candidates[i], candidates[j]
|
||||
if w1, w2 := weight(s1), weight(s2); w1 != w2 {
|
||||
return w1 < w2
|
||||
}
|
||||
|
||||
// At this point, the stableness/determinism of
|
||||
// sorting is guaranteed by the determinism of
|
||||
// the underlying call graph and the call stack
|
||||
// search algorithm.
|
||||
return true
|
||||
})
|
||||
if len(candidates) == 0 {
|
||||
return nil
|
||||
}
|
||||
return candidates[0]
|
||||
}
|
||||
|
||||
// callsites picks a call site from sites for each non-visited function.
|
||||
// For each such function, the smallest (posLess) call site is chosen. The
|
||||
// returned slice is sorted by caller functions (funcLess). Assumes callee
|
||||
// of each call site is the same.
|
||||
func callsites(sites []*CallSite, visited map[*FuncNode]bool) []*CallSite {
|
||||
minCs := make(map[*FuncNode]*CallSite)
|
||||
for _, cs := range sites {
|
||||
if visited[cs.Parent] {
|
||||
continue
|
||||
}
|
||||
if csLess(cs, minCs[cs.Parent]) {
|
||||
minCs[cs.Parent] = cs
|
||||
}
|
||||
}
|
||||
|
||||
var fs []*FuncNode
|
||||
for _, cs := range minCs {
|
||||
fs = append(fs, cs.Parent)
|
||||
}
|
||||
sort.SliceStable(fs, func(i, j int) bool { return funcLess(fs[i], fs[j]) })
|
||||
|
||||
var css []*CallSite
|
||||
for _, f := range fs {
|
||||
css = append(css, minCs[f])
|
||||
}
|
||||
return css
|
||||
}
|
||||
|
||||
// callChain models a chain of function calls.
|
||||
type callChain struct {
|
||||
call *CallSite // nil for entry points
|
||||
f *FuncNode
|
||||
child *callChain
|
||||
}
|
||||
|
||||
// CallStack converts callChain to CallStack type.
|
||||
func (c *callChain) CallStack() CallStack {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return append(CallStack{StackEntry{Function: c.f, Call: c.call}}, c.child.CallStack()...)
|
||||
}
|
||||
|
||||
// weight computes an approximate measure of how easy is to understand the call
|
||||
// stack when presented to the client as a witness. The smaller the value, the more
|
||||
// understandable the stack is. Currently defined as the number of unresolved
|
||||
// call sites in the stack.
|
||||
func weight(stack CallStack) int {
|
||||
w := 0
|
||||
for _, e := range stack {
|
||||
if e.Call != nil && !e.Call.Resolved {
|
||||
w += 1
|
||||
}
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// csLess compares two call sites by their locations and, if needed,
|
||||
// their string representation.
|
||||
func csLess(cs1, cs2 *CallSite) bool {
|
||||
if cs2 == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// fast code path
|
||||
if p1, p2 := cs1.Pos, cs2.Pos; p1 != nil && p2 != nil {
|
||||
if posLess(*p1, *p2) {
|
||||
return true
|
||||
}
|
||||
if posLess(*p2, *p1) {
|
||||
return false
|
||||
}
|
||||
// for sanity, should not occur in practice
|
||||
return fmt.Sprintf("%v.%v", cs1.RecvType, cs2.Name) < fmt.Sprintf("%v.%v", cs2.RecvType, cs2.Name)
|
||||
}
|
||||
|
||||
// code path rarely exercised
|
||||
if cs2.Pos == nil {
|
||||
return true
|
||||
}
|
||||
if cs1.Pos == nil {
|
||||
return false
|
||||
}
|
||||
// should very rarely occur in practice
|
||||
return fmt.Sprintf("%v.%v", cs1.RecvType, cs2.Name) < fmt.Sprintf("%v.%v", cs2.RecvType, cs2.Name)
|
||||
}
|
||||
|
||||
// posLess compares two positions by their line and column number,
|
||||
// and filename if needed.
|
||||
func posLess(p1, p2 token.Position) bool {
|
||||
if p1.Line < p2.Line {
|
||||
return true
|
||||
}
|
||||
if p2.Line < p1.Line {
|
||||
return false
|
||||
}
|
||||
|
||||
if p1.Column < p2.Column {
|
||||
return true
|
||||
}
|
||||
if p2.Column < p1.Column {
|
||||
return false
|
||||
}
|
||||
|
||||
return strings.Compare(p1.Filename, p2.Filename) == -1
|
||||
}
|
||||
|
||||
// funcLess compares two function nodes by locations of
|
||||
// corresponding functions and, if needed, their string representation.
|
||||
func funcLess(f1, f2 *FuncNode) bool {
|
||||
if p1, p2 := f1.Pos, f2.Pos; p1 != nil && p2 != nil {
|
||||
if posLess(*p1, *p2) {
|
||||
return true
|
||||
}
|
||||
if posLess(*p2, *p1) {
|
||||
return false
|
||||
}
|
||||
// for sanity, should not occur in practice
|
||||
return f1.String() < f2.String()
|
||||
}
|
||||
|
||||
if f2.Pos == nil {
|
||||
return true
|
||||
}
|
||||
if f1.Pos == nil {
|
||||
return false
|
||||
}
|
||||
// should happen only for inits
|
||||
return f1.String() < f2.String()
|
||||
}
|
||||
|
||||
// updateInitPositions populates non-existing positions of init functions
|
||||
// and their respective calls in callStacks (see #51575).
|
||||
func updateInitPositions(callStacks map[*Vuln]CallStack) {
|
||||
for _, cs := range callStacks {
|
||||
for i := range cs {
|
||||
updateInitPosition(&cs[i])
|
||||
if i != len(cs)-1 {
|
||||
updateInitCallPosition(&cs[i], cs[i+1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updateInitCallPosition updates the position of a call to init in a stack frame, if
|
||||
// one already does not exist:
|
||||
//
|
||||
// P1.init -> P2.init: position of call to P2.init is the position of "import P2"
|
||||
// statement in P1
|
||||
//
|
||||
// P.init -> P.init#d: P.init is an implicit init. We say it calls the explicit
|
||||
// P.init#d at the place of "package P" statement.
|
||||
func updateInitCallPosition(curr *StackEntry, next StackEntry) {
|
||||
call := curr.Call
|
||||
if !isInit(next.Function) || (call.Pos != nil && call.Pos.IsValid()) {
|
||||
// Skip non-init functions and inits whose call site position is available.
|
||||
return
|
||||
}
|
||||
|
||||
var pos token.Position
|
||||
if curr.Function.Name == "init" && curr.Function.Package == next.Function.Package {
|
||||
// We have implicit P.init calling P.init#d. Set the call position to
|
||||
// be at "package P" statement position.
|
||||
pos = packageStatementPos(curr.Function.Package)
|
||||
} else {
|
||||
// Choose the beginning of the import statement as the position.
|
||||
pos = importStatementPos(curr.Function.Package, next.Function.Package.PkgPath)
|
||||
}
|
||||
|
||||
call.Pos = &pos
|
||||
}
|
||||
|
||||
func importStatementPos(pkg *packages.Package, importPath string) token.Position {
|
||||
var importSpec *ast.ImportSpec
|
||||
spec:
|
||||
for _, f := range pkg.Syntax {
|
||||
for _, impSpec := range f.Imports {
|
||||
// Import spec paths have quotation marks.
|
||||
impSpecPath, err := strconv.Unquote(impSpec.Path.Value)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("import specification: package path has no quotation marks: %v", err))
|
||||
}
|
||||
if impSpecPath == importPath {
|
||||
importSpec = impSpec
|
||||
break spec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if importSpec == nil {
|
||||
// for sanity, in case of a wild call graph imprecision
|
||||
return token.Position{}
|
||||
}
|
||||
|
||||
// Choose the beginning of the import statement as the position.
|
||||
return pkg.Fset.Position(importSpec.Pos())
|
||||
}
|
||||
|
||||
func packageStatementPos(pkg *packages.Package) token.Position {
|
||||
if len(pkg.Syntax) == 0 {
|
||||
return token.Position{}
|
||||
}
|
||||
// Choose beginning of the package statement as the position. Pick
|
||||
// the first file since it is as good as any.
|
||||
return pkg.Fset.Position(pkg.Syntax[0].Package)
|
||||
}
|
||||
|
||||
// updateInitPosition updates the position of P.init function in a stack frame if one
|
||||
// is not available. The new position is the position of the "package P" statement.
|
||||
func updateInitPosition(se *StackEntry) {
|
||||
fun := se.Function
|
||||
if !isInit(fun) || (fun.Pos != nil && fun.Pos.IsValid()) {
|
||||
// Skip non-init functions and inits whose position is available.
|
||||
return
|
||||
}
|
||||
|
||||
pos := packageStatementPos(fun.Package)
|
||||
fun.Pos = &pos
|
||||
}
|
||||
|
||||
func isInit(f *FuncNode) bool {
|
||||
// A source init function, or anonymous functions used in inits, will
|
||||
// be named "init#x" by vulncheck (more precisely, ssa), where x is a
|
||||
// positive integer. Implicit inits are named simply "init".
|
||||
return f.Name == "init" || strings.HasPrefix(f.Name, "init#")
|
||||
}
|
||||
|
||||
// binaryCallstacks computes representative call stacks for binary results.
|
||||
func binaryCallstacks(vr *Result) map[*Vuln]CallStack {
|
||||
callstacks := map[*Vuln]CallStack{}
|
||||
for _, vv := range uniqueVulns(vr.Vulns) {
|
||||
f := &FuncNode{Package: vv.Package, Name: vv.Symbol}
|
||||
parts := strings.Split(vv.Symbol, ".")
|
||||
if len(parts) != 1 {
|
||||
f.RecvType = parts[0]
|
||||
f.Name = parts[1]
|
||||
}
|
||||
callstacks[vv] = CallStack{StackEntry{Function: f}}
|
||||
}
|
||||
return callstacks
|
||||
}
|
||||
|
||||
// uniqueVulns does for binary mode what sourceCallstacks does for source mode.
|
||||
// It tries not to report redundant symbols. Since there are no call stacks in
|
||||
// binary mode, the following approximate approach is used. Do not report unexported
|
||||
// symbols for a <vulnID, pkg, module> triple if there are some exported symbols.
|
||||
// Otherwise, report all unexported symbols to avoid not reporting anything.
|
||||
func uniqueVulns(vulns []*Vuln) []*Vuln {
|
||||
type key struct {
|
||||
id string
|
||||
pkg string
|
||||
mod string
|
||||
}
|
||||
hasExported := make(map[key]bool)
|
||||
for _, v := range vulns {
|
||||
if isExported(v.Symbol) {
|
||||
k := key{id: v.OSV.ID, pkg: v.Package.PkgPath, mod: v.Package.Module.Path}
|
||||
hasExported[k] = true
|
||||
}
|
||||
}
|
||||
|
||||
var uniques []*Vuln
|
||||
for _, v := range vulns {
|
||||
k := key{id: v.OSV.ID, pkg: v.Package.PkgPath, mod: v.Package.Module.Path}
|
||||
if isExported(v.Symbol) || !hasExported[k] {
|
||||
uniques = append(uniques, v)
|
||||
}
|
||||
}
|
||||
return uniques
|
||||
}
|
||||
|
||||
// isExported checks if the symbol is exported. Assumes that the
|
||||
// symbol is of the form "identifier", "identifier1.identifier2",
|
||||
// or "identifier.".
|
||||
func isExported(symbol string) bool {
|
||||
parts := strings.Split(symbol, ".")
|
||||
last := parts[len(parts)-1]
|
||||
if last == "" { // case for "identifier."
|
||||
return false
|
||||
}
|
||||
return unicode.IsUpper(rune(last[0]))
|
||||
}
|
||||
143
vendor/golang.org/x/vuln/internal/web/url.go
generated
vendored
Normal file
143
vendor/golang.org/x/vuln/internal/web/url.go
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
// Code copied from
|
||||
// https://github.com/golang/go/blob/2ebe77a2fda1ee9ff6fd9a3e08933ad1ebaea039/src/cmd/go/internal/web/url.go
|
||||
// TODO(https://go.dev/issue/32456): if accepted, use the new API.
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var errNotAbsolute = errors.New("path is not absolute")
|
||||
|
||||
// URLToFilePath converts a file-scheme url to a file path.
|
||||
func URLToFilePath(u *url.URL) (string, error) {
|
||||
if u.Scheme != "file" {
|
||||
return "", errors.New("non-file URL")
|
||||
}
|
||||
|
||||
checkAbs := func(path string) (string, error) {
|
||||
if !filepath.IsAbs(path) {
|
||||
return "", errNotAbsolute
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
if u.Path == "" {
|
||||
if u.Host != "" || u.Opaque == "" {
|
||||
return "", errors.New("file URL missing path")
|
||||
}
|
||||
return checkAbs(filepath.FromSlash(u.Opaque))
|
||||
}
|
||||
|
||||
path, err := convertFileURLPath(u.Host, u.Path)
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
return checkAbs(path)
|
||||
}
|
||||
|
||||
// URLFromFilePath converts the given absolute path to a URL.
|
||||
func URLFromFilePath(path string) (*url.URL, error) {
|
||||
if !filepath.IsAbs(path) {
|
||||
return nil, errNotAbsolute
|
||||
}
|
||||
|
||||
// If path has a Windows volume name, convert the volume to a host and prefix
|
||||
// per https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/.
|
||||
if vol := filepath.VolumeName(path); vol != "" {
|
||||
if strings.HasPrefix(vol, `\\`) {
|
||||
path = filepath.ToSlash(path[2:])
|
||||
i := strings.IndexByte(path, '/')
|
||||
|
||||
if i < 0 {
|
||||
// A degenerate case.
|
||||
// \\host.example.com (without a share name)
|
||||
// becomes
|
||||
// file://host.example.com/
|
||||
return &url.URL{
|
||||
Scheme: "file",
|
||||
Host: path,
|
||||
Path: "/",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// \\host.example.com\Share\path\to\file
|
||||
// becomes
|
||||
// file://host.example.com/Share/path/to/file
|
||||
return &url.URL{
|
||||
Scheme: "file",
|
||||
Host: path[:i],
|
||||
Path: filepath.ToSlash(path[i:]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// C:\path\to\file
|
||||
// becomes
|
||||
// file:///C:/path/to/file
|
||||
return &url.URL{
|
||||
Scheme: "file",
|
||||
Path: "/" + filepath.ToSlash(path),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// /path/to/file
|
||||
// becomes
|
||||
// file:///path/to/file
|
||||
return &url.URL{
|
||||
Scheme: "file",
|
||||
Path: filepath.ToSlash(path),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func convertFileURLPath(host, path string) (string, error) {
|
||||
if runtime.GOOS == "windows" {
|
||||
return convertFileURLPathWindows(host, path)
|
||||
}
|
||||
switch host {
|
||||
case "", "localhost":
|
||||
default:
|
||||
return "", errors.New("file URL specifies non-local host")
|
||||
}
|
||||
return filepath.FromSlash(path), nil
|
||||
}
|
||||
|
||||
func convertFileURLPathWindows(host, path string) (string, error) {
|
||||
if len(path) == 0 || path[0] != '/' {
|
||||
return "", errNotAbsolute
|
||||
}
|
||||
|
||||
path = filepath.FromSlash(path)
|
||||
|
||||
// We interpret Windows file URLs per the description in
|
||||
// https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/.
|
||||
|
||||
// The host part of a file URL (if any) is the UNC volume name,
|
||||
// but RFC 8089 reserves the authority "localhost" for the local machine.
|
||||
if host != "" && host != "localhost" {
|
||||
// A common "legacy" format omits the leading slash before a drive letter,
|
||||
// encoding the drive letter as the host instead of part of the path.
|
||||
// (See https://blogs.msdn.microsoft.com/freeassociations/2005/05/19/the-bizarre-and-unhappy-story-of-file-urls/.)
|
||||
// We do not support that format, but we should at least emit a more
|
||||
// helpful error message for it.
|
||||
if filepath.VolumeName(host) != "" {
|
||||
return "", errors.New("file URL encodes volume in host field: too few slashes?")
|
||||
}
|
||||
return `\\` + host + path, nil
|
||||
}
|
||||
|
||||
// If host is empty, path must contain an initial slash followed by a
|
||||
// drive letter and path. Remove the slash and verify that the path is valid.
|
||||
if vol := filepath.VolumeName(path[1:]); vol == "" || strings.HasPrefix(vol, `\\`) {
|
||||
return "", errors.New("file URL missing drive letter")
|
||||
}
|
||||
return path[1:], nil
|
||||
}
|
||||
106
vendor/golang.org/x/vuln/scan/scan.go
generated
vendored
Normal file
106
vendor/golang.org/x/vuln/scan/scan.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
/*
|
||||
Package scan provides functionality for running govulncheck.
|
||||
|
||||
See [cmd/govulncheck/main.go] as a usage example.
|
||||
|
||||
[cmd/govulncheck/main.go]: https://go.googlesource.com/vuln/+/master/cmd/govulncheck/main.go
|
||||
*/
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/vuln/internal/scan"
|
||||
)
|
||||
|
||||
// Cmd represents an external govulncheck command being prepared or run,
|
||||
// similar to exec.Cmd.
|
||||
type Cmd struct {
|
||||
// Stdin specifies the standard input. If provided, it is expected to be
|
||||
// the output of govulncheck -json.
|
||||
Stdin io.Reader
|
||||
|
||||
// Stdout specifies the standard output. If nil, Run connects os.Stdout.
|
||||
Stdout io.Writer
|
||||
|
||||
// Stderr specifies the standard error. If nil, Run connects os.Stderr.
|
||||
Stderr io.Writer
|
||||
|
||||
// Env is the environment to use.
|
||||
// If Env is nil, the current environment is used.
|
||||
// As in os/exec's Cmd, only the last value in the slice for
|
||||
// each environment key is used. To specify the setting of only
|
||||
// a few variables, append to the current environment, as in:
|
||||
//
|
||||
// opt.Env = append(os.Environ(), "GOOS=plan9", "GOARCH=386")
|
||||
//
|
||||
Env []string
|
||||
|
||||
ctx context.Context
|
||||
args []string
|
||||
done chan struct{}
|
||||
err error
|
||||
}
|
||||
|
||||
// Command returns the Cmd struct to execute govulncheck with the given
|
||||
// arguments.
|
||||
func Command(ctx context.Context, arg ...string) *Cmd {
|
||||
return &Cmd{
|
||||
ctx: ctx,
|
||||
args: arg,
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the specified command but does not wait for it to complete.
|
||||
//
|
||||
// After a successful call to Start the Wait method must be called in order to
|
||||
// release associated system resources.
|
||||
func (c *Cmd) Start() error {
|
||||
if c.done != nil {
|
||||
return errors.New("vuln: already started")
|
||||
}
|
||||
if c.Stdin == nil {
|
||||
c.Stdin = os.Stdin
|
||||
}
|
||||
if c.Stdout == nil {
|
||||
c.Stdout = os.Stdout
|
||||
}
|
||||
if c.Stderr == nil {
|
||||
c.Stderr = os.Stderr
|
||||
}
|
||||
if c.Env == nil {
|
||||
c.Env = os.Environ()
|
||||
}
|
||||
c.done = make(chan struct{})
|
||||
go func() {
|
||||
defer close(c.done)
|
||||
c.err = c.scan()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait waits for the command to exit. The command must have been started by
|
||||
// Start.
|
||||
//
|
||||
// Wait releases any resources associated with the Cmd.
|
||||
func (c *Cmd) Wait() error {
|
||||
if c.done == nil {
|
||||
return errors.New("vuln: start must be called before wait")
|
||||
}
|
||||
<-c.done
|
||||
return c.err
|
||||
}
|
||||
|
||||
func (c *Cmd) scan() error {
|
||||
if err := c.ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return scan.RunGovulncheck(c.ctx, c.Env, c.Stdin, c.Stdout, c.Stderr, c.args)
|
||||
}
|
||||
Reference in New Issue
Block a user