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 }