Initialize module and dependencies

This commit is contained in:
dwrz
2026-01-04 20:57:40 +00:00
commit a3b390c008
514 changed files with 310495 additions and 0 deletions

27
vendor/golang.org/x/vuln/LICENSE generated vendored Normal file
View 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
View 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
View 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

View 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
View 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
View 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
}

View 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:".

View 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
}

View 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
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

View 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
)

View 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
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 doesnt 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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, ".")
}

View 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
View 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
View 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
View 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)
}