Skip to content

Commit

Permalink
Merge pull request #1856 from puerco/bincheck
Browse files Browse the repository at this point in the history
Binary package: Base module for binary analysis
  • Loading branch information
k8s-ci-robot authored Jan 21, 2021
2 parents 5a5d7a6 + c45bd6a commit 8a27364
Show file tree
Hide file tree
Showing 7 changed files with 1,040 additions and 0 deletions.
131 changes: 131 additions & 0 deletions pkg/binary/binary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
Copyright 2021 The Kubernetes Authors.
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 binary

import (
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
const (
// GOOS labels
LINUX = "linux"
DARWIN = "darwin"
WIN = "windows"

// GOARCH Architecture labels
I386 = "386"
AMD64 = "amd64"
ARM = "arm"
ARM64 = "arm64"
PPC = "ppc"
PPC64LE = "ppc64le"
S390 = "s390"
RISCV = "riscv"
)

// Binary is the base type of the package. It abstracts a binary executable
type Binary struct {
options *Options
binaryImplementation
}

// Options to control the binary checker
type Options struct {
}

// DefaultOptions set of options
var DefaultOptions = &Options{}

// New creates a new binary instance.
func New(filePath string) (bin *Binary, err error) {
// Get the right implementation for the specified file
return NewWithOptions(filePath, DefaultOptions)
}

// NewWithOptions creates a new binary with the specified options
func NewWithOptions(filePath string, opts *Options) (bin *Binary, err error) {
bin = &Binary{
options: opts,
}
// Get the right implementation for the specified file
impl, err := getArchImplementation(filePath, opts)
if err != nil {
return nil, errors.Wrap(err, "getting arch implementation")
}
bin.SetImplementation(impl)
return bin, nil
}

// getArchImplementation returns the implementation that corresponds
// to the specified binary
func getArchImplementation(filePath string, opts *Options) (impl binaryImplementation, err error) {
// Check if we're dealing with a Linux binary
elf, err := NewELFBinary(filePath, opts)
if err != nil {
return nil, errors.Wrap(err, "checking if file is an ELF binary")
}
if elf != nil {
return elf, nil
}

// Check if its a darwin binary
macho, err := NewMachOBinary(filePath, opts)
if err != nil {
return nil, errors.Wrap(err, "checking if file is a Mach-O binary")
}
if macho != nil {
return macho, nil
}

// Finally we check to see if it's a windows binary
pe, err := NewPEBinary(filePath, opts)
if err != nil {
return nil, errors.Wrap(err, "checking if file is a windows PE binary")
}
if pe != nil {
return pe, nil
}

logrus.Warnf("File is not a known executable: %s", filePath)
return nil, errors.New("file is not an executable or is an unknown format")
}

//counterfeiter:generate . binaryImplementation
type binaryImplementation interface {
// GetArch Returns a string with the GOARCH of the binary
Arch() string

// GetOS Returns a string with the GOOS of the binary
OS() string
}

// SetImplementation sets the implementation to handle this sort of executable
func (b *Binary) SetImplementation(impl binaryImplementation) {
b.binaryImplementation = impl
}

// Arch returns a string with the GOARCH label of the file
func (b *Binary) Arch() string {
return b.binaryImplementation.Arch()
}

// OS returns a string with the GOOS label of the binary file
func (b *Binary) OS() string {
return b.binaryImplementation.OS()
}
173 changes: 173 additions & 0 deletions pkg/binary/binary_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
Copyright 2021 The Kubernetes Authors.
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 binary_test

import (
"encoding/base64"
"fmt"
"io/ioutil"
"os"
"testing"

"github.com/stretchr/testify/require"
"k8s.io/release/pkg/binary"
"k8s.io/release/pkg/binary/binaryfakes"
)

type TestHeader struct {
Bits int
Arch string
OS string
Data string
}

// GetTestHeaders returns an array of test binary fragments. The base64 encoded data
// corresponds to the first bytes (between 128 and 512) of the kubectl executables
// of the Kubernetes v1.20.2 release. They are note meant to be the full excecutables,
// only the first bytes to test the header analysis functions.
func GetTestHeaders() []TestHeader {
return []TestHeader{
{
Bits: 64,
Arch: "amd64",
OS: "linux",
Data: "f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAwPZGAAAAAABAAAAAAAAAAJABAAAAAAAAAAAAAEAAOAAGAEAADQADAAYAAAAEAAAAQAAAAAAAAABAAEAAAAAAAEAAQAAAAAAAUAEAAAAAAABQAQAAAAAAAAAQAAAAAAAAAQAAAAUAAAA=",
},
{
Bits: 64,
Arch: "s390x",
OS: "linux",
Data: "f0VMRgICAQAAAAAAAAAAAAACABYAAAABAAAAAAAIFwAAAAAAAAAAQAAAAAAAAAGQAAAAAQBAADgABgBAAA0AAwAAAAYAAAAEAAAAAAAAAEAAAAAAAAEAQAAAAAAAAQBAAAAAAA==",
},
{
Bits: 64,
Arch: "ppc64le",
OS: "linux",
Data: "f0VMRgIBAQAAAAAAAAAAAAIAFQABAAAAAKwHAAAAAABAAAAAAAAAAJABAAAAAAAAAgAAAEAAOAAGAEAADQADAAYAAAAEAAAAQAAAAAAAAABAAAEAAAAAAEAAAQAAAAAAUAEAAA==",
},
{
Bits: 64,
Arch: "arm64",
OS: "linux",
Data: "f0VMRgIBAQAAAAAAAAAAAAIAtwABAAAAkGQHAAAAAABAAAAAAAAAAJABAAAAAAAAAAAAAEAAOAAGAEAADQADAAYAAAAEAAAAQAAAAAAAAABAAAEAAAAAAEAAAQAAAAAAUAEAAA==",
},
{
Bits: 32,
Arch: "386",
OS: "linux",
Data: "f0VMRgEBAQAAAAAAAAAAAAIAAwABAAAA8JsKCDQAAAD0AAAAAAAAADQAIAAGACgADQADAAYAAAA0AAAANIAECDSABAjAAAAAwAAAAAQAAAAAEAAAAQAAAAAAAAAAgAQIAIAECA==",
},
{
Bits: 64,
Arch: "amd64",
OS: "darwin",
Data: "z/rt/gcAAAEDAAAAAgAAAAwAAACABwAAAAAAAAAAAAAZAAAASAAAAF9fUEFHRVpFUk8AAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
},
{
Bits: 32,
Arch: "386",
OS: "windows",
Data: "TVqQAAMABAAAAAAA//8AAIsAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAEGAAAAAAAAqi8CAAAAAOAAAgMLAQMAAHgJAQAyHQAAAAAAMBYGAAAQAAAAUP8BAABAAAAQAAAAAgAABgABAAEAAAAGAAEAAAAAAAAgMgIABAAAAAAAAAMAQIEAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAAA==",
},
{
Bits: 64,
Arch: "amd64",
OS: "windows",
Data: "TVqQAAMABAAAAAAA//8AAIsAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAAZIYGAAAAAAAAXHgCAAAAAPAAIgILAgMAAAw9AQBOHgAAAAAAABMHAAAQAAAAAEAAAAAAAAAQAAAAAgAABgABAAEAAAAGAAEAAAAAAAAwfQIABgAAAAAAAAMAYIEAACAAAAAAAAAQAAAAAAAAAAAQAAAAAAAAEAAAAAAAAA==",
},
}
}

// writeTestBinary Writes a test binary and returns the path
func writeTestBinary(t *testing.T, base64Data *string) *os.File {
f, err := ioutil.TempFile(os.TempDir(), "test-binary-")
require.Nil(t, err)

binData, err := base64.StdEncoding.DecodeString(*base64Data)
require.Nil(t, err)

_, err = f.Write(binData)
require.Nil(t, err)

_, err = f.Seek(0, 0)
require.Nil(t, err)

return f
}

func TestOS(t *testing.T) {
mock := &binaryfakes.FakeBinaryImplementation{}
mock.OSReturns("darwin")
sut := &binary.Binary{}
sut.SetImplementation(mock)

require.Equal(t, "darwin", sut.OS())
}

func TestArch(t *testing.T) {
mock := &binaryfakes.FakeBinaryImplementation{}
mock.ArchReturns("amd64")
sut := &binary.Binary{}
sut.SetImplementation(mock)

require.Equal(t, "amd64", sut.Arch())
}

func TestGetELFHeader(t *testing.T) {
for _, testBin := range GetTestHeaders() {
f := writeTestBinary(t, &testBin.Data)
defer os.Remove(f.Name())
header, err := binary.GetELFHeader(f.Name())
require.Nil(t, err)
if testBin.OS == "linux" {
require.NotNil(t, header)
require.Equal(t, testBin.Bits, header.WordLength())
} else {
require.Nil(t, header)
}
}
}

func TestGetMachOHeader(t *testing.T) {
for _, testBin := range GetTestHeaders() {
f := writeTestBinary(t, &testBin.Data)
defer os.Remove(f.Name())
header, err := binary.GetMachOHeader(f.Name())
require.Nil(t, err)
if testBin.OS == "darwin" {
require.NotNil(t, header)
require.Equal(t, testBin.Bits, header.WordLength())
} else {
require.Nil(t, header)
}
}
}

func TestGetPEHeader(t *testing.T) {
for _, testBin := range GetTestHeaders() {
f := writeTestBinary(t, &testBin.Data)
defer os.Remove(f.Name())
header, err := binary.GetPEHeader(f.Name())
require.Nil(t, err)
if testBin.OS == "windows" {
require.NotNil(t, header, fmt.Sprintf("testing binary for %s/%s", testBin.OS, testBin.Arch))
require.Equal(t, testBin.Bits, header.WordLength())
} else {
require.Nil(t, header)
}
}
}
Loading

0 comments on commit 8a27364

Please sign in to comment.