Add cataloging of macho multi-architecture binaries (#657)

* add cataloging within universal binaries

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update json test fixtures

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add comments + correct 32 bit multi arch magic check

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman 2021-12-08 16:25:24 -05:00 committed by GitHub
parent c7752f0f6c
commit da0b17b719
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 1185 additions and 23 deletions

View file

@ -6,5 +6,5 @@ const (
// JSONSchemaVersion is the current schema version output by the JSON presenter
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "2.0.0"
JSONSchemaVersion = "2.0.1"
)

View file

@ -60,8 +60,6 @@ func AssertPresenterAgainstGoldenImageSnapshot(t *testing.T, pres presenter.Pres
if !bytes.Equal(expected, actual) {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(string(expected), string(actual), true)
t.Logf("len: %d\nexpected: %v", len(expected), expected)
t.Logf("len: %d\nactual: %v", len(actual), actual)
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
}
}

View file

@ -83,7 +83,7 @@
}
},
"schema": {
"version": "2.0.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-2.0.0.json"
"version": "2.0.1",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-2.0.1.json"
}
}

View file

@ -179,7 +179,7 @@
}
},
"schema": {
"version": "2.0.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-2.0.0.json"
"version": "2.0.1",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-2.0.1.json"
}
}

View file

@ -104,7 +104,7 @@
}
},
"schema": {
"version": "2.0.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-2.0.0.json"
"version": "2.0.1",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-2.0.1.json"
}
}

View file

