whatcanGOwrong
This commit is contained in:
@@ -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:".
|
||||
@@ -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 {
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// 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"
|
||||
"io"
|
||||
"net/url"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/vuln/internal/gosym"
|
||||
)
|
||||
|
||||
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
|
||||
// bin 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(bin io.ReaderAt) ([]*packages.Module, []Symbol, *debug.BuildInfo, error) {
|
||||
bi, err := buildinfo.Read(bin)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/tools/go/packages/packagestest"
|
||||
"golang.org/x/vuln/internal/test"
|
||||
"golang.org/x/vuln/internal/testenv"
|
||||
)
|
||||
|
||||
// testAll executes testing function ft on all valid combinations
|
||||
// of gooss and goarchs.
|
||||
func testAll(t *testing.T, gooss, goarchs []string, ft func(*testing.T, string, string)) {
|
||||
// unsupported platforms for building Go binaries.
|
||||
var unsupported = map[string]bool{
|
||||
"darwin/386": true,
|
||||
"darwin/arm": true,
|
||||
}
|
||||
|
||||
for _, g := range gooss {
|
||||
for _, a := range goarchs {
|
||||
goos := g
|
||||
goarch := a
|
||||
|
||||
ga := goos + "/" + goarch
|
||||
if unsupported[ga] {
|
||||
continue
|
||||
}
|
||||
|
||||
t.Run(ga, func(t *testing.T) {
|
||||
ft(t, goos, goarch)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractPackagesAndSymbols(t *testing.T) {
|
||||
testAll(t, []string{"linux", "darwin", "windows", "freebsd"}, []string{"amd64", "386", "arm", "arm64"},
|
||||
func(t *testing.T, goos, goarch string) {
|
||||
binary, done := test.GoBuild(t, "testdata", "", false, "GOOS", goos, "GOARCH", goarch)
|
||||
defer done()
|
||||
|
||||
f, err := os.Open(binary)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
_, syms, _, err := ExtractPackagesAndSymbols(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got := sortedSymbols("main", syms)
|
||||
want := []Symbol{
|
||||
{"main", "f"},
|
||||
{"main", "g"},
|
||||
{"main", "main"},
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("(-want,+got):%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// sortedSymbols gets symbols for pkg and
|
||||
// sorts them for testing purposes.
|
||||
func sortedSymbols(pkg string, syms []Symbol) []Symbol {
|
||||
var s []Symbol
|
||||
for _, ps := range syms {
|
||||
if ps.Pkg == pkg {
|
||||
s = append(s, ps)
|
||||
}
|
||||
}
|
||||
sort.SliceStable(s, func(i, j int) bool { return s[i].Pkg+"."+s[i].Name < s[j].Pkg+"."+s[j].Name })
|
||||
return s
|
||||
}
|
||||
|
||||
// Test58509 is supposed to test issue #58509 where a whole
|
||||
// vulnerable function is deleted from the binary so we
|
||||
// cannot detect its presence.
|
||||
//
|
||||
// Note: the issue is still not addressed and the test
|
||||
// expectations are set to fail once it gets addressed.
|
||||
func Test58509(t *testing.T) {
|
||||
testenv.NeedsGoBuild(t)
|
||||
|
||||
vulnLib := `package bvuln
|
||||
|
||||
%s debug = true
|
||||
|
||||
func Vuln() {
|
||||
if debug {
|
||||
return
|
||||
}
|
||||
print("vuln")
|
||||
}`
|
||||
|
||||
for _, tc := range []struct {
|
||||
gl string
|
||||
want bool
|
||||
}{
|
||||
{"const", false}, // TODO(https://go.dev/issue/58509): change expectations once issue is addressed
|
||||
{"var", true},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.gl, func(t *testing.T) {
|
||||
e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{
|
||||
{
|
||||
Name: "golang.org/entry",
|
||||
Files: map[string]interface{}{
|
||||
"main.go": `
|
||||
package main
|
||||
|
||||
import (
|
||||
"golang.org/bmod/bvuln"
|
||||
)
|
||||
|
||||
func main() {
|
||||
bvuln.Vuln()
|
||||
}
|
||||
`,
|
||||
}},
|
||||
{
|
||||
Name: "golang.org/bmod@v0.5.0",
|
||||
Files: map[string]interface{}{"bvuln/bvuln.go": fmt.Sprintf(vulnLib, tc.gl)},
|
||||
},
|
||||
})
|
||||
defer e.Cleanup()
|
||||
|
||||
cmd := exec.Command("go", "build", "-o", "entry")
|
||||
cmd.Dir = e.Config.Dir
|
||||
cmd.Env = e.Config.Env
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil || len(out) > 0 {
|
||||
t.Fatalf("failed to build the binary %v %v", err, string(out))
|
||||
}
|
||||
|
||||
exe, err := os.Open(filepath.Join(e.Config.Dir, "entry"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer exe.Close()
|
||||
|
||||
_, syms, _, err := ExtractPackagesAndSymbols(exe)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// effectively, Vuln is not optimized away from the program
|
||||
got := len(sortedSymbols("golang.org/bmod/bvuln", syms)) != 0
|
||||
if got != tc.want {
|
||||
t.Errorf("want %t; got %t", tc.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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.
|
||||
|
||||
//go:build go1.22
|
||||
// +build go1.22
|
||||
|
||||
package buildinfo
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/vuln/internal/test"
|
||||
)
|
||||
|
||||
// TestStrippedBinary checks that there is no symbol table for
|
||||
// stripped binaries.
|
||||
func TestStrippedBinary(t *testing.T) {
|
||||
testAll(t, []string{"linux", "windows", "freebsd", "darwin"}, []string{"amd64", "386", "arm", "arm64"},
|
||||
func(t *testing.T, goos, goarch string) {
|
||||
binary, done := test.GoBuild(t, "testdata", "", true, "GOOS", goos, "GOARCH", goarch)
|
||||
defer done()
|
||||
|
||||
f, err := os.Open(binary)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
_, syms, _, err := ExtractPackagesAndSymbols(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(syms) != 0 {
|
||||
t.Errorf("want empty symbol table; got %v symbols", len(syms))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
//go:build go1.18 && !go1.22
|
||||
// +build go1.18,!go1.22
|
||||
|
||||
package buildinfo
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/vuln/internal/test"
|
||||
)
|
||||
|
||||
// TestStrippedBinary checks that there is no symbol table for
|
||||
// stripped binaries. This does not include darwin binaries.
|
||||
// For more info, see #61051.
|
||||
func TestStrippedBinary(t *testing.T) {
|
||||
// We exclude darwin as its stripped binaries seem to
|
||||
// preserve the symbol table. See TestStrippedDarwin.
|
||||
testAll(t, []string{"linux", "windows", "freebsd"}, []string{"amd64", "386", "arm", "arm64"},
|
||||
func(t *testing.T, goos, goarch string) {
|
||||
binary, done := test.GoBuild(t, "testdata", "", true, "GOOS", goos, "GOARCH", goarch)
|
||||
defer done()
|
||||
|
||||
f, err := os.Open(binary)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
_, syms, _, err := ExtractPackagesAndSymbols(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if syms != nil {
|
||||
t.Errorf("want empty symbol table; got %v symbols", len(syms))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestStrippedDarwin checks that the symbol table exists and
|
||||
// is complete on darwin even in the presence of stripping.
|
||||
// For more info, see #61051.
|
||||
func TestStrippedDarwin(t *testing.T) {
|
||||
testAll(t, []string{"darwin"}, []string{"amd64", "386"},
|
||||
func(t *testing.T, goos, goarch string) {
|
||||
binary, done := test.GoBuild(t, "testdata", "", true, "GOOS", goos, "GOARCH", goarch)
|
||||
defer done()
|
||||
|
||||
f, err := os.Open(binary)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
_, syms, _, err := ExtractPackagesAndSymbols(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got := sortedSymbols("main", syms)
|
||||
want := []Symbol{
|
||||
{"main", "f"},
|
||||
{"main", "g"},
|
||||
{"main", "main"},
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("(-want,+got):%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
f()
|
||||
}
|
||||
|
||||
func f() {
|
||||
g()
|
||||
g()
|
||||
}
|
||||
|
||||
func g() {
|
||||
println(1)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
"golang.org/x/vuln/internal/web"
|
||||
)
|
||||
|
||||
var (
|
||||
testLegacyVulndb = filepath.Join("testdata", "vulndb-legacy")
|
||||
testLegacyVulndbFileURL = localURL(testLegacyVulndb)
|
||||
testVulndb = filepath.Join("testdata", "vulndb-v1")
|
||||
testVulndbFileURL = localURL(testVulndb)
|
||||
testFlatVulndb = filepath.Join("testdata", "vulndb-v1", "ID")
|
||||
testFlatVulndbFileURL = localURL(testFlatVulndb)
|
||||
testIDs = []string{
|
||||
"GO-2021-0159",
|
||||
"GO-2022-0229",
|
||||
"GO-2022-0463",
|
||||
"GO-2022-0569",
|
||||
"GO-2022-0572",
|
||||
"GO-2021-0068",
|
||||
"GO-2022-0475",
|
||||
"GO-2022-0476",
|
||||
"GO-2021-0240",
|
||||
"GO-2021-0264",
|
||||
"GO-2022-0273",
|
||||
}
|
||||
)
|
||||
|
||||
func newTestServer(dir string) *httptest.Server {
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", http.FileServer(http.Dir(dir)))
|
||||
return httptest.NewServer(mux)
|
||||
}
|
||||
|
||||
func entries(ids []string) ([]*osv.Entry, error) {
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
entries := make([]*osv.Entry, len(ids))
|
||||
for i, id := range ids {
|
||||
b, err := os.ReadFile(filepath.Join(testVulndb, idDir, id+".json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var entry osv.Entry
|
||||
if err := json.Unmarshal(b, &entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries[i] = &entry
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func localURL(dir string) string {
|
||||
absDir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to read %s: %v", dir, err))
|
||||
}
|
||||
u, err := web.URLFromFilePath(absDir)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to read %s: %v", dir, err))
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
t.Run("vuln.go.dev", func(t *testing.T) {
|
||||
src := "https://vuln.go.dev"
|
||||
c, err := NewClient(src, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c == nil {
|
||||
t.Errorf("NewClient(%s) = nil, want instantiated *Client", src)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("http/v1", func(t *testing.T) {
|
||||
srv := newTestServer(testVulndb)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
c, err := NewClient(srv.URL, &Options{HTTPClient: srv.Client()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c == nil {
|
||||
t.Errorf("NewClient(%s) = nil, want instantiated *Client", srv.URL)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("http/legacy", func(t *testing.T) {
|
||||
srv := newTestServer(testLegacyVulndb)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
_, err := NewClient(srv.URL, &Options{HTTPClient: srv.Client()})
|
||||
if err == nil || !errors.Is(err, errUnknownSchema) {
|
||||
t.Errorf("NewClient() = %s, want error %s", err, errUnknownSchema)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("local/v1", func(t *testing.T) {
|
||||
src := testVulndbFileURL
|
||||
c, err := NewClient(src, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c == nil {
|
||||
t.Errorf("NewClient(%s) = nil, want instantiated *Client", src)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("local/flat", func(t *testing.T) {
|
||||
src := testFlatVulndbFileURL
|
||||
c, err := NewClient(src, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c == nil {
|
||||
t.Errorf("NewClient(%s) = nil, want instantiated *Client", src)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("local/legacy", func(t *testing.T) {
|
||||
src := testLegacyVulndbFileURL
|
||||
_, err := NewClient(src, nil)
|
||||
if err == nil || !errors.Is(err, errUnknownSchema) {
|
||||
t.Errorf("NewClient() = %s, want error %s", err, errUnknownSchema)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLastModifiedTime(t *testing.T) {
|
||||
test := func(t *testing.T, c *Client) {
|
||||
got, err := c.LastModifiedTime(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want, err := time.Parse(time.RFC3339, "2023-04-03T15:57:51Z")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("LastModifiedTime = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
testAllClientTypes(t, test)
|
||||
}
|
||||
|
||||
func TestByModules(t *testing.T) {
|
||||
tcs := []struct {
|
||||
module *ModuleRequest
|
||||
wantIDs []string
|
||||
}{
|
||||
{
|
||||
module: &ModuleRequest{
|
||||
Path: "github.com/beego/beego",
|
||||
},
|
||||
wantIDs: []string{"GO-2022-0463", "GO-2022-0569", "GO-2022-0572"},
|
||||
},
|
||||
{
|
||||
module: &ModuleRequest{
|
||||
Path: "github.com/beego/beego",
|
||||
// "GO-2022-0463" not affected at this version.
|
||||
Version: "1.12.10",
|
||||
},
|
||||
wantIDs: []string{"GO-2022-0569", "GO-2022-0572"},
|
||||
},
|
||||
{
|
||||
module: &ModuleRequest{
|
||||
Path: "stdlib",
|
||||
},
|
||||
wantIDs: []string{"GO-2021-0159", "GO-2021-0240", "GO-2021-0264", "GO-2022-0229", "GO-2022-0273"},
|
||||
},
|
||||
{
|
||||
module: &ModuleRequest{
|
||||
Path: "stdlib",
|
||||
Version: "go1.17",
|
||||
},
|
||||
wantIDs: []string{"GO-2021-0264", "GO-2022-0273"},
|
||||
},
|
||||
{
|
||||
module: &ModuleRequest{
|
||||
Path: "toolchain",
|
||||
},
|
||||
wantIDs: []string{"GO-2021-0068", "GO-2022-0475", "GO-2022-0476"},
|
||||
},
|
||||
{
|
||||
module: &ModuleRequest{
|
||||
Path: "toolchain",
|
||||
// All vulns affected at this version.
|
||||
Version: "1.14.13",
|
||||
},
|
||||
wantIDs: []string{"GO-2021-0068", "GO-2022-0475", "GO-2022-0476"},
|
||||
},
|
||||
{
|
||||
module: &ModuleRequest{
|
||||
Path: "golang.org/x/crypto",
|
||||
},
|
||||
wantIDs: []string{"GO-2022-0229"},
|
||||
},
|
||||
{
|
||||
module: &ModuleRequest{
|
||||
Path: "golang.org/x/crypto",
|
||||
// Vuln was fixed at exactly this version.
|
||||
Version: "1.13.7",
|
||||
},
|
||||
wantIDs: nil,
|
||||
},
|
||||
{
|
||||
module: &ModuleRequest{
|
||||
Path: "does.not/exist",
|
||||
},
|
||||
wantIDs: nil,
|
||||
},
|
||||
{
|
||||
module: &ModuleRequest{
|
||||
Path: "does.not/exist",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
wantIDs: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// Test each case as an individual call to ByModules.
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.module.Path+"@"+tc.module.Version, func(t *testing.T) {
|
||||
test := func(t *testing.T, c *Client) {
|
||||
got, err := c.ByModules(context.Background(), []*ModuleRequest{tc.module})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantEntries, err := entries(tc.wantIDs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []*ModuleResponse{{
|
||||
Path: tc.module.Path,
|
||||
Version: tc.module.Version,
|
||||
Entries: wantEntries,
|
||||
}}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("ByModule() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
testAllClientTypes(t, test)
|
||||
})
|
||||
}
|
||||
|
||||
// Now create a single test that makes all the requests
|
||||
// in a single call to ByModules.
|
||||
reqs := make([]*ModuleRequest, len(tcs))
|
||||
want := make([]*ModuleResponse, len(tcs))
|
||||
for i, tc := range tcs {
|
||||
reqs[i] = tc.module
|
||||
wantEntries, err := entries(tc.wantIDs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want[i] = &ModuleResponse{
|
||||
Path: tc.module.Path,
|
||||
Version: tc.module.Version,
|
||||
Entries: wantEntries,
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("all", func(t *testing.T) {
|
||||
test := func(t *testing.T, c *Client) {
|
||||
got, err := c.ByModules(context.Background(), reqs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("ByModules() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
testAllClientTypes(t, test)
|
||||
})
|
||||
}
|
||||
|
||||
// testAllClientTypes runs a given test for all client types.
|
||||
func testAllClientTypes(t *testing.T, test func(t *testing.T, c *Client)) {
|
||||
t.Run("http", func(t *testing.T) {
|
||||
srv := newTestServer(testVulndb)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
hc, err := NewClient(srv.URL, &Options{HTTPClient: srv.Client()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
test(t, hc)
|
||||
})
|
||||
|
||||
t.Run("local", func(t *testing.T) {
|
||||
fc, err := NewClient(testVulndbFileURL, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
test(t, fc)
|
||||
})
|
||||
|
||||
t.Run("hybrid", func(t *testing.T) {
|
||||
fc, err := NewClient(testFlatVulndbFileURL, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
test(t, fc)
|
||||
})
|
||||
|
||||
t.Run("in-memory", func(t *testing.T) {
|
||||
testEntries, err := entries(testIDs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mc, err := NewInMemoryClient(testEntries)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
test(t, mc)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
tcs := []struct {
|
||||
endpoint string
|
||||
}{
|
||||
{
|
||||
endpoint: "index/db",
|
||||
},
|
||||
{
|
||||
endpoint: "index/modules",
|
||||
},
|
||||
{
|
||||
endpoint: "ID/GO-2021-0068",
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
test := func(t *testing.T, s source) {
|
||||
got, err := s.get(context.Background(), tc.endpoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want, err := os.ReadFile(testVulndb + "/" + tc.endpoint + ".json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(got) != string(want) {
|
||||
t.Errorf("get(%s) = %s, want %s", tc.endpoint, got, want)
|
||||
}
|
||||
}
|
||||
testAllSourceTypes(t, test)
|
||||
}
|
||||
}
|
||||
|
||||
// testAllSourceTypes runs a given test for all source types.
|
||||
func testAllSourceTypes(t *testing.T, test func(t *testing.T, s source)) {
|
||||
t.Run("http", func(t *testing.T) {
|
||||
srv := newTestServer(testVulndb)
|
||||
hs := newHTTPSource(srv.URL, &Options{HTTPClient: srv.Client()})
|
||||
test(t, hs)
|
||||
})
|
||||
|
||||
t.Run("local", func(t *testing.T) {
|
||||
test(t, newLocalSource(testVulndb))
|
||||
})
|
||||
|
||||
t.Run("in-memory", func(t *testing.T) {
|
||||
testEntries, err := entries(testIDs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ms, err := newInMemorySource(testEntries)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
test(t, ms)
|
||||
})
|
||||
|
||||
t.Run("hybrid", func(t *testing.T) {
|
||||
hs, err := newHybridSource(testFlatVulndb)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
test(t, hs)
|
||||
})
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"id":"GO-2021-0157","published":"2022-01-05T20:00:00Z","modified":"2022-08-29T16:50:59Z","aliases":["CVE-2015-5739"],"details":"The MIME header parser treated spaces and hyphens\nas equivalent, which can permit HTTP request smuggling.\n","affected":[{"package":{"name":"stdlib","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.4.3"}]}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2021-0157"},"ecosystem_specific":{"imports":[{"path":"net/textproto","symbols":["CanonicalMIMEHeaderKey","canonicalMIMEHeaderKey"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/11772"},{"type":"FIX","url":"https://go.googlesource.com/go/+/117ddcb83d7f42d6aa72241240af99ded81118e9"},{"type":"REPORT","url":"https://go.dev/issue/53035"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/iSIyW4lM4hY/m/ADuQR4DiDwAJ"}]}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"id":"GO-2021-0159","published":"2022-01-05T21:39:14Z","modified":"2022-08-29T16:50:59Z","aliases":["CVE-2015-5739","CVE-2015-5740","CVE-2015-5741"],"details":"HTTP headers were not properly parsed, which allows remote attackers to\nconduct HTTP request smuggling attacks via a request that contains\nContent-Length and Transfer-Encoding header fields.\n","affected":[{"package":{"name":"stdlib","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.4.3"}]}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2021-0159"},"ecosystem_specific":{"imports":[{"path":"net/http","symbols":["CanonicalMIMEHeaderKey","body.readLocked","canonicalMIMEHeaderKey","chunkWriter.writeHeader","fixLength","fixTransferEncoding","readTransfer","transferWriter.shouldSendContentLength","validHeaderFieldByte"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/13148"},{"type":"FIX","url":"https://go.googlesource.com/go/+/26049f6f9171d1190f3bbe05ec304845cfe6399f"},{"type":"FIX","url":"https://go.dev/cl/11772"},{"type":"FIX","url":"https://go.dev/cl/11810"},{"type":"FIX","url":"https://go.dev/cl/12865"},{"type":"FIX","url":"https://go.googlesource.com/go/+/117ddcb83d7f42d6aa72241240af99ded81118e9"},{"type":"FIX","url":"https://go.googlesource.com/go/+/300d9a21583e7cf0149a778a0611e76ff7c6680f"},{"type":"FIX","url":"https://go.googlesource.com/go/+/c2db5f4ccc61ba7df96a747e268a277b802cbb87"},{"type":"REPORT","url":"https://go.dev/issue/12027"},{"type":"REPORT","url":"https://go.dev/issue/11930"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/iSIyW4lM4hY/m/ADuQR4DiDwAJ"}]}
|
||||
Vendored
+293
@@ -0,0 +1,293 @@
|
||||
{
|
||||
"id": "GO-2022-0463",
|
||||
"published": "2022-07-01T20:06:59Z",
|
||||
"modified": "2022-08-19T22:21:47Z",
|
||||
"aliases": [
|
||||
"CVE-2022-31259",
|
||||
"GHSA-qx32-f6g6-fcfr"
|
||||
],
|
||||
"details": "Routes in the beego HTTP router can match unintended patterns.\nThis overly-broad matching may permit an attacker to bypass access\ncontrols.\n\nFor example, the pattern \"/a/b/:name\" can match the URL \"/a.xml/b/\".\nThis may bypass access control applied to the prefix \"/a/\".\n",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/beego/beego",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "0"
|
||||
},
|
||||
{
|
||||
"fixed": "1.12.9"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2022-0463"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/beego/beego",
|
||||
"symbols": [
|
||||
"App.Run",
|
||||
"ControllerRegister.FindPolicy",
|
||||
"ControllerRegister.FindRouter",
|
||||
"ControllerRegister.ServeHTTP",
|
||||
"FilterRouter.ValidRouter",
|
||||
"InitBeegoBeforeTest",
|
||||
"Run",
|
||||
"RunWithMiddleWares",
|
||||
"TestBeegoInit",
|
||||
"Tree.Match",
|
||||
"Tree.match",
|
||||
"adminApp.Run"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/beego/beego/v2",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "0"
|
||||
},
|
||||
{
|
||||
"fixed": "2.0.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2022-0463"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/beego/beego/v2/server/web",
|
||||
"symbols": [
|
||||
"AddNamespace",
|
||||
"Any",
|
||||
"AutoPrefix",
|
||||
"AutoRouter",
|
||||
"Compare",
|
||||
"CompareNot",
|
||||
"Controller.Bind",
|
||||
"Controller.BindForm",
|
||||
"Controller.BindXML",
|
||||
"Controller.BindYAML",
|
||||
"Controller.GetSecureCookie",
|
||||
"Controller.ParseForm",
|
||||
"Controller.Render",
|
||||
"Controller.RenderBytes",
|
||||
"Controller.RenderString",
|
||||
"Controller.Resp",
|
||||
"Controller.SaveToFile",
|
||||
"Controller.ServeFormatted",
|
||||
"Controller.ServeXML",
|
||||
"Controller.ServeYAML",
|
||||
"Controller.SetSecureCookie",
|
||||
"Controller.Trace",
|
||||
"Controller.URLFor",
|
||||
"Controller.XMLResp",
|
||||
"Controller.XSRFFormHTML",
|
||||
"Controller.XSRFToken",
|
||||
"Controller.YamlResp",
|
||||
"ControllerRegister.Add",
|
||||
"ControllerRegister.AddAuto",
|
||||
"ControllerRegister.AddAutoPrefix",
|
||||
"ControllerRegister.AddMethod",
|
||||
"ControllerRegister.AddRouterMethod",
|
||||
"ControllerRegister.Any",
|
||||
"ControllerRegister.CtrlAny",
|
||||
"ControllerRegister.CtrlDelete",
|
||||
"ControllerRegister.CtrlGet",
|
||||
"ControllerRegister.CtrlHead",
|
||||
"ControllerRegister.CtrlOptions",
|
||||
"ControllerRegister.CtrlPatch",
|
||||
"ControllerRegister.CtrlPost",
|
||||
"ControllerRegister.CtrlPut",
|
||||
"ControllerRegister.Delete",
|
||||
"ControllerRegister.FindPolicy",
|
||||
"ControllerRegister.FindRouter",
|
||||
"ControllerRegister.Get",
|
||||
"ControllerRegister.Handler",
|
||||
"ControllerRegister.Head",
|
||||
"ControllerRegister.Include",
|
||||
"ControllerRegister.Init",
|
||||
"ControllerRegister.InsertFilter",
|
||||
"ControllerRegister.Options",
|
||||
"ControllerRegister.Patch",
|
||||
"ControllerRegister.Post",
|
||||
"ControllerRegister.Put",
|
||||
"ControllerRegister.ServeHTTP",
|
||||
"ControllerRegister.URLFor",
|
||||
"CtrlAny",
|
||||
"CtrlDelete",
|
||||
"CtrlGet",
|
||||
"CtrlHead",
|
||||
"CtrlOptions",
|
||||
"CtrlPatch",
|
||||
"CtrlPost",
|
||||
"CtrlPut",
|
||||
"Date",
|
||||
"DateParse",
|
||||
"Delete",
|
||||
"Exception",
|
||||
"ExecuteTemplate",
|
||||
"ExecuteViewPathTemplate",
|
||||
"FilterRouter.ValidRouter",
|
||||
"FlashData.Error",
|
||||
"FlashData.Notice",
|
||||
"FlashData.Set",
|
||||
"FlashData.Store",
|
||||
"FlashData.Success",
|
||||
"FlashData.Warning",
|
||||
"Get",
|
||||
"GetConfig",
|
||||
"HTML2str",
|
||||
"Handler",
|
||||
"Head",
|
||||
"Htmlquote",
|
||||
"Htmlunquote",
|
||||
"HttpServer.Any",
|
||||
"HttpServer.AutoPrefix",
|
||||
"HttpServer.AutoRouter",
|
||||
"HttpServer.CtrlAny",
|
||||
"HttpServer.CtrlDelete",
|
||||
"HttpServer.CtrlGet",
|
||||
"HttpServer.CtrlHead",
|
||||
"HttpServer.CtrlOptions",
|
||||
"HttpServer.CtrlPatch",
|
||||
"HttpServer.CtrlPost",
|
||||
"HttpServer.CtrlPut",
|
||||
"HttpServer.Delete",
|
||||
"HttpServer.Get",
|
||||
"HttpServer.Handler",
|
||||
"HttpServer.Head",
|
||||
"HttpServer.Include",
|
||||
"HttpServer.InsertFilter",
|
||||
"HttpServer.Options",
|
||||
"HttpServer.Patch",
|
||||
"HttpServer.Post",
|
||||
"HttpServer.PrintTree",
|
||||
"HttpServer.Put",
|
||||
"HttpServer.RESTRouter",
|
||||
"HttpServer.Router",
|
||||
"HttpServer.RouterWithOpts",
|
||||
"HttpServer.Run",
|
||||
"Include",
|
||||
"InitBeegoBeforeTest",
|
||||
"InsertFilter",
|
||||
"LoadAppConfig",
|
||||
"MapGet",
|
||||
"Namespace.Any",
|
||||
"Namespace.AutoPrefix",
|
||||
"Namespace.AutoRouter",
|
||||
"Namespace.Cond",
|
||||
"Namespace.CtrlAny",
|
||||
"Namespace.CtrlDelete",
|
||||
"Namespace.CtrlGet",
|
||||
"Namespace.CtrlHead",
|
||||
"Namespace.CtrlOptions",
|
||||
"Namespace.CtrlPatch",
|
||||
"Namespace.CtrlPost",
|
||||
"Namespace.CtrlPut",
|
||||
"Namespace.Delete",
|
||||
"Namespace.Filter",
|
||||
"Namespace.Get",
|
||||
"Namespace.Handler",
|
||||
"Namespace.Head",
|
||||
"Namespace.Include",
|
||||
"Namespace.Namespace",
|
||||
"Namespace.Options",
|
||||
"Namespace.Patch",
|
||||
"Namespace.Post",
|
||||
"Namespace.Put",
|
||||
"Namespace.Router",
|
||||
"NewControllerRegister",
|
||||
"NewControllerRegisterWithCfg",
|
||||
"NewHttpServerWithCfg",
|
||||
"NewHttpSever",
|
||||
"NewNamespace",
|
||||
"NotNil",
|
||||
"Options",
|
||||
"ParseForm",
|
||||
"Patch",
|
||||
"Policy",
|
||||
"Post",
|
||||
"PrintTree",
|
||||
"Put",
|
||||
"RESTRouter",
|
||||
"ReadFromRequest",
|
||||
"RenderForm",
|
||||
"Router",
|
||||
"RouterWithOpts",
|
||||
"Run",
|
||||
"RunWithMiddleWares",
|
||||
"TestBeegoInit",
|
||||
"Tree.AddRouter",
|
||||
"Tree.AddTree",
|
||||
"Tree.Match",
|
||||
"Tree.match",
|
||||
"URLFor",
|
||||
"URLMap.GetMap",
|
||||
"URLMap.GetMapData",
|
||||
"adminApp.Run",
|
||||
"adminController.AdminIndex",
|
||||
"adminController.Healthcheck",
|
||||
"adminController.ListConf",
|
||||
"adminController.ProfIndex",
|
||||
"adminController.PrometheusMetrics",
|
||||
"adminController.QpsIndex",
|
||||
"adminController.TaskStatus",
|
||||
"beegoAppConfig.Bool",
|
||||
"beegoAppConfig.DefaultBool"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/beego/beego/pull/4958"
|
||||
},
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/beego/beego/commit/64cf44d725c8cc35d782327d333df9cbeb1bf2dd"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://beego.vip"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/beego/beego/issues/4946"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/beego/beego/pull/4954"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-31259"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/advisories/GHSA-qx32-f6g6-fcfr"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"id": "GO-2022-0569",
|
||||
"published": "2022-08-23T13:24:17Z",
|
||||
"modified": "2022-08-23T13:24:17Z",
|
||||
"aliases": [
|
||||
"CVE-2022-31836",
|
||||
"GHSA-95f9-94vc-665h"
|
||||
],
|
||||
"details": "The leafInfo.match() function uses path.join()\nto deal with wildcard values which can lead to cross directory risk.\n",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/beego/beego",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "0"
|
||||
},
|
||||
{
|
||||
"fixed": "1.12.11"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2022-0569"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/beego/beego",
|
||||
"symbols": [
|
||||
"Tree.Match"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/beego/beego/v2",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "2.0.0"
|
||||
},
|
||||
{
|
||||
"fixed": "2.0.4"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2022-0569"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/beego/beego/v2/server/web",
|
||||
"symbols": [
|
||||
"Tree.Match"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/beego/beego/pull/5025"
|
||||
},
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/beego/beego/pull/5025/commits/ea5ae58d40589d249cf577a053e490509de2bf57"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-31836"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/advisories/GHSA-95f9-94vc-665h"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+91
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"id": "GO-2022-0572",
|
||||
"published": "2022-08-22T17:56:17Z",
|
||||
"modified": "2022-08-23T19:54:38Z",
|
||||
"aliases": [
|
||||
"CVE-2021-30080",
|
||||
"GHSA-28r6-jm5h-mrgg"
|
||||
],
|
||||
"details": "An issue was discovered in the route lookup process in\nbeego which attackers to bypass access control.\n",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/beego/beego",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2022-0572"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/beego/beego",
|
||||
"symbols": [
|
||||
"Tree.Match"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/beego/beego/v2",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "2.0.0"
|
||||
},
|
||||
{
|
||||
"fixed": "2.0.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2022-0572"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/beego/beego/v2/server/web",
|
||||
"symbols": [
|
||||
"Tree.Match"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/beego/beego/pull/4459"
|
||||
},
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/beego/beego/commit/d5df5e470d0a8ed291930ae802fd7e6b95226519"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-30080"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/advisories/GHSA-28r6-jm5h-mrgg"
|
||||
}
|
||||
]
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
[
|
||||
"GO-2022-0463",
|
||||
"GO-2022-0569",
|
||||
"GO-2022-0572"
|
||||
]
|
||||
+1204
File diff suppressed because it is too large
Load Diff
+480
@@ -0,0 +1,480 @@
|
||||
[
|
||||
{
|
||||
"id": "GO-2022-0463",
|
||||
"published": "2022-07-01T20:06:59Z",
|
||||
"modified": "2022-08-19T22:21:47Z",
|
||||
"aliases": [
|
||||
"CVE-2022-31259",
|
||||
"GHSA-qx32-f6g6-fcfr"
|
||||
],
|
||||
"details": "Routes in the beego HTTP router can match unintended patterns.\nThis overly-broad matching may permit an attacker to bypass access\ncontrols.\n\nFor example, the pattern \"/a/b/:name\" can match the URL \"/a.xml/b/\".\nThis may bypass access control applied to the prefix \"/a/\".\n",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/beego/beego",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "0"
|
||||
},
|
||||
{
|
||||
"fixed": "1.12.9"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2022-0463"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/beego/beego",
|
||||
"symbols": [
|
||||
"App.Run",
|
||||
"ControllerRegister.FindPolicy",
|
||||
"ControllerRegister.FindRouter",
|
||||
"ControllerRegister.ServeHTTP",
|
||||
"FilterRouter.ValidRouter",
|
||||
"InitBeegoBeforeTest",
|
||||
"Run",
|
||||
"RunWithMiddleWares",
|
||||
"TestBeegoInit",
|
||||
"Tree.Match",
|
||||
"Tree.match",
|
||||
"adminApp.Run"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/beego/beego/v2",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "0"
|
||||
},
|
||||
{
|
||||
"fixed": "2.0.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2022-0463"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/beego/beego/v2/server/web",
|
||||
"symbols": [
|
||||
"AddNamespace",
|
||||
"Any",
|
||||
"AutoPrefix",
|
||||
"AutoRouter",
|
||||
"Compare",
|
||||
"CompareNot",
|
||||
"Controller.Bind",
|
||||
"Controller.BindForm",
|
||||
"Controller.BindXML",
|
||||
"Controller.BindYAML",
|
||||
"Controller.GetSecureCookie",
|
||||
"Controller.ParseForm",
|
||||
"Controller.Render",
|
||||
"Controller.RenderBytes",
|
||||
"Controller.RenderString",
|
||||
"Controller.Resp",
|
||||
"Controller.SaveToFile",
|
||||
"Controller.ServeFormatted",
|
||||
"Controller.ServeXML",
|
||||
"Controller.ServeYAML",
|
||||
"Controller.SetSecureCookie",
|
||||
"Controller.Trace",
|
||||
"Controller.URLFor",
|
||||
"Controller.XMLResp",
|
||||
"Controller.XSRFFormHTML",
|
||||
"Controller.XSRFToken",
|
||||
"Controller.YamlResp",
|
||||
"ControllerRegister.Add",
|
||||
"ControllerRegister.AddAuto",
|
||||
"ControllerRegister.AddAutoPrefix",
|
||||
"ControllerRegister.AddMethod",
|
||||
"ControllerRegister.AddRouterMethod",
|
||||
"ControllerRegister.Any",
|
||||
"ControllerRegister.CtrlAny",
|
||||
"ControllerRegister.CtrlDelete",
|
||||
"ControllerRegister.CtrlGet",
|
||||
"ControllerRegister.CtrlHead",
|
||||
"ControllerRegister.CtrlOptions",
|
||||
"ControllerRegister.CtrlPatch",
|
||||
"ControllerRegister.CtrlPost",
|
||||
"ControllerRegister.CtrlPut",
|
||||
"ControllerRegister.Delete",
|
||||
"ControllerRegister.FindPolicy",
|
||||
"ControllerRegister.FindRouter",
|
||||
"ControllerRegister.Get",
|
||||
"ControllerRegister.Handler",
|
||||
"ControllerRegister.Head",
|
||||
"ControllerRegister.Include",
|
||||
"ControllerRegister.Init",
|
||||
"ControllerRegister.InsertFilter",
|
||||
"ControllerRegister.Options",
|
||||
"ControllerRegister.Patch",
|
||||
"ControllerRegister.Post",
|
||||
"ControllerRegister.Put",
|
||||
"ControllerRegister.ServeHTTP",
|
||||
"ControllerRegister.URLFor",
|
||||
"CtrlAny",
|
||||
"CtrlDelete",
|
||||
"CtrlGet",
|
||||
"CtrlHead",
|
||||
"CtrlOptions",
|
||||
"CtrlPatch",
|
||||
"CtrlPost",
|
||||
"CtrlPut",
|
||||
"Date",
|
||||
"DateParse",
|
||||
"Delete",
|
||||
"Exception",
|
||||
"ExecuteTemplate",
|
||||
"ExecuteViewPathTemplate",
|
||||
"FilterRouter.ValidRouter",
|
||||
"FlashData.Error",
|
||||
"FlashData.Notice",
|
||||
"FlashData.Set",
|
||||
"FlashData.Store",
|
||||
"FlashData.Success",
|
||||
"FlashData.Warning",
|
||||
"Get",
|
||||
"GetConfig",
|
||||
"HTML2str",
|
||||
"Handler",
|
||||
"Head",
|
||||
"Htmlquote",
|
||||
"Htmlunquote",
|
||||
"HttpServer.Any",
|
||||
"HttpServer.AutoPrefix",
|
||||
"HttpServer.AutoRouter",
|
||||
"HttpServer.CtrlAny",
|
||||
"HttpServer.CtrlDelete",
|
||||
"HttpServer.CtrlGet",
|
||||
"HttpServer.CtrlHead",
|
||||
"HttpServer.CtrlOptions",
|
||||
"HttpServer.CtrlPatch",
|
||||
"HttpServer.CtrlPost",
|
||||
"HttpServer.CtrlPut",
|
||||
"HttpServer.Delete",
|
||||
"HttpServer.Get",
|
||||
"HttpServer.Handler",
|
||||
"HttpServer.Head",
|
||||
"HttpServer.Include",
|
||||
"HttpServer.InsertFilter",
|
||||
"HttpServer.Options",
|
||||
"HttpServer.Patch",
|
||||
"HttpServer.Post",
|
||||
"HttpServer.PrintTree",
|
||||
"HttpServer.Put",
|
||||
"HttpServer.RESTRouter",
|
||||
"HttpServer.Router",
|
||||
"HttpServer.RouterWithOpts",
|
||||
"HttpServer.Run",
|
||||
"Include",
|
||||
"InitBeegoBeforeTest",
|
||||
"InsertFilter",
|
||||
"LoadAppConfig",
|
||||
"MapGet",
|
||||
"Namespace.Any",
|
||||
"Namespace.AutoPrefix",
|
||||
"Namespace.AutoRouter",
|
||||
"Namespace.Cond",
|
||||
"Namespace.CtrlAny",
|
||||
"Namespace.CtrlDelete",
|
||||
"Namespace.CtrlGet",
|
||||
"Namespace.CtrlHead",
|
||||
"Namespace.CtrlOptions",
|
||||
"Namespace.CtrlPatch",
|
||||
"Namespace.CtrlPost",
|
||||
"Namespace.CtrlPut",
|
||||
"Namespace.Delete",
|
||||
"Namespace.Filter",
|
||||
"Namespace.Get",
|
||||
"Namespace.Handler",
|
||||
"Namespace.Head",
|
||||
"Namespace.Include",
|
||||
"Namespace.Namespace",
|
||||
"Namespace.Options",
|
||||
"Namespace.Patch",
|
||||
"Namespace.Post",
|
||||
"Namespace.Put",
|
||||
"Namespace.Router",
|
||||
"NewControllerRegister",
|
||||
"NewControllerRegisterWithCfg",
|
||||
"NewHttpServerWithCfg",
|
||||
"NewHttpSever",
|
||||
"NewNamespace",
|
||||
"NotNil",
|
||||
"Options",
|
||||
"ParseForm",
|
||||
"Patch",
|
||||
"Policy",
|
||||
"Post",
|
||||
"PrintTree",
|
||||
"Put",
|
||||
"RESTRouter",
|
||||
"ReadFromRequest",
|
||||
"RenderForm",
|
||||
"Router",
|
||||
"RouterWithOpts",
|
||||
"Run",
|
||||
"RunWithMiddleWares",
|
||||
"TestBeegoInit",
|
||||
"Tree.AddRouter",
|
||||
"Tree.AddTree",
|
||||
"Tree.Match",
|
||||
"Tree.match",
|
||||
"URLFor",
|
||||
"URLMap.GetMap",
|
||||
"URLMap.GetMapData",
|
||||
"adminApp.Run",
|
||||
"adminController.AdminIndex",
|
||||
"adminController.Healthcheck",
|
||||
"adminController.ListConf",
|
||||
"adminController.ProfIndex",
|
||||
"adminController.PrometheusMetrics",
|
||||
"adminController.QpsIndex",
|
||||
"adminController.TaskStatus",
|
||||
"beegoAppConfig.Bool",
|
||||
"beegoAppConfig.DefaultBool"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/beego/beego/pull/4958"
|
||||
},
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/beego/beego/commit/64cf44d725c8cc35d782327d333df9cbeb1bf2dd"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://beego.vip"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/beego/beego/issues/4946"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/beego/beego/pull/4954"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-31259"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/advisories/GHSA-qx32-f6g6-fcfr"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "GO-2022-0569",
|
||||
"published": "2022-08-23T13:24:17Z",
|
||||
"modified": "2022-08-23T13:24:17Z",
|
||||
"aliases": [
|
||||
"CVE-2022-31836",
|
||||
"GHSA-95f9-94vc-665h"
|
||||
],
|
||||
"details": "The leafInfo.match() function uses path.join()\nto deal with wildcard values which can lead to cross directory risk.\n",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/beego/beego",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "0"
|
||||
},
|
||||
{
|
||||
"fixed": "1.12.11"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2022-0569"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/beego/beego",
|
||||
"symbols": [
|
||||
"Tree.Match"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/beego/beego/v2",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "2.0.0"
|
||||
},
|
||||
{
|
||||
"fixed": "2.0.4"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2022-0569"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/beego/beego/v2/server/web",
|
||||
"symbols": [
|
||||
"Tree.Match"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/beego/beego/pull/5025"
|
||||
},
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/beego/beego/pull/5025/commits/ea5ae58d40589d249cf577a053e490509de2bf57"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-31836"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/advisories/GHSA-95f9-94vc-665h"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "GO-2022-0572",
|
||||
"published": "2022-08-22T17:56:17Z",
|
||||
"modified": "2022-08-23T19:54:38Z",
|
||||
"aliases": [
|
||||
"CVE-2021-30080",
|
||||
"GHSA-28r6-jm5h-mrgg"
|
||||
],
|
||||
"details": "An issue was discovered in the route lookup process in\nbeego which attackers to bypass access control.\n",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/beego/beego",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2022-0572"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/beego/beego",
|
||||
"symbols": [
|
||||
"Tree.Match"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/beego/beego/v2",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "2.0.0"
|
||||
},
|
||||
{
|
||||
"fixed": "2.0.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2022-0572"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/beego/beego/v2/server/web",
|
||||
"symbols": [
|
||||
"Tree.Match"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/beego/beego/pull/4459"
|
||||
},
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/beego/beego/commit/d5df5e470d0a8ed291930ae802fd7e6b95226519"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-30080"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/advisories/GHSA-28r6-jm5h-mrgg"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
[
|
||||
{"id": "GO-2021-0054",
|
||||
"published": "2021-04-14T20:04:52Z",
|
||||
"modified": "2022-08-19T22:21:47Z",
|
||||
"aliases": [
|
||||
"CVE-2020-36067"
|
||||
],
|
||||
"details": "Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector.",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/tidwall/gjson",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "0"
|
||||
},
|
||||
{
|
||||
"fixed": "1.6.6"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2021-0054"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/tidwall/gjson",
|
||||
"symbols": [
|
||||
"Result.ForEach",
|
||||
"unwrap"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/tidwall/gjson/commit/bf4efcb3c18d1825b2988603dea5909140a5302b"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/tidwall/gjson/issues/196"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2020-36067"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "GO-2021-0059",
|
||||
"published": "2021-04-14T20:04:52Z",
|
||||
"modified": "2022-08-19T22:21:47Z",
|
||||
"aliases": [
|
||||
"CVE-2020-35380",
|
||||
"GHSA-w942-gw6m-p62c"
|
||||
],
|
||||
"details": "Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector.",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/tidwall/gjson",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "0"
|
||||
},
|
||||
{
|
||||
"fixed": "1.6.4"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2021-0059"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/tidwall/gjson",
|
||||
"symbols": [
|
||||
"sqaush"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/tidwall/gjson/commit/f0ee9ebde4b619767ae4ac03e8e42addb530f6bc"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/tidwall/gjson/issues/192"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2020-35380"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/advisories/GHSA-w942-gw6m-p62c"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "GO-2021-0265",
|
||||
"published": "2022-01-14T17:30:24Z",
|
||||
"modified": "2022-08-19T22:21:47Z",
|
||||
"aliases": [
|
||||
"CVE-2020-36066",
|
||||
"CVE-2021-42836",
|
||||
"GHSA-ppj4-34rq-v8j9",
|
||||
"GHSA-wjm3-fq3r-5x46"
|
||||
],
|
||||
"details": "GJSON allowed a ReDoS (regular expression denial of service) attack.",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/tidwall/gjson",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "0"
|
||||
},
|
||||
{
|
||||
"fixed": "1.9.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2021-0265"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/tidwall/gjson",
|
||||
"symbols": [
|
||||
"match.Match"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/tidwall/gjson/commit/590010fdac311cc8990ef5c97448d4fec8f29944"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/tidwall/gjson/compare/v1.9.2...v1.9.3"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/tidwall/gjson/issues/236"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/tidwall/gjson/issues/237"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2020-36066"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-42836"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/advisories/GHSA-ppj4-34rq-v8j9"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/advisories/GHSA-wjm3-fq3r-5x46"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "GO-2022-0592",
|
||||
"published": "2022-08-15T18:06:07Z",
|
||||
"modified": "2022-08-19T22:21:47Z",
|
||||
"aliases": [
|
||||
"CVE-2021-42248",
|
||||
"GHSA-c9gm-7rfj-8w5h"
|
||||
],
|
||||
"details": "A maliciously crafted path can cause Get and other query functions to consume excessive amounts of CPU and time.",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "github.com/tidwall/gjson",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "0"
|
||||
},
|
||||
{
|
||||
"fixed": "1.9.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2022-0592"
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "github.com/tidwall/gjson",
|
||||
"symbols": [
|
||||
"Get",
|
||||
"GetBytes",
|
||||
"GetMany",
|
||||
"GetManyBytes",
|
||||
"Result.Get",
|
||||
"queryMatches"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://github.com/tidwall/gjson/commit/77a57fda87dca6d0d7d4627d512a630f89a91c96"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/tidwall/gjson/issues/237"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-42248"
|
||||
},
|
||||
{
|
||||
"type": "WEB",
|
||||
"url": "https://github.com/advisories/GHSA-c9gm-7rfj-8w5h"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"github.com/BeeGo/beego": "2022-08-23T19:54:38Z",
|
||||
"github.com/tidwall/gjson": "2022-08-23T19:54:38Z"
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"schema_version":"1.3.1","id":"GO-2021-0068","modified":"2023-04-03T15:57:51Z","published":"2021-04-14T20:04:52Z","aliases":["CVE-2021-3115"],"details":"The go command may execute arbitrary code at build time when using cgo on Windows. This can be triggered by running go get on a malicious module, or any other time the code is built.","affected":[{"package":{"name":"toolchain","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.14.14"},{"introduced":"1.15.0"},{"fixed":"1.15.7"}]}],"ecosystem_specific":{"imports":[{"path":"cmd/go","goos":["windows"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/284783"},{"type":"FIX","url":"https://go.googlesource.com/go/+/953d1feca9b21af075ad5fc8a3dad096d3ccc3a0"},{"type":"REPORT","url":"https://go.dev/issue/43783"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/mperVMGa98w/m/yo5W5wnvAAAJ"},{"type":"FIX","url":"https://go.dev/cl/284780"},{"type":"FIX","url":"https://go.googlesource.com/go/+/46e2e2e9d99925bbf724b12693c6d3e27a95d6a0"}],"credits":[{"name":"RyotaK"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2021-0068"}}
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"schema_version":"1.3.1","id":"GO-2021-0159","modified":"2023-04-03T15:57:51Z","published":"2022-01-05T21:39:14Z","aliases":["CVE-2015-5739","CVE-2015-5740","CVE-2015-5741"],"details":"HTTP headers were not properly parsed, which allows remote attackers to conduct HTTP request smuggling attacks via a request that contains Content-Length and Transfer-Encoding header fields.","affected":[{"package":{"name":"stdlib","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.4.3"}]}],"ecosystem_specific":{"imports":[{"path":"net/http","symbols":["CanonicalMIMEHeaderKey","body.readLocked","canonicalMIMEHeaderKey","chunkWriter.writeHeader","fixLength","fixTransferEncoding","readTransfer","transferWriter.shouldSendContentLength","validHeaderFieldByte"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/13148"},{"type":"FIX","url":"https://go.googlesource.com/go/+/26049f6f9171d1190f3bbe05ec304845cfe6399f"},{"type":"FIX","url":"https://go.dev/cl/11772"},{"type":"FIX","url":"https://go.dev/cl/11810"},{"type":"FIX","url":"https://go.dev/cl/12865"},{"type":"FIX","url":"https://go.googlesource.com/go/+/117ddcb83d7f42d6aa72241240af99ded81118e9"},{"type":"FIX","url":"https://go.googlesource.com/go/+/300d9a21583e7cf0149a778a0611e76ff7c6680f"},{"type":"FIX","url":"https://go.googlesource.com/go/+/c2db5f4ccc61ba7df96a747e268a277b802cbb87"},{"type":"REPORT","url":"https://go.dev/issue/12027"},{"type":"REPORT","url":"https://go.dev/issue/11930"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/iSIyW4lM4hY/m/ADuQR4DiDwAJ"}],"credits":[{"name":"Jed Denlea and Régis Leroy"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2021-0159"}}
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"schema_version":"1.3.1","id":"GO-2021-0240","modified":"2023-04-03T15:57:51Z","published":"2022-02-17T17:33:25Z","aliases":["CVE-2021-33196"],"details":"NewReader and OpenReader can cause a panic or an unrecoverable fatal error when reading an archive that claims to contain a large number of files, regardless of its actual size.","affected":[{"package":{"name":"stdlib","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.15.13"},{"introduced":"1.16.0"},{"fixed":"1.16.5"}]}],"ecosystem_specific":{"imports":[{"path":"archive/zip","symbols":["Reader.init"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/318909"},{"type":"FIX","url":"https://go.googlesource.com/go/+/74242baa4136c7a9132a8ccd9881354442788c8c"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/RgCMkAEQjSI"},{"type":"REPORT","url":"https://go.dev/issue/46242"}],"credits":[{"name":"the OSS-Fuzz project for discovering this issue and\nEmmanuel Odeke for reporting it\n"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2021-0240"}}
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"schema_version":"1.3.1","id":"GO-2021-0264","modified":"2023-04-03T15:57:51Z","published":"2022-01-13T20:54:43Z","aliases":["CVE-2021-41772"],"details":"Previously, opening a zip with (*Reader).Open could result in a panic if the zip contained a file whose name was exclusively made up of slash characters or \"..\" path elements.\n\nOpen could also panic if passed the empty string directly as an argument.\n\nNow, any files in the zip whose name could not be made valid for fs.FS.Open will be skipped, and no longer added to the fs.FS file list, although they are still accessible through (*Reader).File.\n\nNote that it was already the case that a file could be accessible from (*Reader).Open with a name different from the one in (*Reader).File, as the former is the cleaned name, while the latter is the original one.\n\nFinally, the actual panic site was made robust as a defense-in-depth measure.","affected":[{"package":{"name":"stdlib","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.16.10"},{"introduced":"1.17.0"},{"fixed":"1.17.3"}]}],"ecosystem_specific":{"imports":[{"path":"archive/zip","symbols":["Reader.Open","split"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/349770"},{"type":"FIX","url":"https://go.googlesource.com/go/+/b24687394b55a93449e2be4e6892ead58ea9a10f"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/0fM21h43arc"},{"type":"REPORT","url":"https://go.dev/issue/48085"}],"credits":[{"name":"Colin Arnott, SiteHost and Noah Santschi-Cooney, Sourcegraph Code Intelligence Team"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2021-0264"}}
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"schema_version":"1.3.1","id":"GO-2022-0229","modified":"2023-04-03T15:57:51Z","published":"2022-07-06T18:23:48Z","aliases":["CVE-2020-7919","GHSA-cjjc-xp8v-855w"],"details":"On 32-bit architectures, a malformed input to crypto/x509 or the ASN.1 parsing functions of golang.org/x/crypto/cryptobyte can lead to a panic.\n\nThe malformed certificate can be delivered via a crypto/tls connection to a client, or to a server that accepts client certificates. net/http clients can be made to crash by an HTTPS server, while net/http servers that accept client certificates will recover the panic and are unaffected.","affected":[{"package":{"name":"stdlib","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.12.16"},{"introduced":"1.13.0"},{"fixed":"1.13.7"}]}],"ecosystem_specific":{"imports":[{"path":"crypto/x509"}]}},{"package":{"name":"golang.org/x/crypto","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.0.0-20200124225646-8b5121be2f68"}]}],"ecosystem_specific":{"imports":[{"path":"golang.org/x/crypto/cryptobyte"}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/216680"},{"type":"FIX","url":"https://go.googlesource.com/go/+/b13ce14c4a6aa59b7b041ad2b6eed2d23e15b574"},{"type":"FIX","url":"https://go.dev/cl/216677"},{"type":"REPORT","url":"https://go.dev/issue/36837"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/Hsw4mHYc470"}],"credits":[{"name":"Project Wycheproof"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0229"}}
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"schema_version":"1.3.1","id":"GO-2022-0273","modified":"2023-04-03T15:57:51Z","published":"2022-05-18T18:23:31Z","aliases":["CVE-2021-39293"],"details":"The NewReader and OpenReader functions in archive/zip can cause a panic or an unrecoverable fatal error when reading an archive that claims to contain a large number of files, regardless of its actual size. This is caused by an incomplete fix for CVE-2021-33196.","affected":[{"package":{"name":"stdlib","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.16.8"},{"introduced":"1.17.0"},{"fixed":"1.17.1"}]}],"ecosystem_specific":{"imports":[{"path":"archive/zip","symbols":["NewReader","OpenReader"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/343434"},{"type":"FIX","url":"https://go.googlesource.com/go/+/bacbc33439b124ffd7392c91a5f5d96eca8c0c0b"},{"type":"REPORT","url":"https://go.dev/issue/47801"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/dx9d7IOseHw"}],"credits":[{"name":"OSS-Fuzz Project and Emmanuel Odeke"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0273"}}
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
BIN
Binary file not shown.
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"schema_version":"1.3.1","id":"GO-2022-0475","modified":"2023-04-03T15:57:51Z","published":"2022-07-28T17:24:30Z","aliases":["CVE-2020-28366"],"details":"The go command may execute arbitrary code at build time when cgo is in use. This may occur when running go get on a malicious package, or any other command that builds untrusted code.\n\nThis can be caused by malicious unquoted symbol name in a linked object file.","affected":[{"package":{"name":"toolchain","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.14.12"},{"introduced":"1.15.0"},{"fixed":"1.15.5"}]}],"ecosystem_specific":{"imports":[{"path":"cmd/go","symbols":["Builder.cgo"]},{"path":"cmd/cgo","symbols":["dynimport"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/269658"},{"type":"FIX","url":"https://go.googlesource.com/go/+/062e0e5ce6df339dc26732438ad771f73dbf2292"},{"type":"REPORT","url":"https://go.dev/issue/42559"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM"}],"credits":[{"name":"Chris Brown and Tempus Ex"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0475"}}
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"schema_version":"1.3.1","id":"GO-2022-0476","modified":"2023-04-03T15:57:51Z","published":"2022-07-28T17:24:43Z","aliases":["CVE-2020-28367"],"details":"The go command may execute arbitrary code at build time when cgo is in use. This may occur when running go get on a malicious package, or any other command that builds untrusted code.\n\nThis can be caused by malicious gcc flags specified via a cgo directive.","affected":[{"package":{"name":"toolchain","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.14.12"},{"introduced":"1.15.0"},{"fixed":"1.15.5"}]}],"ecosystem_specific":{"imports":[{"path":"cmd/go","symbols":["validCompilerFlags"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/267277"},{"type":"FIX","url":"https://go.googlesource.com/go/+/da7aa86917811a571e6634b45a457f918b8e6561"},{"type":"REPORT","url":"https://go.dev/issue/42556"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM"}],"credits":[{"name":"Imre Rad"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0476"}}
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
BIN
Binary file not shown.
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
{"modified":"2023-04-03T15:57:51Z"}
|
||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
[{"path":"github.com/astaxie/beego","vulns":[{"id":"GO-2022-0463","modified":"2023-04-03T15:57:51Z"},{"id":"GO-2022-0569","modified":"2023-04-03T15:57:51Z"},{"id":"GO-2022-0572","modified":"2023-04-03T15:57:51Z"}]},{"path":"github.com/beego/beego","vulns":[{"id":"GO-2022-0463","modified":"2023-04-03T15:57:51Z","fixed":"1.12.9"},{"id":"GO-2022-0569","modified":"2023-04-03T15:57:51Z","fixed":"1.12.11"},{"id":"GO-2022-0572","modified":"2023-04-03T15:57:51Z"}]},{"path":"github.com/beego/beego/v2","vulns":[{"id":"GO-2022-0463","modified":"2023-04-03T15:57:51Z","fixed":"2.0.3"},{"id":"GO-2022-0569","modified":"2023-04-03T15:57:51Z","fixed":"2.0.4"},{"id":"GO-2022-0572","modified":"2023-04-03T15:57:51Z","fixed":"2.0.3"}]},{"path":"golang.org/x/crypto","vulns":[{"id":"GO-2022-0229","modified":"2023-04-03T15:57:51Z","fixed":"0.0.0-20200124225646-8b5121be2f68"}]},{"path":"stdlib","vulns":[{"id":"GO-2021-0159","modified":"2023-04-03T15:57:51Z","fixed":"1.4.3"},{"id":"GO-2021-0240","modified":"2023-04-03T15:57:51Z","fixed":"1.16.5"},{"id":"GO-2021-0264","modified":"2023-04-03T15:57:51Z","fixed":"1.17.3"},{"id":"GO-2022-0229","modified":"2023-04-03T15:57:51Z","fixed":"1.13.7"},{"id":"GO-2022-0273","modified":"2023-04-03T15:57:51Z","fixed":"1.17.1"}]},{"path":"toolchain","vulns":[{"id":"GO-2021-0068","modified":"2023-04-03T15:57:51Z","fixed":"1.15.7"},{"id":"GO-2022-0475","modified":"2023-04-03T15:57:51Z","fixed":"1.15.5"},{"id":"GO-2022-0476","modified":"2023-04-03T15:57:51Z","fixed":"1.15.5"}]}]
|
||||
Vendored
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
[{"id":"GO-2021-0068","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2021-3115"]},{"id":"GO-2021-0159","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2015-5739","CVE-2015-5740","CVE-2015-5741"]},{"id":"GO-2021-0240","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2021-33196"]},{"id":"GO-2021-0264","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2021-41772"]},{"id":"GO-2022-0229","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2020-7919","GHSA-cjjc-xp8v-855w"]},{"id":"GO-2022-0273","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2021-39293"]},{"id":"GO-2022-0463","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2022-31259","GHSA-qx32-f6g6-fcfr"]},{"id":"GO-2022-0475","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2020-28366"]},{"id":"GO-2022-0476","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2020-28367"]},{"id":"GO-2022-0569","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2022-31836","GHSA-95f9-94vc-665h"]},{"id":"GO-2022-0572","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2021-30080","GHSA-28r6-jm5h-mrgg"]}]
|
||||
BIN
Binary file not shown.
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
var ics []InlinedCall
|
||||
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
|
||||
}
|
||||
@@ -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 gosym
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
)
|
||||
|
||||
func TestFuncSymName(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
v string
|
||||
want string
|
||||
}{
|
||||
{"go1.15", ""},
|
||||
{"go1.18", funcSymNameGo119Lower},
|
||||
{"go1.19", funcSymNameGo119Lower},
|
||||
{"devel go1.19", funcSymNameGo119Lower},
|
||||
{"go1.19-pre4", funcSymNameGo119Lower},
|
||||
{"go1.20", funcSymNameGo120},
|
||||
{"devel bd56cb90a72e6725e", funcSymNameGo120},
|
||||
{"go1.21", funcSymNameGo120},
|
||||
{"unknown version", funcSymNameGo120},
|
||||
} {
|
||||
if got := FuncSymName(test.v); got != test.want {
|
||||
t.Errorf("got %s; want %s", got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInlineTree(t *testing.T) {
|
||||
t.Skip("to temporarily resolve #61511")
|
||||
pclinetestBinary, cleanup := dotest(t)
|
||||
defer cleanup()
|
||||
|
||||
f, err := elf.Open(pclinetestBinary)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
pclndat, err := f.Section(".gopclntab").Data()
|
||||
if err != nil {
|
||||
t.Fatalf("reading %s gopclntab: %v", pclinetestBinary, err)
|
||||
}
|
||||
|
||||
// The test binaries will be compiled with the same Go version
|
||||
// used to run the tests.
|
||||
goFunc := lookupSymbol(f, FuncSymName(runtime.Version()))
|
||||
if goFunc == nil {
|
||||
t.Fatal("couldn't find go.func.*")
|
||||
}
|
||||
prog := progContaining(f, goFunc.Value)
|
||||
if prog == nil {
|
||||
t.Fatal("couldn't find go.func.* Prog")
|
||||
}
|
||||
pcln := NewLineTable(pclndat, f.Section(".text").Addr)
|
||||
s := f.Section(".gosymtab")
|
||||
if s == nil {
|
||||
t.Fatal("no .gosymtab section")
|
||||
}
|
||||
d, err := s.Data()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tab, err := NewTable(d, pcln)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fun := tab.LookupFunc("main.main")
|
||||
got, err := pcln.InlineTree(fun, goFunc.Value, prog.Vaddr, prog.ReaderAt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []InlinedCall{
|
||||
{FuncID: 0, Name: "main.inline1"},
|
||||
{FuncID: 0, Name: "main.inline2"},
|
||||
}
|
||||
if !cmp.Equal(got, want, cmpopts.IgnoreFields(InlinedCall{}, "ParentPC")) {
|
||||
t.Errorf("got\n%+v\nwant\n%+v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func progContaining(f *elf.File, addr uint64) *elf.Prog {
|
||||
for _, p := range f.Progs {
|
||||
if addr >= p.Vaddr && addr < p.Vaddr+p.Filesz {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupSymbol(f *elf.File, name string) *elf.Symbol {
|
||||
syms, err := f.Symbols()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, s := range syms {
|
||||
if s.Name == name {
|
||||
return &s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,382 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"debug/elf"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/vuln/internal/test"
|
||||
"golang.org/x/vuln/internal/testenv"
|
||||
)
|
||||
|
||||
func dotest(t *testing.T) (binaryName string, cleanup func()) {
|
||||
testenv.NeedsGoBuild(t)
|
||||
// For now, only works on amd64 platforms.
|
||||
if runtime.GOARCH != "amd64" {
|
||||
t.Skipf("skipping on non-AMD64 system %s", runtime.GOARCH)
|
||||
}
|
||||
// This test builds a Linux/AMD64 binary. Skipping in short mode if cross compiling.
|
||||
if runtime.GOOS != "linux" && testing.Short() {
|
||||
t.Skipf("skipping in short mode on non-Linux system %s", runtime.GOARCH)
|
||||
}
|
||||
|
||||
return test.GoBuild(t, "testdata", "", false, "GOOS", "linux")
|
||||
}
|
||||
|
||||
// skipIfNotELF skips the test if we are not running on an ELF system.
|
||||
// These tests open and examine the test binary, and use elf.Open to do so.
|
||||
func skipIfNotELF(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris", "illumos":
|
||||
// OK.
|
||||
default:
|
||||
t.Skipf("skipping on non-ELF system %s", runtime.GOOS)
|
||||
}
|
||||
}
|
||||
|
||||
func getTable(t *testing.T) *Table {
|
||||
f, tab := crack(os.Args[0], t)
|
||||
f.Close()
|
||||
return tab
|
||||
}
|
||||
|
||||
func crack(file string, t *testing.T) (*elf.File, *Table) {
|
||||
// Open self
|
||||
f, err := elf.Open(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return parse(file, f, t)
|
||||
}
|
||||
|
||||
func parse(file string, f *elf.File, t *testing.T) (*elf.File, *Table) {
|
||||
s := f.Section(".gosymtab")
|
||||
if s == nil {
|
||||
t.Skip("no .gosymtab section")
|
||||
}
|
||||
symdat, err := s.Data()
|
||||
if err != nil {
|
||||
f.Close()
|
||||
t.Fatalf("reading %s gosymtab: %v", file, err)
|
||||
}
|
||||
pclndat, err := f.Section(".gopclntab").Data()
|
||||
if err != nil {
|
||||
f.Close()
|
||||
t.Fatalf("reading %s gopclntab: %v", file, err)
|
||||
}
|
||||
|
||||
pcln := NewLineTable(pclndat, f.Section(".text").Addr)
|
||||
tab, err := NewTable(symdat, pcln)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
t.Fatalf("parsing %s gosymtab: %v", file, err)
|
||||
}
|
||||
|
||||
return f, tab
|
||||
}
|
||||
|
||||
func TestLineFromAline(t *testing.T) {
|
||||
skipIfNotELF(t)
|
||||
|
||||
tab := getTable(t)
|
||||
if tab.go12line != nil {
|
||||
// aline's don't exist in the Go 1.2 table.
|
||||
t.Skip("not relevant to Go 1.2 symbol table")
|
||||
}
|
||||
|
||||
// Find the sym package
|
||||
pkg := tab.LookupFunc("debug/gosym.TestLineFromAline").Obj
|
||||
if pkg == nil {
|
||||
t.Fatalf("nil pkg")
|
||||
}
|
||||
|
||||
// Walk every absolute line and ensure that we hit every
|
||||
// source line monotonically
|
||||
lastline := make(map[string]int)
|
||||
final := -1
|
||||
for i := 0; i < 10000; i++ {
|
||||
path, line := pkg.lineFromAline(i)
|
||||
// Check for end of object
|
||||
if path == "" {
|
||||
if final == -1 {
|
||||
final = i - 1
|
||||
}
|
||||
continue
|
||||
} else if final != -1 {
|
||||
t.Fatalf("reached end of package at absolute line %d, but absolute line %d mapped to %s:%d", final, i, path, line)
|
||||
}
|
||||
// It's okay to see files multiple times (e.g., sys.a)
|
||||
if line == 1 {
|
||||
lastline[path] = 1
|
||||
continue
|
||||
}
|
||||
// Check that the is the next line in path
|
||||
ll, ok := lastline[path]
|
||||
if !ok {
|
||||
t.Errorf("file %s starts on line %d", path, line)
|
||||
} else if line != ll+1 {
|
||||
t.Fatalf("expected next line of file %s to be %d, got %d", path, ll+1, line)
|
||||
}
|
||||
lastline[path] = line
|
||||
}
|
||||
if final == -1 {
|
||||
t.Errorf("never reached end of object")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLineAline(t *testing.T) {
|
||||
skipIfNotELF(t)
|
||||
|
||||
tab := getTable(t)
|
||||
if tab.go12line != nil {
|
||||
// aline's don't exist in the Go 1.2 table.
|
||||
t.Skip("not relevant to Go 1.2 symbol table")
|
||||
}
|
||||
|
||||
for _, o := range tab.Files {
|
||||
// A source file can appear multiple times in a
|
||||
// object. alineFromLine will always return alines in
|
||||
// the first file, so track which lines we've seen.
|
||||
found := make(map[string]int)
|
||||
for i := 0; i < 1000; i++ {
|
||||
path, line := o.lineFromAline(i)
|
||||
if path == "" {
|
||||
break
|
||||
}
|
||||
|
||||
// cgo files are full of 'Z' symbols, which we don't handle
|
||||
if len(path) > 4 && path[len(path)-4:] == ".cgo" {
|
||||
continue
|
||||
}
|
||||
|
||||
if minline, ok := found[path]; path != "" && ok {
|
||||
if minline >= line {
|
||||
// We've already covered this file
|
||||
continue
|
||||
}
|
||||
}
|
||||
found[path] = line
|
||||
|
||||
a, err := o.alineFromLine(path, line)
|
||||
if err != nil {
|
||||
t.Errorf("absolute line %d in object %s maps to %s:%d, but mapping that back gives error %s", i, o.Paths[0].Name, path, line, err)
|
||||
} else if a != i {
|
||||
t.Errorf("absolute line %d in object %s maps to %s:%d, which maps back to absolute line %d\n", i, o.Paths[0].Name, path, line, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPCLine(t *testing.T) {
|
||||
pclinetestBinary, cleanup := dotest(t)
|
||||
defer cleanup()
|
||||
|
||||
f, tab := crack(pclinetestBinary, t)
|
||||
defer f.Close()
|
||||
text := f.Section(".text")
|
||||
textdat, err := text.Data()
|
||||
if err != nil {
|
||||
t.Fatalf("reading .text: %v", err)
|
||||
}
|
||||
|
||||
// Test PCToLine
|
||||
sym := tab.LookupFunc("main.linefrompc")
|
||||
wantLine := 0
|
||||
for pc := sym.Entry; pc < sym.End; pc++ {
|
||||
off := pc - text.Addr // TODO(rsc): should not need off; bug in 8g
|
||||
if textdat[off] == 255 {
|
||||
break
|
||||
}
|
||||
wantLine += int(textdat[off])
|
||||
t.Logf("off is %d %#x (max %d)", off, textdat[off], sym.End-pc)
|
||||
file, line, fn := tab.PCToLine(pc)
|
||||
if fn == nil {
|
||||
t.Errorf("failed to get line of PC %#x", pc)
|
||||
} else if !strings.HasSuffix(file, "pclinetest.s") || line != wantLine || fn != sym {
|
||||
t.Errorf("PCToLine(%#x) = %s:%d (%s), want %s:%d (%s)", pc, file, line, fn.Name, "pclinetest.s", wantLine, sym.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Test LineToPC
|
||||
sym = tab.LookupFunc("main.pcfromline")
|
||||
lookupline := -1
|
||||
wantLine = 0
|
||||
off := uint64(0) // TODO(rsc): should not need off; bug in 8g
|
||||
for pc := sym.Value; pc < sym.End; pc += 2 + uint64(textdat[off]) {
|
||||
file, line, fn := tab.PCToLine(pc)
|
||||
off = pc - text.Addr
|
||||
if textdat[off] == 255 {
|
||||
break
|
||||
}
|
||||
wantLine += int(textdat[off])
|
||||
if line != wantLine {
|
||||
t.Errorf("expected line %d at PC %#x in pcfromline, got %d", wantLine, pc, line)
|
||||
off = pc + 1 - text.Addr
|
||||
continue
|
||||
}
|
||||
if lookupline == -1 {
|
||||
lookupline = line
|
||||
}
|
||||
for ; lookupline <= line; lookupline++ {
|
||||
pc2, fn2, err := tab.LineToPC(file, lookupline)
|
||||
if lookupline != line {
|
||||
// Should be nothing on this line
|
||||
if err == nil {
|
||||
t.Errorf("expected no PC at line %d, got %#x (%s)", lookupline, pc2, fn2.Name)
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Errorf("failed to get PC of line %d: %s", lookupline, err)
|
||||
} else if pc != pc2 {
|
||||
t.Errorf("expected PC %#x (%s) at line %d, got PC %#x (%s)", pc, fn.Name, line, pc2, fn2.Name)
|
||||
}
|
||||
}
|
||||
off = pc + 1 - text.Addr
|
||||
}
|
||||
}
|
||||
|
||||
func TestSymVersion(t *testing.T) {
|
||||
skipIfNotELF(t)
|
||||
|
||||
table := getTable(t)
|
||||
if table.go12line == nil {
|
||||
t.Skip("not relevant to Go 1.2+ symbol table")
|
||||
}
|
||||
for _, fn := range table.Funcs {
|
||||
if fn.goVersion == verUnknown {
|
||||
t.Fatalf("unexpected symbol version: %v", fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// read115Executable returns a hello world executable compiled by Go 1.15.
|
||||
//
|
||||
// The file was compiled in /tmp/hello.go:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// func main() {
|
||||
// println("hello")
|
||||
// }
|
||||
func read115Executable(tb testing.TB) []byte {
|
||||
zippedDat, err := os.ReadFile("testdata/pcln115.gz")
|
||||
if err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
var gzReader *gzip.Reader
|
||||
gzReader, err = gzip.NewReader(bytes.NewBuffer(zippedDat))
|
||||
if err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
var dat []byte
|
||||
dat, err = io.ReadAll(gzReader)
|
||||
if err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
return dat
|
||||
}
|
||||
|
||||
// Test that we can parse a pclntab from 1.15.
|
||||
func Test115PclnParsing(t *testing.T) {
|
||||
dat := read115Executable(t)
|
||||
const textStart = 0x1001000
|
||||
pcln := NewLineTable(dat, textStart)
|
||||
tab, err := NewTable(nil, pcln)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var f *Func
|
||||
var pc uint64
|
||||
pc, f, err = tab.LineToPC("/tmp/hello.go", 3)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pcln.version != ver12 {
|
||||
t.Fatal("Expected pcln to parse as an older version")
|
||||
}
|
||||
if pc != 0x105c280 {
|
||||
t.Fatalf("expect pc = 0x105c280, got 0x%x", pc)
|
||||
}
|
||||
if f.Name != "main.main" {
|
||||
t.Fatalf("expected to parse name as main.main, got %v", f.Name)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
sinkLineTable *LineTable
|
||||
sinkTable *Table
|
||||
)
|
||||
|
||||
func Benchmark115(b *testing.B) {
|
||||
dat := read115Executable(b)
|
||||
const textStart = 0x1001000
|
||||
|
||||
b.Run("NewLineTable", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sinkLineTable = NewLineTable(dat, textStart)
|
||||
}
|
||||
})
|
||||
|
||||
pcln := NewLineTable(dat, textStart)
|
||||
b.Run("NewTable", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var err error
|
||||
sinkTable, err = NewTable(nil, pcln)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
tab, err := NewTable(nil, pcln)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.Run("LineToPC", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var f *Func
|
||||
var pc uint64
|
||||
pc, f, err = tab.LineToPC("/tmp/hello.go", 3)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if pcln.version != ver12 {
|
||||
b.Fatalf("want version=%d, got %d", ver12, pcln.version)
|
||||
}
|
||||
if pc != 0x105c280 {
|
||||
b.Fatalf("want pc=0x105c280, got 0x%x", pc)
|
||||
}
|
||||
if f.Name != "main.main" {
|
||||
b.Fatalf("want name=main.main, got %q", f.Name)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("PCToLine", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
file, line, fn := tab.PCToLine(0x105c280)
|
||||
if file != "/tmp/hello.go" {
|
||||
b.Fatalf("want name=/tmp/hello.go, got %q", file)
|
||||
}
|
||||
if line != 3 {
|
||||
b.Fatalf("want line=3, got %d", line)
|
||||
}
|
||||
if fn.Name != "main.main" {
|
||||
b.Fatalf("want name=main.main, got %q", fn.Name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2016 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 (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assertString(t *testing.T, dsc, out, tgt string) {
|
||||
if out != tgt {
|
||||
t.Fatalf("Expected: %q Actual: %q for %s", tgt, out, dsc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStandardLibPackage(t *testing.T) {
|
||||
s1 := Sym{Name: "io.(*LimitedReader).Read"}
|
||||
s2 := Sym{Name: "io.NewSectionReader"}
|
||||
assertString(t, fmt.Sprintf("package of %q", s1.Name), s1.PackageName(), "io")
|
||||
assertString(t, fmt.Sprintf("package of %q", s2.Name), s2.PackageName(), "io")
|
||||
assertString(t, fmt.Sprintf("receiver of %q", s1.Name), s1.ReceiverName(), "(*LimitedReader)")
|
||||
assertString(t, fmt.Sprintf("receiver of %q", s2.Name), s2.ReceiverName(), "")
|
||||
}
|
||||
|
||||
func TestStandardLibPathPackage(t *testing.T) {
|
||||
s1 := Sym{Name: "debug/gosym.(*LineTable).PCToLine"}
|
||||
s2 := Sym{Name: "debug/gosym.NewTable"}
|
||||
assertString(t, fmt.Sprintf("package of %q", s1.Name), s1.PackageName(), "debug/gosym")
|
||||
assertString(t, fmt.Sprintf("package of %q", s2.Name), s2.PackageName(), "debug/gosym")
|
||||
assertString(t, fmt.Sprintf("receiver of %q", s1.Name), s1.ReceiverName(), "(*LineTable)")
|
||||
assertString(t, fmt.Sprintf("receiver of %q", s2.Name), s2.ReceiverName(), "")
|
||||
}
|
||||
|
||||
func TestGenericNames(t *testing.T) {
|
||||
s1 := Sym{Name: "main.set[int]"}
|
||||
s2 := Sym{Name: "main.(*value[int]).get"}
|
||||
s3 := Sym{Name: "a/b.absDifference[c/d.orderedAbs[float64]]"}
|
||||
s4 := Sym{Name: "main.testfunction[.shape.int]"}
|
||||
assertString(t, fmt.Sprintf("package of %q", s1.Name), s1.PackageName(), "main")
|
||||
assertString(t, fmt.Sprintf("package of %q", s2.Name), s2.PackageName(), "main")
|
||||
assertString(t, fmt.Sprintf("package of %q", s3.Name), s3.PackageName(), "a/b")
|
||||
assertString(t, fmt.Sprintf("package of %q", s4.Name), s4.PackageName(), "main")
|
||||
assertString(t, fmt.Sprintf("receiver of %q", s1.Name), s1.ReceiverName(), "")
|
||||
assertString(t, fmt.Sprintf("receiver of %q", s2.Name), s2.ReceiverName(), "(*value[int])")
|
||||
assertString(t, fmt.Sprintf("receiver of %q", s3.Name), s3.ReceiverName(), "")
|
||||
assertString(t, fmt.Sprintf("receiver of %q", s4.Name), s4.ReceiverName(), "")
|
||||
assertString(t, fmt.Sprintf("base of %q", s1.Name), s1.BaseName(), "set[int]")
|
||||
assertString(t, fmt.Sprintf("base of %q", s2.Name), s2.BaseName(), "get")
|
||||
assertString(t, fmt.Sprintf("base of %q", s3.Name), s3.BaseName(), "absDifference[c/d.orderedAbs[float64]]")
|
||||
assertString(t, fmt.Sprintf("base of %q", s4.Name), s4.BaseName(), "testfunction[.shape.int]")
|
||||
}
|
||||
|
||||
func TestRemotePackage(t *testing.T) {
|
||||
s1 := Sym{Name: "github.com/docker/doc.ker/pkg/mflag.(*FlagSet).PrintDefaults"}
|
||||
s2 := Sym{Name: "github.com/docker/doc.ker/pkg/mflag.PrintDefaults"}
|
||||
assertString(t, fmt.Sprintf("package of %q", s1.Name), s1.PackageName(), "github.com/docker/doc.ker/pkg/mflag")
|
||||
assertString(t, fmt.Sprintf("package of %q", s2.Name), s2.PackageName(), "github.com/docker/doc.ker/pkg/mflag")
|
||||
assertString(t, fmt.Sprintf("receiver of %q", s1.Name), s1.ReceiverName(), "(*FlagSet)")
|
||||
assertString(t, fmt.Sprintf("receiver of %q", s2.Name), s2.ReceiverName(), "")
|
||||
}
|
||||
|
||||
func TestIssue29551(t *testing.T) {
|
||||
tests := []struct {
|
||||
sym Sym
|
||||
pkgName string
|
||||
}{
|
||||
{Sym{goVersion: ver120, Name: "type:.eq.[9]debug/elf.intName"}, ""},
|
||||
{Sym{goVersion: ver120, Name: "type:.hash.debug/elf.ProgHeader"}, ""},
|
||||
{Sym{goVersion: ver120, Name: "type:.eq.runtime._panic"}, ""},
|
||||
{Sym{goVersion: ver120, Name: "type:.hash.struct { runtime.gList; runtime.n int32 }"}, ""},
|
||||
{Sym{goVersion: ver120, Name: "go:(*struct { sync.Mutex; math/big.table [64]math/big"}, ""},
|
||||
{Sym{goVersion: ver120, Name: "go.uber.org/zap/buffer.(*Buffer).AppendString"}, "go.uber.org/zap/buffer"},
|
||||
{Sym{goVersion: ver118, Name: "type..eq.[9]debug/elf.intName"}, ""},
|
||||
{Sym{goVersion: ver118, Name: "type..hash.debug/elf.ProgHeader"}, ""},
|
||||
{Sym{goVersion: ver118, Name: "type..eq.runtime._panic"}, ""},
|
||||
{Sym{goVersion: ver118, Name: "type..hash.struct { runtime.gList; runtime.n int32 }"}, ""},
|
||||
{Sym{goVersion: ver118, Name: "go.(*struct { sync.Mutex; math/big.table [64]math/big"}, ""},
|
||||
// unfortunate
|
||||
{Sym{goVersion: ver118, Name: "go.uber.org/zap/buffer.(*Buffer).AppendString"}, ""},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
assertString(t, fmt.Sprintf("package of %q", tc.sym.Name), tc.sym.PackageName(), tc.pkgName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
func linefrompc()
|
||||
func pcfromline()
|
||||
|
||||
func main() {
|
||||
// Prevent GC of our test symbols
|
||||
linefrompc()
|
||||
pcfromline()
|
||||
inline1()
|
||||
}
|
||||
|
||||
func inline1() {
|
||||
inline2()
|
||||
}
|
||||
|
||||
func inline2() {
|
||||
println(1)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// +build ignore
|
||||
|
||||
// Empty include file to generate z symbols
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// EOF
|
||||
@@ -0,0 +1,48 @@
|
||||
TEXT ·linefrompc(SB),4,$0 // Each byte stores its line delta
|
||||
BYTE $2;
|
||||
BYTE $1;
|
||||
BYTE $1; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
BYTE $1;
|
||||
BYTE $1;
|
||||
BYTE $1; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
#include "pclinetest.h"
|
||||
BYTE $2;
|
||||
#include "pclinetest.h"
|
||||
BYTE $2;
|
||||
BYTE $255;
|
||||
|
||||
TEXT ·pcfromline(SB),4,$0 // Each record stores its line delta, then n, then n more bytes
|
||||
BYTE $32; BYTE $0;
|
||||
BYTE $1; BYTE $1; BYTE $0;
|
||||
BYTE $1; BYTE $0;
|
||||
|
||||
BYTE $2; BYTE $4; BYTE $0; BYTE $0; BYTE $0; BYTE $0;
|
||||
|
||||
|
||||
#include "pclinetest.h"
|
||||
BYTE $4; BYTE $0;
|
||||
|
||||
|
||||
BYTE $3; BYTE $3; BYTE $0; BYTE $0; BYTE $0;
|
||||
#include "pclinetest.h"
|
||||
|
||||
|
||||
BYTE $4; BYTE $3; BYTE $0; BYTE $0; BYTE $0;
|
||||
BYTE $255;
|
||||
Binary file not shown.
@@ -0,0 +1,189 @@
|
||||
// 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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// 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.
|
||||
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 symbols called 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
|
||||
func (l ScanLevel) WantPackages() bool { return l == ScanLevelPackage || l == ScanLevelSymbol }
|
||||
@@ -0,0 +1,17 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/vuln/internal/test"
|
||||
)
|
||||
|
||||
func TestImports(t *testing.T) {
|
||||
test.VerifyImports(t,
|
||||
"golang.org/x/vuln/internal/osv", // allowed to pull in the osv json entries
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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
|
||||
|
||||
// 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.OSV != nil {
|
||||
err = to.OSV(msg.OSV)
|
||||
}
|
||||
if msg.Finding != nil {
|
||||
err = to.Finding(msg.Finding)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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})
|
||||
}
|
||||
|
||||
// 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})
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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"
|
||||
)
|
||||
@@ -0,0 +1,238 @@
|
||||
// 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"`
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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 osv_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/vuln/internal/test"
|
||||
)
|
||||
|
||||
func TestImports(t *testing.T) {
|
||||
test.VerifyImports(t) // no non stdlib imports allowed
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// 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.
|
||||
//
|
||||
// See https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=sarif
|
||||
// for more information on the SARIF format.
|
||||
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 OSV.
|
||||
Results []Result `json:"results,omitempty"`
|
||||
|
||||
// URIBaseIDs encodes the SARIF originalUriBaseIds property
|
||||
URIBaseIDs map[string]ArtifactLocation `json:"originalUriBaseIds,omitempty"`
|
||||
}
|
||||
|
||||
// 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 should be "govulncheck"
|
||||
Name string `json:"name,omitempty"`
|
||||
// Version should be the govulncheck version
|
||||
Version string `json:"semanticVersion,omitempty"`
|
||||
// InformationURI should point 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,omitempty"`
|
||||
}
|
||||
|
||||
// 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 should contain OSV.Aliases (CVEs and GHSAs) as tags.
|
||||
// Consumers of govulncheck SARIF can use these tags to filter
|
||||
// results based on, say, CVEs.
|
||||
Properties RuleTags `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// RuleTags defines properties.tags.
|
||||
type RuleTags struct {
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
// 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 lowest possible
|
||||
// level appear in the Result. For instance, if there are findings
|
||||
// with call stacks for an OSV, those findings will be in the Result,
|
||||
// but not the “imports” and “requires” 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", "note", and "none".
|
||||
Level string `json:"level,omitempty"`
|
||||
// Message explains the overall findings.
|
||||
Message Description `json:"message,omitempty"`
|
||||
// Locations to which the findings are associated.
|
||||
Locations []Location `json:"locations,omitempty"`
|
||||
// CodeFlows can encode call stacks produced by govulncheck.
|
||||
CodeFlows []CodeFlow `json:"codeFlows,omitempty"`
|
||||
// Stacks can encode call stacks produced by govulncheck.
|
||||
Stacks []Stack `json:"stacks,omitempty"`
|
||||
// TODO: support Fixes when integration points to the same
|
||||
}
|
||||
|
||||
// CodeFlow describes 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,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 string `json:"module,omitempty"`
|
||||
// Location also contains a Message field.
|
||||
Location Location `json:"location,omitempty"`
|
||||
// Can also contain Stack field that encodes a call stack
|
||||
// leading to this thread flow location.
|
||||
}
|
||||
|
||||
// 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,omitempty"`
|
||||
}
|
||||
|
||||
// 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 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"`
|
||||
}
|
||||
|
||||
// ArtifactLocation is a path to an offending file.
|
||||
type ArtifactLocation struct {
|
||||
// URI is a path to the artifact. If URIBaseID is empty, then
|
||||
// URI is absolute and it needs to start with, say, "file://."
|
||||
URI string `json:"uri,omitempty"`
|
||||
// URIBaseID is offset for URI. An example is %SRCROOT%, used by
|
||||
// Github Code Scanning to point to the root of the target repo.
|
||||
// Its value must be defined in URIBaseIDs of a Run.
|
||||
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"`
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// 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 scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
|
||||
"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) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// 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(f)
|
||||
if err == nil {
|
||||
return &vulncheck.Bin{
|
||||
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(f)
|
||||
if bin != nil {
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("unrecognized binary format")
|
||||
}
|
||||
|
||||
// parseBlob extracts vulncheck.Bin from a valid blob. If it
|
||||
// cannot recognize a valid blob, returns nil.
|
||||
func parseBlob(from io.Reader) *vulncheck.Bin {
|
||||
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 ""
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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 print the usage message with exit 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")
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAbsRelShorter(t *testing.T) {
|
||||
thisFileAbs, _ := filepath.Abs("filepath_test.go")
|
||||
|
||||
for _, test := range []struct {
|
||||
l string
|
||||
want string
|
||||
}{
|
||||
{"filepath_test.go", "filepath_test.go"},
|
||||
{thisFileAbs, "filepath_test.go"},
|
||||
} {
|
||||
if got := AbsRelShorter(test.l); got != test.want {
|
||||
t.Errorf("want %s; got %s", test.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// 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 (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
govulncheck.Config
|
||||
patterns []string
|
||||
mode string
|
||||
db string
|
||||
json bool
|
||||
dir string
|
||||
tags []string
|
||||
test bool
|
||||
show []string
|
||||
env []string
|
||||
}
|
||||
|
||||
const (
|
||||
modeBinary = "binary"
|
||||
modeSource = "source"
|
||||
modeConvert = "convert" // only intended for use by gopls
|
||||
modeQuery = "query" // only intended for use by gopls
|
||||
modeExtract = "extract" // currently, only binary extraction is supported
|
||||
)
|
||||
|
||||
func parseFlags(cfg *config, stderr io.Writer, args []string) error {
|
||||
var tagsFlag buildutil.TagsFlag
|
||||
var showFlag showFlag
|
||||
var version bool
|
||||
flags := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
flags.SetOutput(stderr)
|
||||
flags.BoolVar(&cfg.json, "json", false, "output JSON")
|
||||
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.StringVar(&cfg.mode, "mode", modeSource, "supports source or binary")
|
||||
flags.Var(&tagsFlag, "tags", "comma-separated `list` of build tags")
|
||||
flags.Var(&showFlag, "show", "enable display of additional information specified by the comma separated `list`\nThe supported values are 'traces','color', 'version', and 'verbose'")
|
||||
flags.BoolVar(&version, "version", false, "print the version information")
|
||||
scanLevel := flags.String("scan", "symbol", "set the scanning level desired, one of module, package or symbol")
|
||||
flags.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 {
|
||||
return errHelp
|
||||
}
|
||||
return err
|
||||
}
|
||||
cfg.patterns = flags.Args()
|
||||
cfg.tags = tagsFlag
|
||||
cfg.show = showFlag
|
||||
if version {
|
||||
cfg.show = append(cfg.show, "version")
|
||||
}
|
||||
cfg.ScanLevel = govulncheck.ScanLevel(*scanLevel)
|
||||
if err := validateConfig(cfg); err != nil {
|
||||
fmt.Fprintln(flags.Output(), err)
|
||||
return errUsage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var supportedModes = map[string]bool{
|
||||
modeSource: true,
|
||||
modeBinary: true,
|
||||
modeConvert: true,
|
||||
modeQuery: true,
|
||||
modeExtract: true,
|
||||
}
|
||||
|
||||
var supportedLevels = map[string]bool{
|
||||
govulncheck.ScanLevelModule: true,
|
||||
govulncheck.ScanLevelPackage: true,
|
||||
govulncheck.ScanLevelSymbol: true,
|
||||
}
|
||||
|
||||
func validateConfig(cfg *config) error {
|
||||
if _, ok := supportedModes[cfg.mode]; !ok {
|
||||
return fmt.Errorf("%q is not a valid mode", cfg.mode)
|
||||
}
|
||||
if _, ok := supportedLevels[string(cfg.ScanLevel)]; !ok {
|
||||
return fmt.Errorf("%q is not a valid scan level", cfg.ScanLevel)
|
||||
}
|
||||
switch cfg.mode {
|
||||
case modeSource:
|
||||
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 modeBinary:
|
||||
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 modeExtract:
|
||||
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.json {
|
||||
return fmt.Errorf("the -json flag 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 modeConvert:
|
||||
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 modeQuery:
|
||||
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.json {
|
||||
return fmt.Errorf("the -json flag 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
|
||||
}
|
||||
}
|
||||
}
|
||||
if cfg.json && len(cfg.show) > 0 {
|
||||
return fmt.Errorf("the -show flag is not supported for JSON output")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isFile(path string) bool {
|
||||
s, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return !s.IsDir()
|
||||
}
|
||||
|
||||
type showFlag []string
|
||||
|
||||
func (v *showFlag) Set(s string) error {
|
||||
*v = append(*v, strings.Split(s, ",")...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *showFlag) Get() interface{} { return *f }
|
||||
func (f *showFlag) String() string { return "<options>" }
|
||||
@@ -0,0 +1,79 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
"golang.org/x/vuln/internal/scan"
|
||||
)
|
||||
|
||||
var update = flag.Bool("update", false, "update test files with results")
|
||||
|
||||
func TestPrinting(t *testing.T) {
|
||||
testdata := os.DirFS("testdata")
|
||||
inputs, err := fs.Glob(testdata, "*.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, input := range inputs {
|
||||
name := strings.TrimSuffix(input, ".json")
|
||||
rawJSON, _ := fs.ReadFile(testdata, input)
|
||||
textfiles, err := fs.Glob(testdata, name+"*.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, textfile := range textfiles {
|
||||
textname := strings.TrimSuffix(textfile, ".txt")
|
||||
t.Run(textname, func(t *testing.T) {
|
||||
wantText, _ := fs.ReadFile(testdata, textfile)
|
||||
got := &bytes.Buffer{}
|
||||
handler := scan.NewTextHandler(got)
|
||||
handler.Show(strings.Split(textname, "_")[1:])
|
||||
testRunHandler(t, rawJSON, handler)
|
||||
if diff := cmp.Diff(string(wantText), got.String()); diff != "" {
|
||||
if *update {
|
||||
// write the output back to the file
|
||||
os.WriteFile(filepath.Join("testdata", textfile), got.Bytes(), 0644)
|
||||
return
|
||||
}
|
||||
t.Errorf("Readable mismatch (-want, +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
t.Run(name+"_json", func(t *testing.T) {
|
||||
// this effectively tests that we can round trip the json
|
||||
got := &strings.Builder{}
|
||||
testRunHandler(t, rawJSON, govulncheck.NewJSONHandler(got))
|
||||
if diff := cmp.Diff(strings.TrimSpace(string(rawJSON)), strings.TrimSpace(got.String())); diff != "" {
|
||||
t.Errorf("JSON mismatch (-want, +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testRunHandler(t *testing.T, rawJSON []byte, handler govulncheck.Handler) {
|
||||
if err := govulncheck.HandleJSON(bytes.NewReader(rawJSON), handler); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err := scan.Flush(handler)
|
||||
switch e := err.(type) {
|
||||
case nil:
|
||||
case interface{ ExitCode() int }:
|
||||
if e.ExitCode() != 0 && e.ExitCode() != 3 {
|
||||
// not success or vulnerabilities found
|
||||
t.Fatal(err)
|
||||
}
|
||||
default:
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// 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"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/vuln/internal/client"
|
||||
"golang.org/x/vuln/internal/osv"
|
||||
"golang.org/x/vuln/internal/test"
|
||||
)
|
||||
|
||||
func TestRunQuery(t *testing.T) {
|
||||
e := &osv.Entry{
|
||||
ID: "GO-1999-0001",
|
||||
Affected: []osv.Affected{{
|
||||
Module: osv.Module{Path: "bad.com"},
|
||||
Ranges: []osv.Range{{
|
||||
Type: osv.RangeTypeSemver,
|
||||
Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "1.2.3"}},
|
||||
}},
|
||||
EcosystemSpecific: osv.EcosystemSpecific{
|
||||
Packages: []osv.Package{{
|
||||
Path: "bad.com",
|
||||
}, {
|
||||
Path: "bad.com/bad",
|
||||
}},
|
||||
},
|
||||
}, {
|
||||
Module: osv.Module{Path: "unfixable.com"},
|
||||
Ranges: []osv.Range{{
|
||||
Type: osv.RangeTypeSemver,
|
||||
Events: []osv.RangeEvent{{Introduced: "0"}}, // no fix
|
||||
}},
|
||||
EcosystemSpecific: osv.EcosystemSpecific{
|
||||
Packages: []osv.Package{{
|
||||
Path: "unfixable.com",
|
||||
}},
|
||||
},
|
||||
}},
|
||||
}
|
||||
e2 := &osv.Entry{
|
||||
ID: "GO-1999-0002",
|
||||
Affected: []osv.Affected{{
|
||||
Module: osv.Module{Path: "bad.com"},
|
||||
Ranges: []osv.Range{{
|
||||
Type: osv.RangeTypeSemver,
|
||||
Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "1.2.0"}},
|
||||
}},
|
||||
EcosystemSpecific: osv.EcosystemSpecific{
|
||||
Packages: []osv.Package{{
|
||||
Path: "bad.com/pkg",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
stdlib := &osv.Entry{
|
||||
ID: "GO-2000-0003",
|
||||
Affected: []osv.Affected{{
|
||||
Module: osv.Module{Path: "stdlib"},
|
||||
Ranges: []osv.Range{{
|
||||
Type: osv.RangeTypeSemver,
|
||||
Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "1.19.4"}},
|
||||
}},
|
||||
EcosystemSpecific: osv.EcosystemSpecific{
|
||||
Packages: []osv.Package{{
|
||||
Path: "net/http",
|
||||
}},
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
c, err := client.NewInMemoryClient([]*osv.Entry{e, e2, stdlib})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
for _, tc := range []struct {
|
||||
query []string
|
||||
want []*osv.Entry
|
||||
}{
|
||||
{
|
||||
query: []string{"stdlib@go1.18"},
|
||||
want: []*osv.Entry{stdlib},
|
||||
},
|
||||
{
|
||||
query: []string{"stdlib@1.18"},
|
||||
want: []*osv.Entry{stdlib},
|
||||
},
|
||||
{
|
||||
query: []string{"stdlib@v1.18.0"},
|
||||
want: []*osv.Entry{stdlib},
|
||||
},
|
||||
{
|
||||
query: []string{"bad.com@1.2.3"},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
query: []string{"bad.com@v1.1.0"},
|
||||
want: []*osv.Entry{e, e2},
|
||||
},
|
||||
{
|
||||
query: []string{"unfixable.com@2.0.0"},
|
||||
want: []*osv.Entry{e},
|
||||
},
|
||||
{
|
||||
// each entry should only show up once
|
||||
query: []string{"bad.com@1.1.0", "unfixable.com@2.0.0"},
|
||||
want: []*osv.Entry{e, e2},
|
||||
},
|
||||
{
|
||||
query: []string{"stdlib@1.18", "unfixable.com@2.0.0"},
|
||||
want: []*osv.Entry{stdlib, e},
|
||||
},
|
||||
} {
|
||||
t.Run(strings.Join(tc.query, ","), func(t *testing.T) {
|
||||
h := test.NewMockHandler()
|
||||
err := runQuery(ctx, h, &config{patterns: tc.query}, c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(h.OSVMessages, tc.want); diff != "" {
|
||||
t.Errorf("runQuery: unexpected diff:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModuleQuery(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
pattern, wantMod, wantVer string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
pattern: "stdlib@go1.18",
|
||||
wantMod: "stdlib",
|
||||
wantVer: "go1.18",
|
||||
},
|
||||
{
|
||||
pattern: "golang.org/x/tools@v0.0.0-20140414041502-123456789012",
|
||||
wantMod: "golang.org/x/tools",
|
||||
wantVer: "v0.0.0-20140414041502-123456789012",
|
||||
},
|
||||
{
|
||||
pattern: "golang.org/x/tools",
|
||||
wantErr: "invalid query",
|
||||
},
|
||||
{
|
||||
pattern: "golang.org/x/tools@1.0.0.2",
|
||||
wantErr: "not valid semver",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.pattern, func(t *testing.T) {
|
||||
gotMod, gotVer, err := parseModuleQuery(tc.pattern)
|
||||
if tc.wantErr == "" {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if gotMod != tc.wantMod || gotVer != tc.wantVer {
|
||||
t.Errorf("parseModuleQuery = (%s, %s), want (%s, %s)", gotMod, gotVer, tc.wantMod, tc.wantVer)
|
||||
}
|
||||
} else {
|
||||
if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
|
||||
t.Errorf("parseModuleQuery = %v, want err containing %s", err, tc.wantErr)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// 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 (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
func TestFrame(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
frame *govulncheck.Frame
|
||||
short bool
|
||||
wantFunc string
|
||||
wantPos string
|
||||
}{
|
||||
{
|
||||
name: "position and function",
|
||||
frame: &govulncheck.Frame{
|
||||
Package: "golang.org/x/vuln/internal/vulncheck",
|
||||
Function: "Foo",
|
||||
Position: &govulncheck.Position{Filename: "some/path/file.go", Line: 12},
|
||||
},
|
||||
wantFunc: "golang.org/x/vuln/internal/vulncheck.Foo",
|
||||
wantPos: "some/path/file.go:12",
|
||||
},
|
||||
{
|
||||
name: "receiver",
|
||||
frame: &govulncheck.Frame{
|
||||
Package: "golang.org/x/vuln/internal/vulncheck",
|
||||
Receiver: "Bar",
|
||||
Function: "Foo",
|
||||
},
|
||||
wantFunc: "golang.org/x/vuln/internal/vulncheck.Bar.Foo",
|
||||
},
|
||||
{
|
||||
name: "function and receiver",
|
||||
frame: &govulncheck.Frame{Receiver: "*ServeMux", Function: "Handle"},
|
||||
wantFunc: "ServeMux.Handle",
|
||||
},
|
||||
{
|
||||
name: "package and function",
|
||||
frame: &govulncheck.Frame{Package: "net/http", Function: "Get"},
|
||||
wantFunc: "net/http.Get",
|
||||
},
|
||||
{
|
||||
name: "package, function and receiver",
|
||||
frame: &govulncheck.Frame{Package: "net/http", Receiver: "*ServeMux", Function: "Handle"},
|
||||
wantFunc: "net/http.ServeMux.Handle",
|
||||
},
|
||||
{
|
||||
name: "short",
|
||||
frame: &govulncheck.Frame{Package: "net/http", Function: "Get"},
|
||||
short: true,
|
||||
wantFunc: "http.Get",
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
buf := &strings.Builder{}
|
||||
addSymbolName(buf, test.frame, test.short)
|
||||
got := buf.String()
|
||||
if got != test.wantFunc {
|
||||
t.Errorf("want %v func name; got %v", test.wantFunc, got)
|
||||
}
|
||||
if got := posToString(test.frame.Position); got != test.wantPos {
|
||||
t.Errorf("want %v call position; got %v", test.wantPos, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// 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/vuln/internal/client"
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
case cfg.json:
|
||||
handler = govulncheck.NewJSONHandler(stdout)
|
||||
default:
|
||||
th := NewTextHandler(stdout)
|
||||
th.Show(cfg.show)
|
||||
handler = th
|
||||
}
|
||||
|
||||
// Write the introductory message to the user.
|
||||
if err := handler.Config(&cfg.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch cfg.mode {
|
||||
case modeSource:
|
||||
dir := filepath.FromSlash(cfg.dir)
|
||||
err = runSource(ctx, handler, cfg, client, dir)
|
||||
case modeBinary:
|
||||
err = runBinary(ctx, handler, cfg, client)
|
||||
case modeExtract:
|
||||
return runExtract(cfg, stdout)
|
||||
case modeQuery:
|
||||
err = runQuery(ctx, handler, cfg, client)
|
||||
case modeConvert:
|
||||
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.mode == modeSource && 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()
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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 (
|
||||
"runtime/debug"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGovulncheckVersion(t *testing.T) {
|
||||
bi := &debug.BuildInfo{
|
||||
Settings: []debug.BuildSetting{
|
||||
{Key: "vcs.revision", Value: "1234567890001234"},
|
||||
{Key: "vcs.time", Value: "2023-01-25T19:57:54Z"},
|
||||
},
|
||||
}
|
||||
|
||||
want := "v0.0.0-123456789000-20230125195754"
|
||||
got := &config{}
|
||||
scannerVersion(got, bi)
|
||||
if got.ScannerVersion != want {
|
||||
t.Errorf("got %s; want %s", got.ScannerVersion, want)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// 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
|
||||
}
|
||||
var pkgs []*packages.Package
|
||||
var mods []*packages.Module
|
||||
graph := vulncheck.NewPackageGraph(cfg.GoVersion)
|
||||
pkgConfig := &packages.Config{
|
||||
Dir: dir,
|
||||
Tests: cfg.test,
|
||||
Env: cfg.env,
|
||||
}
|
||||
pkgs, mods, err = graph.LoadPackagesAndMods(pkgConfig, cfg.tags, cfg.patterns)
|
||||
if err != nil {
|
||||
if isGoVersionMismatchError(err) {
|
||||
return fmt.Errorf("%v\n\n%v", errGoVersionMismatch, err)
|
||||
}
|
||||
return fmt.Errorf("loading packages: %w", err)
|
||||
}
|
||||
|
||||
if err := handler.Progress(sourceProgressMessage(pkgs, len(mods)-1, cfg.ScanLevel)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.ScanLevel.WantPackages() && len(pkgs) == 0 {
|
||||
return nil // early exit
|
||||
}
|
||||
return vulncheck.Source(ctx, handler, pkgs, mods, &cfg.Config, client, graph)
|
||||
}
|
||||
|
||||
// sourceProgressMessage returns a string of the form
|
||||
//
|
||||
// "Scanning your code and P packages across M dependent modules for known vulnerabilities..."
|
||||
//
|
||||
// P is the number of strictly dependent packages of
|
||||
// topPkgs and Y is the number of their modules. If P
|
||||
// is 0, then the following message is returned
|
||||
//
|
||||
// "No packages matching the provided pattern."
|
||||
func sourceProgressMessage(topPkgs []*packages.Package, mods int, mode govulncheck.ScanLevel) *govulncheck.Progress {
|
||||
var pkgsPhrase, modsPhrase string
|
||||
|
||||
if mode.WantPackages() {
|
||||
if len(topPkgs) == 0 {
|
||||
// The package pattern is valid, but no packages are matching.
|
||||
// Example is pkg/strace/... (see #59623).
|
||||
return &govulncheck.Progress{Message: "No packages matching the provided pattern."}
|
||||
}
|
||||
pkgs := depPkgs(topPkgs)
|
||||
pkgsPhrase = fmt.Sprintf(" and %d package%s", pkgs, choose(pkgs != 1, "s", ""))
|
||||
}
|
||||
modsPhrase = fmt.Sprintf(" %d dependent module%s", mods, choose(mods != 1, "s", ""))
|
||||
|
||||
msg := fmt.Sprintf("Scanning your code%s across%s for known vulnerabilities...", pkgsPhrase, modsPhrase)
|
||||
return &govulncheck.Progress{Message: msg}
|
||||
}
|
||||
|
||||
// depPkgs returns the number of packages that topPkgs depend on
|
||||
func depPkgs(topPkgs []*packages.Package) int {
|
||||
tops := make(map[string]bool)
|
||||
depPkgs := make(map[string]bool)
|
||||
|
||||
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 depPkgs[path] {
|
||||
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] = true
|
||||
}
|
||||
|
||||
for _, d := range p.Imports {
|
||||
visit(d, false)
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range topPkgs {
|
||||
visit(t, true)
|
||||
}
|
||||
|
||||
return len(depPkgs)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// 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 (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
func TestSummarizeCallStack(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in, want string
|
||||
}{
|
||||
{"ma.a.F", "a.F"},
|
||||
{"m1.p1.F", "p1.F"},
|
||||
{"mv.v.V", "v.V"},
|
||||
{
|
||||
"m1.p1.F mv.v.V",
|
||||
"p1.F calls v.V",
|
||||
},
|
||||
{
|
||||
"m1.p1.F m1.p2.G mv.v.V1 mv.v.v2",
|
||||
"p2.G calls v.V1, which calls v.v2",
|
||||
},
|
||||
{
|
||||
"m1.p1.F m1.p2.G mv.v.V$1 mv.v.V1",
|
||||
"p2.G calls v.V, which calls v.V1",
|
||||
},
|
||||
{
|
||||
"m1.p1.F m1.p2.G$1 mv.v.V1",
|
||||
"p2.G calls v.V1",
|
||||
},
|
||||
{
|
||||
"m1.p1.F m1.p2.G$1 mv.v.V$1 mv.v.V1",
|
||||
"p2.G calls v.V, which calls v.V1",
|
||||
},
|
||||
{
|
||||
"m1.p1.F w.x.Y m1.p2.G ma.a.H mb.b.I mc.c.J mv.v.V",
|
||||
"p2.G calls a.H, which eventually calls v.V",
|
||||
},
|
||||
{
|
||||
"m1.p1.F w.x.Y m1.p2.G ma.a.H mb.b.I mc.c.J mv.v.V$1 mv.v.V1",
|
||||
"p2.G calls a.H, which eventually calls v.V1",
|
||||
},
|
||||
{
|
||||
"m1.p1.F m1.p1.F$1 ma.a.H mb.b.I mv.v.V1",
|
||||
"p1.F calls a.H, which eventually calls v.V1",
|
||||
},
|
||||
} {
|
||||
in := stringToFinding(test.in)
|
||||
got := compactTrace(in)
|
||||
if got != test.want {
|
||||
t.Errorf("%s:\ngot %s\nwant %s", test.in, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stringToFinding(s string) *govulncheck.Finding {
|
||||
f := &govulncheck.Finding{}
|
||||
entries := strings.Fields(s)
|
||||
for i := len(entries) - 1; i >= 0; i-- {
|
||||
e := entries[i]
|
||||
firstDot := strings.Index(e, ".")
|
||||
lastDot := strings.LastIndex(e, ".")
|
||||
f.Trace = append(f.Trace, &govulncheck.Frame{
|
||||
Module: e[:firstDot],
|
||||
Package: e[:firstDot] + "/" + e[firstDot+1:lastDot],
|
||||
Function: e[lastDot+1:],
|
||||
})
|
||||
}
|
||||
return f
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
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{}
|
||||
addSymbolName(buf, frame, short)
|
||||
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 {
|
||||
if len(finding.Trace) < 1 {
|
||||
return ""
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
buf := &strings.Builder{}
|
||||
topPos := posToString(finding.Trace[iTop].Position)
|
||||
if topPos != "" {
|
||||
buf.WriteString(topPos)
|
||||
buf.WriteString(": ")
|
||||
}
|
||||
|
||||
if iTop > 0 {
|
||||
addSymbolName(buf, finding.Trace[iTop], true)
|
||||
buf.WriteString(" calls ")
|
||||
}
|
||||
if iTop > 1 {
|
||||
addSymbolName(buf, finding.Trace[iTop-1], true)
|
||||
buf.WriteString(", which")
|
||||
if iTop > 2 {
|
||||
buf.WriteString(" eventually")
|
||||
}
|
||||
buf.WriteString(" calls ")
|
||||
}
|
||||
addSymbolName(buf, finding.Trace[0], true)
|
||||
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 addSymbolName(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, ".")
|
||||
}
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "symbol"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0001",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Third-party vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"amd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0001"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "golang.org/vmod",
|
||||
"function": "Vuln"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0002",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Stdlib vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "stdlib",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0002"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0002",
|
||||
"trace": [
|
||||
{
|
||||
"module": "stdlib",
|
||||
"version": "v0.0.1",
|
||||
"package": "net/http",
|
||||
"function": "Vuln2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
=== Symbol Results ===
|
||||
|
||||
Vulnerability #1: GO-0000-0002
|
||||
Stdlib vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0002
|
||||
Standard library
|
||||
Found in: net/http@go0.0.1
|
||||
Fixed in: N/A
|
||||
Example traces found:
|
||||
#1: http.Vuln2
|
||||
|
||||
Vulnerability #2: GO-0000-0001
|
||||
Third-party vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0001
|
||||
Module: golang.org/vmod
|
||||
Found in: golang.org/vmod@v0.0.1
|
||||
Fixed in: golang.org/vmod@v0.1.3
|
||||
Platforms: amd
|
||||
Example traces found:
|
||||
#1: vmod.Vuln
|
||||
|
||||
Your code is affected by 2 vulnerabilities from 1 module and the Go standard library.
|
||||
This scan found no other vulnerabilities in packages you import or modules you
|
||||
require.
|
||||
Use '-show verbose' for more details.
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "symbol"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0001",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Third-party vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"amd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0001"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "golang.org/vmod",
|
||||
"function": "VulnFoo"
|
||||
},
|
||||
{
|
||||
"module": "golang.org/main",
|
||||
"version": "v0.0.1",
|
||||
"package": "golang.org/main",
|
||||
"function": "main"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
=== Symbol Results ===
|
||||
|
||||
Vulnerability #1: GO-0000-0001
|
||||
Third-party vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0001
|
||||
Module: golang.org/vmod
|
||||
Found in: golang.org/vmod@v0.0.1
|
||||
Fixed in: golang.org/vmod@v0.1.3
|
||||
Platforms: amd
|
||||
Example traces found:
|
||||
#1: main.main calls vmod.VulnFoo
|
||||
|
||||
Your code is affected by 1 vulnerability from 1 module.
|
||||
This scan found no other vulnerabilities in packages you import or modules you
|
||||
require.
|
||||
Use '-show verbose' for more details.
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "module"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0001",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Third-party vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"amd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0001"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
=== Module Results ===
|
||||
|
||||
Vulnerability #1: GO-0000-0001
|
||||
Third-party vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0001
|
||||
Module: golang.org/vmod
|
||||
Found in: golang.org/vmod@v0.0.1
|
||||
Fixed in: golang.org/vmod@v0.1.3
|
||||
Platforms: amd
|
||||
|
||||
Your code may be affected by 1 vulnerability.
|
||||
Use '-scan symbol' for more fine grained vulnerability detection.
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "module"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0001",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Third-party vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"amd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0001"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0002",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Third-party vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0002"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0002",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
=== Module Results ===
|
||||
|
||||
Vulnerability #1: GO-0000-0002
|
||||
Third-party vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0002
|
||||
Module: golang.org/vmod
|
||||
Found in: golang.org/vmod@v0.0.1
|
||||
Fixed in: golang.org/vmod@v0.1.3
|
||||
|
||||
Vulnerability #2: GO-0000-0001
|
||||
Third-party vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0001
|
||||
Module: golang.org/vmod
|
||||
Found in: golang.org/vmod@v0.0.1
|
||||
Fixed in: golang.org/vmod@v0.1.3
|
||||
Platforms: amd
|
||||
|
||||
Your code may be affected by 2 vulnerabilities.
|
||||
Use '-scan symbol' for more fine grained vulnerability detection.
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "symbol"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0001",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Third-party vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"amd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0001"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "vmod",
|
||||
"function": "Vuln"
|
||||
},
|
||||
{
|
||||
"module": "golang.org/main",
|
||||
"version": "v0.0.1",
|
||||
"package": "main",
|
||||
"function": "main"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "vmod",
|
||||
"function": "VulnFoo"
|
||||
},
|
||||
{
|
||||
"module": "golang.org/main",
|
||||
"version": "v0.0.1",
|
||||
"package": "main",
|
||||
"function": "main"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.0.4",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod1",
|
||||
"version": "v0.0.3",
|
||||
"package": "vmod1",
|
||||
"function": "Vuln"
|
||||
},
|
||||
{
|
||||
"module": "golang.org/other",
|
||||
"version": "v2.0.3",
|
||||
"package": "other",
|
||||
"function": "Foo"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.0.4",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod1",
|
||||
"version": "v0.0.3",
|
||||
"package": "vmod1",
|
||||
"function": "VulnFoo"
|
||||
},
|
||||
{
|
||||
"module": "golang.org/other",
|
||||
"version": "v2.0.3",
|
||||
"package": "other",
|
||||
"function": "Bar"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
=== Symbol Results ===
|
||||
|
||||
Vulnerability #1: GO-0000-0001
|
||||
Third-party vulnerability
|
||||
More info: https://pkg.go.dev/vuln/GO-0000-0001
|
||||
Module: golang.org/vmod
|
||||
Found in: golang.org/vmod@v0.0.1
|
||||
Fixed in: golang.org/vmod@v0.1.3
|
||||
Platforms: amd
|
||||
Example traces found:
|
||||
#1: main.main calls vmod.Vuln
|
||||
#2: main.main calls vmod.VulnFoo
|
||||
|
||||
Module: golang.org/vmod1
|
||||
Found in: golang.org/vmod1@v0.0.3
|
||||
Fixed in: golang.org/vmod1@v0.0.4
|
||||
Example traces found:
|
||||
#1: other.Foo calls vmod1.Vuln
|
||||
#2: other.Bar calls vmod1.VulnFoo
|
||||
|
||||
Your code is affected by 1 vulnerability from the Go standard library.
|
||||
This scan found no other vulnerabilities in packages you import or modules you
|
||||
require.
|
||||
Use '-show verbose' for more details.
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"config": {
|
||||
"protocol_version": "v0.1.0",
|
||||
"scanner_name": "govulncheck",
|
||||
"scan_level": "package"
|
||||
}
|
||||
}
|
||||
{
|
||||
"osv": {
|
||||
"id": "GO-0000-0001",
|
||||
"modified": "0001-01-01T00:00:00Z",
|
||||
"published": "0001-01-01T00:00:00Z",
|
||||
"details": "Third-party vulnerability",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/vmod",
|
||||
"ecosystem": ""
|
||||
},
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"goos": [
|
||||
"amd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-0000-0001"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-0000-0001",
|
||||
"fixed_version": "v0.1.3",
|
||||
"trace": [
|
||||
{
|
||||
"module": "golang.org/vmod",
|
||||
"version": "v0.0.1",
|
||||
"package": "golang.org/vmod"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user