diff --git a/pkg/binary/binary.go b/pkg/binary/binary.go index 17b80e6dc70..e867f68b3b0 100644 --- a/pkg/binary/binary.go +++ b/pkg/binary/binary.go @@ -56,13 +56,10 @@ type Options struct { Path string } -// 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) + return NewWithOptions(filePath, &Options{Path: filePath}) } // NewWithOptions creates a new binary with the specified options @@ -121,6 +118,9 @@ type binaryImplementation interface { // GetOS Returns a string with the GOOS of the binary OS() string + + // LinkMode returns the linking mode of the binary. + LinkMode() (LinkMode, error) } // SetImplementation sets the implementation to handle this sort of executable @@ -138,6 +138,20 @@ func (b *Binary) OS() string { return b.binaryImplementation.OS() } +// LinkMode is the enum for all available linking modes. +type LinkMode string + +const ( + LinkModeUnknown LinkMode = "unknown" + LinkModeStatic LinkMode = "static" + LinkModeDynamic LinkMode = "dynamic" +) + +// LinkMode returns the linking mode of the binary. +func (b *Binary) LinkMode() (LinkMode, error) { + return b.binaryImplementation.LinkMode() +} + // ContainsStrings searches the printable strings un a binary file func (b *Binary) ContainsStrings(s ...string) (match bool, err error) { // We cannot search for 0 items: diff --git a/pkg/binary/binaryfakes/fake_binary_implementation.go b/pkg/binary/binaryfakes/fake_binary_implementation.go index aaf9a237e22..9465abc8da8 100644 --- a/pkg/binary/binaryfakes/fake_binary_implementation.go +++ b/pkg/binary/binaryfakes/fake_binary_implementation.go @@ -19,6 +19,8 @@ package binaryfakes import ( "sync" + + "k8s.io/release/pkg/binary" ) type FakeBinaryImplementation struct { @@ -32,6 +34,18 @@ type FakeBinaryImplementation struct { archReturnsOnCall map[int]struct { result1 string } + LinkModeStub func() (binary.LinkMode, error) + linkModeMutex sync.RWMutex + linkModeArgsForCall []struct { + } + linkModeReturns struct { + result1 binary.LinkMode + result2 error + } + linkModeReturnsOnCall map[int]struct { + result1 binary.LinkMode + result2 error + } OSStub func() string oSMutex sync.RWMutex oSArgsForCall []struct { @@ -99,6 +113,62 @@ func (fake *FakeBinaryImplementation) ArchReturnsOnCall(i int, result1 string) { }{result1} } +func (fake *FakeBinaryImplementation) LinkMode() (binary.LinkMode, error) { + fake.linkModeMutex.Lock() + ret, specificReturn := fake.linkModeReturnsOnCall[len(fake.linkModeArgsForCall)] + fake.linkModeArgsForCall = append(fake.linkModeArgsForCall, struct { + }{}) + stub := fake.LinkModeStub + fakeReturns := fake.linkModeReturns + fake.recordInvocation("LinkMode", []interface{}{}) + fake.linkModeMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeBinaryImplementation) LinkModeCallCount() int { + fake.linkModeMutex.RLock() + defer fake.linkModeMutex.RUnlock() + return len(fake.linkModeArgsForCall) +} + +func (fake *FakeBinaryImplementation) LinkModeCalls(stub func() (binary.LinkMode, error)) { + fake.linkModeMutex.Lock() + defer fake.linkModeMutex.Unlock() + fake.LinkModeStub = stub +} + +func (fake *FakeBinaryImplementation) LinkModeReturns(result1 binary.LinkMode, result2 error) { + fake.linkModeMutex.Lock() + defer fake.linkModeMutex.Unlock() + fake.LinkModeStub = nil + fake.linkModeReturns = struct { + result1 binary.LinkMode + result2 error + }{result1, result2} +} + +func (fake *FakeBinaryImplementation) LinkModeReturnsOnCall(i int, result1 binary.LinkMode, result2 error) { + fake.linkModeMutex.Lock() + defer fake.linkModeMutex.Unlock() + fake.LinkModeStub = nil + if fake.linkModeReturnsOnCall == nil { + fake.linkModeReturnsOnCall = make(map[int]struct { + result1 binary.LinkMode + result2 error + }) + } + fake.linkModeReturnsOnCall[i] = struct { + result1 binary.LinkMode + result2 error + }{result1, result2} +} + func (fake *FakeBinaryImplementation) OS() string { fake.oSMutex.Lock() ret, specificReturn := fake.oSReturnsOnCall[len(fake.oSArgsForCall)] @@ -157,6 +227,8 @@ func (fake *FakeBinaryImplementation) Invocations() map[string][][]interface{} { defer fake.invocationsMutex.RUnlock() fake.archMutex.RLock() defer fake.archMutex.RUnlock() + fake.linkModeMutex.RLock() + defer fake.linkModeMutex.RUnlock() fake.oSMutex.RLock() defer fake.oSMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} diff --git a/pkg/binary/elf.go b/pkg/binary/elf.go index b20fbce5548..92a03030bdd 100644 --- a/pkg/binary/elf.go +++ b/pkg/binary/elf.go @@ -18,6 +18,7 @@ package binary import ( "bufio" + debugelf "debug/elf" "encoding/binary" "fmt" "os" @@ -55,7 +56,8 @@ func NewELFBinary(filePath string, opts *Options) (*ELFBinary, error) { } return &ELFBinary{ - Header: header, + Header: header, + Options: opts, }, nil } @@ -170,3 +172,27 @@ func (elf *ELFBinary) Arch() string { func (elf *ELFBinary) OS() string { return LINUX } + +// LinkMode returns the linking mode of the binary. +func (elf *ELFBinary) LinkMode() (LinkMode, error) { + file, err := os.Open(elf.Options.Path) + if err != nil { + return LinkModeUnknown, fmt.Errorf("open binary path: %w", err) + } + + elfFile, err := debugelf.NewFile(file) + if err != nil { + return LinkModeUnknown, fmt.Errorf("unable to parse elf: %w", err) + } + + for _, programHeader := range elfFile.Progs { + // If the elf program header refers to an interpreter, then the binary + // is not statically linked. See `file` implementation reference: + // https://github.com/file/file/blob/FILE5_36/src/readelf.c#L1581 + if programHeader.Type == debugelf.PT_INTERP { + return LinkModeDynamic, nil + } + } + + return LinkModeStatic, nil +} diff --git a/pkg/binary/mach-o.go b/pkg/binary/mach-o.go index 3f97afcd83c..8a6c7077a46 100644 --- a/pkg/binary/mach-o.go +++ b/pkg/binary/mach-o.go @@ -178,3 +178,8 @@ func (macho *MachOBinary) Arch() string { func (macho *MachOBinary) OS() string { return DARWIN } + +// LinkMode returns the linking mode of the binary. +func (macho *MachOBinary) LinkMode() (LinkMode, error) { + return LinkModeUnknown, nil +} diff --git a/pkg/binary/windows.go b/pkg/binary/windows.go index b3c7ce43ad7..f34b8afa830 100644 --- a/pkg/binary/windows.go +++ b/pkg/binary/windows.go @@ -198,3 +198,8 @@ func (pe *PEBinary) Arch() string { func (pe *PEBinary) OS() string { return WIN } + +// LinkMode returns the linking mode of the binary. +func (pe *PEBinary) LinkMode() (LinkMode, error) { + return LinkModeUnknown, nil +} diff --git a/pkg/release/binaries.go b/pkg/release/binaries.go index 5ffdae3789e..08f2972e49c 100644 --- a/pkg/release/binaries.go +++ b/pkg/release/binaries.go @@ -145,6 +145,15 @@ func (impl *defaultArtifactCheckerImpl) CheckVersionArch( binData.Path, binData.Arch, binData.Platform, bin.Arch(), bin.OS(), ) } + + linkMode, err := bin.LinkMode() + if err != nil { + logrus.Warnf("Unable to get linkmode from binary %s: %v", binData.Path, err) + } else if linkMode == binary.LinkModeDynamic { + // TODO: fail hard if not all binaries are static (or unknown) + // Ref: https://github.com/kubernetes/release/issues/2786 + logrus.Warnf("Binary is dynamically linked, which should be nothing we release: %s", binData.Path) + } } return nil }