| // 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 binscan contains methods for parsing Go binary files for the purpose |
| // of extracting module dependency and symbol table information. |
| package binscan |
| |
| // 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/vulncheck/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 |
| } |
| |
| // ExtractPackagesAndSymbols extracts the symbols, packages, and their |
| // associated module versions from a Go binary. |
| func ExtractPackagesAndSymbols(bin io.ReaderAt) ([]*packages.Module, map[string][]string, error) { |
| bi, err := buildinfo.Read(bin) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| x, err := openExe(bin) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| pclntab, textOffset := x.PCLNTab() |
| if pclntab == nil { |
| // TODO(roland): if we have build information, but not PCLN table, we should be able to |
| // fall back to much higher granularity vulnerability checking. |
| return nil, nil, errors.New("unable to load the PCLN table") |
| } |
| lineTab := gosym.NewLineTable(pclntab, textOffset) |
| if lineTab == nil { |
| return nil, nil, errors.New("invalid line table") |
| } |
| tab, err := gosym.NewTable(nil, lineTab) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| packageSymbols := map[string][]string{} |
| for _, f := range tab.Funcs { |
| if f.Func == nil { |
| continue |
| } |
| pkgName, symName, err := parseName(f.Func.Sym) |
| if err != nil { |
| return nil, nil, err |
| } |
| packageSymbols[pkgName] = append(packageSymbols[pkgName], symName) |
| value, base, r, err := x.SymbolInfo("go.func.*") |
| if err != nil { |
| return nil, nil, fmt.Errorf("reading go.func.*: %v", err) |
| } |
| it, err := lineTab.InlineTree(&f, value, base, r) |
| if err != nil { |
| return 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, err |
| } |
| packageSymbols[pkgName] = append(packageSymbols[pkgName], symName) |
| } |
| } |
| |
| return debugModulesToPackagesModules(bi.Deps), packageSymbols, 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 |
| } |