-
Notifications
You must be signed in to change notification settings - Fork 10
/
extractor.go
148 lines (122 loc) · 4.27 KB
/
extractor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package gobinary extracts packages from buildinfo inside go binaries files.
package gobinary
import (
"context"
"debug/buildinfo"
"errors"
"io"
"io/fs"
"path/filepath"
"runtime/debug"
"strings"
"github.com/google/osv-scalibr/extractor"
"github.com/google/osv-scalibr/extractor/filesystem"
"github.com/google/osv-scalibr/log"
"github.com/google/osv-scalibr/purl"
)
const (
// Name is the unique name of this extractor.
Name = "go/binary"
)
// Extractor extracts packages from buildinfo inside go binaries files.
type Extractor struct{}
type packageJSON struct {
Version string `json:"version"`
Name string `json:"name"`
}
const permissiveVersionParsing = true
// Name of the extractor.
func (e Extractor) Name() string { return Name }
// Version of the extractor.
func (e Extractor) Version() int { return 0 }
// FileRequired returns true if the specified file is marked executable.
func (e Extractor) FileRequired(path string, mode fs.FileMode) bool {
if !mode.IsRegular() {
// Includes dirs, symlinks, sockets, pipes...
return false
}
// TODO(b/279138598): Research: Maybe on windows all files have the executable bit set.
// Either windows .exe or unix executable bit is set.
return filepath.Ext(path) == ".exe" || mode&0111 != 0
}
// Extract returns a list of installed third party dependencies in a Go binary.
func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) ([]*extractor.Inventory, error) {
binfo, err := buildinfo.Read(input.Reader.(io.ReaderAt))
if err != nil {
log.Debugf("error parsing the contents of Go binary (%s) for extraction: %v", input.Path, err)
return []*extractor.Inventory{}, nil
}
return e.extractPackagesFromBuildInfo(binfo, input.Path)
}
func (e *Extractor) extractPackagesFromBuildInfo(binfo *buildinfo.BuildInfo, filename string) ([]*extractor.Inventory, error) {
res := []*extractor.Inventory{}
validatedGoVers, err := validateGoVersion(binfo.GoVersion)
if err != nil {
log.Warnf("failed to validate the Go version from buildinfo (%v): %v", binfo, err)
}
if validatedGoVers != "" {
res = append(res, &extractor.Inventory{
Name: "go",
Version: validatedGoVers,
Locations: []string{filename},
})
}
for _, dep := range binfo.Deps {
pkgName, pkgVers := parseDependency(dep)
if pkgName == "" {
continue
}
pkgVers = strings.TrimPrefix(pkgVers, "v")
pkg := &extractor.Inventory{
Name: pkgName,
Version: pkgVers,
Locations: []string{filename},
}
res = append(res, pkg)
}
return res, nil
}
func validateGoVersion(vers string) (string, error) {
if vers == "" {
return "", errors.New("can't validate empty Go version")
}
// The Go version can have multiple parts, in particular for development
// versions of Go. The actual Go version should be the first part (e.g.
// 'go1.20-pre3 +a813be86df' -> 'go1.20-pre3')
goVersion := strings.Split(vers, " ")[0]
// Strip the "go" prefix from the Go version. (e.g. go1.16.3 => 1.16.3)
res := strings.TrimPrefix(goVersion, "go")
return res, nil
}
func parseDependency(d *debug.Module) (string, string) {
dep := d
// Handle module replacement, but don't replace module if the replacement
// doesn't have a package name.
if dep.Replace != nil && dep.Replace.Path != "" {
dep = dep.Replace
}
return dep.Path, dep.Version
}
// ToPURL converts an inventory created by this extractor into a PURL.
func (e Extractor) ToPURL(i *extractor.Inventory) (*purl.PackageURL, error) {
return &purl.PackageURL{
Type: purl.TypeGolang,
Name: i.Name,
Version: i.Version,
}, nil
}
// ToCPEs is not applicable as this extractor does not infer CPEs from the Inventory.
func (e Extractor) ToCPEs(i *extractor.Inventory) ([]string, error) { return []string{}, nil }