src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

additions_scan.go (4110B)


      1 // Copyright 2021 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 //go:build go1.18
      6 
      7 package buildinfo
      8 
      9 // Code in this package is dervied from src/cmd/go/internal/version/version.go
     10 // and cmd/go/internal/version/exe.go.
     11 
     12 import (
     13 	"debug/buildinfo"
     14 	"errors"
     15 	"fmt"
     16 	"net/url"
     17 	"os"
     18 	"runtime/debug"
     19 	"strings"
     20 
     21 	"golang.org/x/tools/go/packages"
     22 	"golang.org/x/vuln/internal/gosym"
     23 	"golang.org/x/vuln/internal/goversion"
     24 )
     25 
     26 func debugModulesToPackagesModules(debugModules []*debug.Module) []*packages.Module {
     27 	packagesModules := make([]*packages.Module, len(debugModules))
     28 	for i, mod := range debugModules {
     29 		packagesModules[i] = &packages.Module{
     30 			Path:    mod.Path,
     31 			Version: mod.Version,
     32 		}
     33 		if mod.Replace != nil {
     34 			packagesModules[i].Replace = &packages.Module{
     35 				Path:    mod.Replace.Path,
     36 				Version: mod.Replace.Version,
     37 			}
     38 		}
     39 	}
     40 	return packagesModules
     41 }
     42 
     43 type Symbol struct {
     44 	Pkg  string `json:"pkg,omitempty"`
     45 	Name string `json:"name,omitempty"`
     46 }
     47 
     48 // ExtractPackagesAndSymbols extracts symbols, packages, modules from
     49 // Go binary file as well as bin's metadata.
     50 //
     51 // If the symbol table is not available, such as in the case of stripped
     52 // binaries, returns module and binary info but without the symbol info.
     53 func ExtractPackagesAndSymbols(file string) ([]*packages.Module, []Symbol, *debug.BuildInfo, error) {
     54 	bin, err := os.Open(file)
     55 	if err != nil {
     56 		return nil, nil, nil, err
     57 	}
     58 	defer bin.Close()
     59 
     60 	bi, err := buildinfo.Read(bin)
     61 	if err != nil {
     62 		// It could be that bin is an ancient Go binary.
     63 		v, err := goversion.ReadExe(file)
     64 		if err != nil {
     65 			return nil, nil, nil, err
     66 		}
     67 		bi := &debug.BuildInfo{
     68 			GoVersion: v.Release,
     69 			Main:      debug.Module{Path: v.ModuleInfo},
     70 		}
     71 		// We cannot analyze symbol tables of ancient binaries.
     72 		return nil, nil, bi, nil
     73 	}
     74 
     75 	funcSymName := gosym.FuncSymName(bi.GoVersion)
     76 	if funcSymName == "" {
     77 		return nil, nil, nil, fmt.Errorf("binary built using unsupported Go version: %q", bi.GoVersion)
     78 	}
     79 
     80 	x, err := openExe(bin)
     81 	if err != nil {
     82 		return nil, nil, nil, err
     83 	}
     84 
     85 	value, base, r, err := x.SymbolInfo(funcSymName)
     86 	if err != nil {
     87 		if errors.Is(err, ErrNoSymbols) {
     88 			// bin is stripped, so return just module info and metadata.
     89 			return debugModulesToPackagesModules(bi.Deps), nil, bi, nil
     90 		}
     91 		return nil, nil, nil, fmt.Errorf("reading %v: %v", funcSymName, err)
     92 	}
     93 
     94 	pclntab, textOffset := x.PCLNTab()
     95 	if pclntab == nil {
     96 		// If we have build information, but not PCLN table, fall
     97 		// back to much higher granularity vulnerability checking.
     98 		return debugModulesToPackagesModules(bi.Deps), nil, bi, nil
     99 	}
    100 	lineTab := gosym.NewLineTable(pclntab, textOffset)
    101 	if lineTab == nil {
    102 		return nil, nil, nil, errors.New("invalid line table")
    103 	}
    104 	tab, err := gosym.NewTable(nil, lineTab)
    105 	if err != nil {
    106 		return nil, nil, nil, err
    107 	}
    108 
    109 	pkgSyms := make(map[Symbol]bool)
    110 	for _, f := range tab.Funcs {
    111 		if f.Func == nil {
    112 			continue
    113 		}
    114 		pkgName, symName, err := parseName(f.Func.Sym)
    115 		if err != nil {
    116 			return nil, nil, nil, err
    117 		}
    118 		pkgSyms[Symbol{pkgName, symName}] = true
    119 
    120 		// Collect symbols that were inlined in f.
    121 		it, err := lineTab.InlineTree(&f, value, base, r)
    122 		if err != nil {
    123 			return nil, nil, nil, fmt.Errorf("InlineTree: %v", err)
    124 		}
    125 		for _, ic := range it {
    126 			pkgName, symName, err := parseName(&gosym.Sym{Name: ic.Name})
    127 			if err != nil {
    128 				return nil, nil, nil, err
    129 			}
    130 			pkgSyms[Symbol{pkgName, symName}] = true
    131 		}
    132 	}
    133 
    134 	var syms []Symbol
    135 	for ps := range pkgSyms {
    136 		syms = append(syms, ps)
    137 	}
    138 
    139 	return debugModulesToPackagesModules(bi.Deps), syms, bi, nil
    140 }
    141 
    142 func parseName(s *gosym.Sym) (pkg, sym string, err error) {
    143 	symName := s.BaseName()
    144 	if r := s.ReceiverName(); r != "" {
    145 		if strings.HasPrefix(r, "(*") {
    146 			r = strings.Trim(r, "(*)")
    147 		}
    148 		symName = fmt.Sprintf("%s.%s", r, symName)
    149 	}
    150 
    151 	pkgName := s.PackageName()
    152 	if pkgName != "" {
    153 		pkgName, err = url.PathUnescape(pkgName)
    154 		if err != nil {
    155 			return "", "", err
    156 		}
    157 	}
    158 	return pkgName, symName, nil
    159 }