@ -35,6 +35,7 @@ type artifactMetadataContainer struct {
Python pkg.PythonPackageMetadata
Rpm pkg.RpmdbMetadata
Cargo pkg.CargoPackageMetadata
Go pkg.GolangBinMetadata
}
func main() {

File diff suppressed because it is too large Load diff

View file

@ -14,6 +14,7 @@ import (
"debug/pe"
"fmt"
"io"
"strings"
)
// An exe is a generic interface to an OS executable (ELF, Mach-O, PE, XCOFF).
@ -24,6 +25,9 @@ type exe interface {
// ReadData reads and returns up to size byte starting at virtual address addr.
ReadData(addr, size uint64) ([]byte, error)
// ArchName returns a string that represents the CPU architecture of the executable.
ArchName() string
// DataStart returns the writable data segment start address.
DataStart() uint64
}
@ -32,7 +36,7 @@ type exe interface {
// we changed this signature from accpeting a string
// to a ReadCloser so we could adapt the code to the
// stereoscope api. We removed the file open methods.
func openExe(file io.ReadCloser) (exe, error) {
func openExe(file io.ReadCloser) ([]exe, error) {
/*
f, err := os.Open(file)
if err != nil {
@ -57,7 +61,7 @@ func openExe(file io.ReadCloser) (exe, error) {
return nil, err
}
return &elfExe{file, e}, nil
return []exe{&elfExe{file, e}}, nil
}
if bytes.HasPrefix(data, []byte("MZ")) {
@ -65,7 +69,7 @@ func openExe(file io.ReadCloser) (exe, error) {
if err != nil {
return nil, err
}
return &peExe{file, e}, nil
return []exe{&peExe{file, e}}, nil
}
if bytes.HasPrefix(data, []byte("\xFE\xED\xFA")) || bytes.HasPrefix(data[1:], []byte("\xFA\xED\xFE")) {
@ -73,7 +77,20 @@ func openExe(file io.ReadCloser) (exe, error) {
if err != nil {
return nil, err
}
return &machoExe{file, e}, nil
return []exe{&machoExe{file, e}}, nil
}
// adding macho multi-architecture support (both for 64bit and 32 bit)... this case is not in the stdlib yet
if bytes.HasPrefix(data, []byte("\xCA\xFE\xBA\xBE")) || bytes.HasPrefix(data, []byte("\xCA\xFE\xBA\xBF")) {
fatExe, err := macho.NewFatFile(f)
if err != nil {
return nil, err
}
var exes []exe
for _, arch := range fatExe.Arches {
exes = append(exes, &machoExe{file, arch.File})
}
return exes, nil
}
return nil, fmt.Errorf("unrecognized executable format")
@ -90,6 +107,14 @@ func (x *elfExe) Close() error {
return x.os.Close()
}
func (x *elfExe) ArchName() string {
return cleanElfArch(x.f.Machine)
}
func cleanElfArch(machine elf.Machine) string {
return strings.TrimPrefix(strings.ToLower(machine.String()), "em_")
}
func (x *elfExe) ReadData(addr, size uint64) ([]byte, error) {
for _, prog := range x.f.Progs {
if prog.Vaddr <= addr && addr <= prog.Vaddr+prog.Filesz-1 {
@ -132,6 +157,56 @@ func (x *peExe) Close() error {
return x.os.Close()
}
func (x *peExe) ArchName() string {
// from: debug/pe/pe.go
switch x.f.Machine {
case pe.IMAGE_FILE_MACHINE_AM33:
return "amd33"
case pe.IMAGE_FILE_MACHINE_AMD64:
return "amd64"
case pe.IMAGE_FILE_MACHINE_ARM:
return "arm"
case pe.IMAGE_FILE_MACHINE_ARMNT:
return "armnt"
case pe.IMAGE_FILE_MACHINE_ARM64:
return "arm64"
case pe.IMAGE_FILE_MACHINE_EBC:
return "ebc"
case pe.IMAGE_FILE_MACHINE_I386:
return "i386"
case pe.IMAGE_FILE_MACHINE_IA64:
return "ia64"
case pe.IMAGE_FILE_MACHINE_M32R:
return "m32r"
case pe.IMAGE_FILE_MACHINE_MIPS16:
return "mips16"
case pe.IMAGE_FILE_MACHINE_MIPSFPU:
return "mipsfpu"
case pe.IMAGE_FILE_MACHINE_MIPSFPU16:
return "mipsfpu16"
case pe.IMAGE_FILE_MACHINE_POWERPC:
return "ppc"
case pe.IMAGE_FILE_MACHINE_POWERPCFP:
return "ppcfp"
case pe.IMAGE_FILE_MACHINE_R4000:
return "r4000"
case pe.IMAGE_FILE_MACHINE_SH3:
return "sh3"
case pe.IMAGE_FILE_MACHINE_SH3DSP:
return "sh3dsp"
case pe.IMAGE_FILE_MACHINE_SH4:
return "sh4"
case pe.IMAGE_FILE_MACHINE_SH5:
return "sh5"
case pe.IMAGE_FILE_MACHINE_THUMB:
return "thumb"
case pe.IMAGE_FILE_MACHINE_WCEMIPSV2:
return "wcemipsv2"
default:
return fmt.Sprintf("unknown-pe-machine-%d", x.f.Machine)
}
}
func (x *peExe) imageBase() uint64 {
switch oh := x.f.OptionalHeader.(type) {
case *pe.OptionalHeader32:
@ -193,6 +268,14 @@ func (x *machoExe) Close() error {
return x.os.Close()
}
func (x *machoExe) ArchName() string {
return cleanMachoArch(x.f.Cpu)
}
func cleanMachoArch(cpu macho.Cpu) string {
return strings.TrimPrefix(strings.ToLower(cpu.String()), "cpu")
}
func (x *machoExe) ReadData(addr, size uint64) ([]byte, error) {
for _, load := range x.f.Loads {
seg, ok := load.(*macho.Segment)

View file

@ -0,0 +1,43 @@
package golang
import (
"debug/elf"
"debug/macho"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_cleanElfArch(t *testing.T) {
tests := []struct {
machine elf.Machine
want string
}{
{
machine: elf.EM_X86_64,
want: "x86_64",
},
}
for _, test := range tests {
t.Run(test.machine.String(), func(t *testing.T) {
assert.Equalf(t, test.want, cleanElfArch(test.machine), "cleanElfArch(%v)", test.machine)
})
}
}
func Test_cleanMachoArch(t *testing.T) {
tests := []struct {
cpu macho.Cpu
want string
}{
{
cpu: macho.CpuAmd64,
want: "amd64",
},
}
for _, test := range tests {
t.Run(test.cpu.String(), func(t *testing.T) {
assert.Equalf(t, test.want, cleanMachoArch(test.cpu), "cleanMachoArch(%v)", test.cpu)
})
}
}

View file

@ -16,17 +16,20 @@ const (
func parseGoBin(location source.Location, reader io.ReadCloser) ([]pkg.Package, error) {
// Identify if bin was compiled by go
x, err := openExe(reader)
exes, err := openExe(reader)
if err != nil {
return nil, err
}
goVersion, mod := findVers(x)
return buildGoPkgInfo(location, mod, goVersion), nil
var pkgs []pkg.Package
for _, x := range exes {
goVersion, mod := findVers(x)
pkgs = append(pkgs, buildGoPkgInfo(location, mod, goVersion, x.ArchName())...)
}
return pkgs, nil
}
func buildGoPkgInfo(location source.Location, mod, goVersion string) []pkg.Package {
func buildGoPkgInfo(location source.Location, mod, goVersion, arch string) []pkg.Package {
pkgsSlice := make([]pkg.Package, 0)
scanner := bufio.NewScanner(strings.NewReader(mod))
@ -52,6 +55,7 @@ func buildGoPkgInfo(location source.Location, mod, goVersion string) []pkg.Packa
Metadata: pkg.GolangBinMetadata{
GoCompiledVersion: goVersion,
H1Digest: fields[3],
Architecture: arch,
},
})
}

View file

@ -8,9 +8,11 @@ import (
"github.com/stretchr/testify/assert"
)
const goCompiledVersion = "1.17"
func TestBuildGoPkgInfo(t *testing.T) {
const (
goCompiledVersion = "1.17"
archDetails = "amd64"
)
tests := []struct {
name string
mod string
@ -43,6 +45,7 @@ func TestBuildGoPkgInfo(t *testing.T) {
MetadataType: pkg.GolangBinMetadataType,
Metadata: pkg.GolangBinMetadata{
GoCompiledVersion: goCompiledVersion,
Architecture: archDetails,
H1Digest: "h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=",
},
},
@ -62,6 +65,7 @@ func TestBuildGoPkgInfo(t *testing.T) {
MetadataType: pkg.GolangBinMetadataType,
Metadata: pkg.GolangBinMetadata{
GoCompiledVersion: goCompiledVersion,
Architecture: archDetails,
H1Digest: "h1:DYssiUV1pBmKqzKsm4mqXx8artqC0Q8HgZsVI3lMsAg=",
},
},
@ -92,6 +96,7 @@ func TestBuildGoPkgInfo(t *testing.T) {
MetadataType: pkg.GolangBinMetadataType,
Metadata: pkg.GolangBinMetadata{
GoCompiledVersion: goCompiledVersion,
Architecture: archDetails,
H1Digest: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k=",
},
},
@ -111,6 +116,7 @@ func TestBuildGoPkgInfo(t *testing.T) {
MetadataType: pkg.GolangBinMetadataType,
Metadata: pkg.GolangBinMetadata{
GoCompiledVersion: goCompiledVersion,
Architecture: archDetails,
H1Digest: "h1:PjhxBct4MZii8FFR8+oeS7QOvxKOTZXgk63EU2XpfJE=",
},
},
@ -130,6 +136,7 @@ func TestBuildGoPkgInfo(t *testing.T) {
MetadataType: pkg.GolangBinMetadataType,
Metadata: pkg.GolangBinMetadata{
GoCompiledVersion: goCompiledVersion,
Architecture: archDetails,
H1Digest: "h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI=",
},
},
@ -146,7 +153,7 @@ func TestBuildGoPkgInfo(t *testing.T) {
FileSystemID: "layer-id",
},
}
pkgs := buildGoPkgInfo(location, tt.mod, goCompiledVersion)
pkgs := buildGoPkgInfo(location, tt.mod, goCompiledVersion, archDetails)
assert.Equal(t, tt.expected, pkgs)
})
}

View file

@ -2,6 +2,7 @@ package pkg
// GolangBinMetadata represents all captured data for a Golang Binary
type GolangBinMetadata struct {
GoCompiledVersion string
H1Digest string
GoCompiledVersion string `json:"goCompiledVersion"`
Architecture string `json:"architecture"`
H1Digest string `json:"h1Digest"`
}