whatcanGOwrong

This commit is contained in:
2024-09-19 21:38:24 -04:00
commit d0ae4d841d
17908 changed files with 4096831 additions and 0 deletions
@@ -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)
})
}
@@ -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"}]}
@@ -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"}]}
@@ -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"
}
]
}
@@ -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"
}
]
}
@@ -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"
}
]
}
@@ -0,0 +1,5 @@
[
"GO-2022-0463",
"GO-2022-0569",
"GO-2022-0572"
]
File diff suppressed because it is too large Load Diff
@@ -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"
}
]
}
]
@@ -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"
}
]
}
]
@@ -0,0 +1,4 @@
{
"github.com/BeeGo/beego": "2022-08-23T19:54:38Z",
"github.com/tidwall/gjson": "2022-08-23T19:54:38Z"
}
@@ -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"}}
@@ -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"}}
@@ -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"}}
@@ -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"}}
@@ -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"}}
@@ -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"}}
File diff suppressed because one or more lines are too long
@@ -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"}}
@@ -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"}}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"modified":"2023-04-03T15:57:51Z"}
@@ -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"}]}]
@@ -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"]}]
@@ -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;
@@ -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.
@@ -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"
}
]
}
}
@@ -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.
@@ -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