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