// Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build go1.18 // +build go1.18 package buildinfo // Code in this package is dervied from src/cmd/go/internal/version/version.go // and cmd/go/internal/version/exe.go. import ( "debug/buildinfo" "errors" "fmt" "net/url" "os" "runtime/debug" "strings" "golang.org/x/tools/go/packages" "golang.org/x/vuln/internal/gosym" "golang.org/x/vuln/internal/goversion" ) func debugModulesToPackagesModules(debugModules []*debug.Module) []*packages.Module { packagesModules := make([]*packages.Module, len(debugModules)) for i, mod := range debugModules { packagesModules[i] = &packages.Module{ Path: mod.Path, Version: mod.Version, } if mod.Replace != nil { packagesModules[i].Replace = &packages.Module{ Path: mod.Replace.Path, Version: mod.Replace.Version, } } } return packagesModules } type Symbol struct { Pkg string `json:"pkg,omitempty"` Name string `json:"name,omitempty"` } // ExtractPackagesAndSymbols extracts symbols, packages, modules from // Go binary file as well as bin's metadata. // // If the symbol table is not available, such as in the case of stripped // binaries, returns module and binary info but without the symbol info. func ExtractPackagesAndSymbols(file string) ([]*packages.Module, []Symbol, *debug.BuildInfo, error) { bin, err := os.Open(file) if err != nil { return nil, nil, nil, err } defer bin.Close() bi, err := buildinfo.Read(bin) if err != nil { // It could be that bin is an ancient Go binary. v, err := goversion.ReadExe(file) if err != nil { return nil, nil, nil, err } bi := &debug.BuildInfo{ GoVersion: v.Release, Main: debug.Module{Path: v.ModuleInfo}, } // We cannot analyze symbol tables of ancient binaries. return nil, nil, bi, nil } funcSymName := gosym.FuncSymName(bi.GoVersion) if funcSymName == "" { return nil, nil, nil, fmt.Errorf("binary built using unsupported Go version: %q", bi.GoVersion) } x, err := openExe(bin) if err != nil { return nil, nil, nil, err } value, base, r, err := x.SymbolInfo(funcSymName) if err != nil { if errors.Is(err, ErrNoSymbols) { // bin is stripped, so return just module info and metadata. return debugModulesToPackagesModules(bi.Deps), nil, bi, nil } return nil, nil, nil, fmt.Errorf("reading %v: %v", funcSymName, err) } pclntab, textOffset := x.PCLNTab() if pclntab == nil { // If we have build information, but not PCLN table, fall // back to much higher granularity vulnerability checking. return debugModulesToPackagesModules(bi.Deps), nil, bi, nil } lineTab := gosym.NewLineTable(pclntab, textOffset) if lineTab == nil { return nil, nil, nil, errors.New("invalid line table") } tab, err := gosym.NewTable(nil, lineTab) if err != nil { return nil, nil, nil, err } pkgSyms := make(map[Symbol]bool) for _, f := range tab.Funcs { if f.Func == nil { continue } pkgName, symName, err := parseName(f.Func.Sym) if err != nil { return nil, nil, nil, err } pkgSyms[Symbol{pkgName, symName}] = true // Collect symbols that were inlined in f. it, err := lineTab.InlineTree(&f, value, base, r) if err != nil { return nil, nil, nil, fmt.Errorf("InlineTree: %v", err) } for _, ic := range it { pkgName, symName, err := parseName(&gosym.Sym{Name: ic.Name}) if err != nil { return nil, nil, nil, err } pkgSyms[Symbol{pkgName, symName}] = true } } var syms []Symbol for ps := range pkgSyms { syms = append(syms, ps) } return debugModulesToPackagesModules(bi.Deps), syms, bi, nil } func parseName(s *gosym.Sym) (pkg, sym string, err error) { symName := s.BaseName() if r := s.ReceiverName(); r != "" { if strings.HasPrefix(r, "(*") { r = strings.Trim(r, "(*)") } symName = fmt.Sprintf("%s.%s", r, symName) } pkgName := s.PackageName() if pkgName != "" { pkgName, err = url.PathUnescape(pkgName) if err != nil { return "", "", err } } return pkgName, symName, nil }