diff --git a/syft/file/cataloger/executable/macho.go b/syft/file/cataloger/executable/macho.go index 8493c04a066..7cd80f7e588 100644 --- a/syft/file/cataloger/executable/macho.go +++ b/syft/file/cataloger/executable/macho.go @@ -3,6 +3,7 @@ package executable import ( "debug/macho" + "github.com/anchore/syft/internal" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/internal/unionreader" ) @@ -19,20 +20,38 @@ const ( func findMachoFeatures(data *file.Executable, reader unionreader.UnionReader) error { // TODO: support security features - // TODO: support multi-architecture binaries - f, err := macho.NewFile(reader) + // a universal binary may have multiple architectures, so we need to check each one + readers, err := unionreader.GetReaders(reader) if err != nil { return err } - libs, err := f.ImportedLibraries() - if err != nil { - return err + var libs []string + for _, r := range readers { + f, err := macho.NewFile(r) + if err != nil { + return err + } + + rLibs, err := f.ImportedLibraries() + if err != nil { + return err + } + libs = append(libs, rLibs...) + + // TODO handle only some having entrypoints/exports? If that is even practical + // only check for entrypoint if we don't already have one + if !data.HasEntrypoint { + data.HasEntrypoint = machoHasEntrypoint(f) + } + // only check for exports if we don't already have them + if !data.HasExports { + data.HasExports = machoHasExports(f) + } } - data.ImportedLibraries = libs - data.HasEntrypoint = machoHasEntrypoint(f) - data.HasExports = machoHasExports(f) + // de-duplicate libraries + data.ImportedLibraries = internal.NewSet(libs...).ToSlice() return nil } diff --git a/syft/file/cataloger/executable/macho_test.go b/syft/file/cataloger/executable/macho_test.go index 1d4e7ab7af5..ed881667186 100644 --- a/syft/file/cataloger/executable/macho_test.go +++ b/syft/file/cataloger/executable/macho_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/internal/unionreader" ) @@ -83,3 +84,39 @@ func Test_machoHasExports(t *testing.T) { }) } } + +func Test_machoUniversal(t *testing.T) { + readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader { + t.Helper() + f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture)) + require.NoError(t, err) + return f + } + + tests := []struct { + name string + fixture string + want file.Executable + }{ + { + name: "universal lib", + fixture: "bin/libhello_universal.dylib", + want: file.Executable{HasExports: true, HasEntrypoint: false}, + }, + { + name: "universal application", + fixture: "bin/hello_mac_universal", + want: file.Executable{HasExports: false, HasEntrypoint: true}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var data file.Executable + err := findMachoFeatures(&data, readerForFixture(t, tt.fixture)) + require.NoError(t, err) + + assert.Equal(t, tt.want.HasEntrypoint, data.HasEntrypoint) + assert.Equal(t, tt.want.HasExports, data.HasExports) + }) + } +} diff --git a/syft/file/cataloger/executable/test-fixtures/shared-info/project/hello/Makefile b/syft/file/cataloger/executable/test-fixtures/shared-info/project/hello/Makefile index 6a0bbbf4e24..578d7a95fe5 100644 --- a/syft/file/cataloger/executable/test-fixtures/shared-info/project/hello/Makefile +++ b/syft/file/cataloger/executable/test-fixtures/shared-info/project/hello/Makefile @@ -2,13 +2,13 @@ BIN=../../bin -all: $(BIN)/hello_linux $(BIN)/hello.exe $(BIN)/hello_mac +all: $(BIN)/hello_linux $(BIN)/hello.exe $(BIN)/hello_mac $(BIN)/hello_mac_universal linux: $(BIN)/libhello.so windows: $(BIN)/libhello.dll -mac: $(BIN)/libhello.dylib +mac: $(BIN)/libhello.dylib $(BIN)/hello_mac_universal $(BIN)/hello_linux: gcc hello.c -o $(BIN)/hello_linux @@ -19,5 +19,8 @@ $(BIN)/hello.exe: $(BIN)/hello_mac: o64-clang hello.c -o $(BIN)/hello_mac +$(BIN)/hello_mac_universal: + o64-clang -arch arm64 -arch x86_64 hello.c -o $(BIN)/hello_mac_universal + clean: - rm -f $(BIN)/hello_linux $(BIN)/hello.exe $(BIN)/hello_mac + rm -f $(BIN)/hello_linux $(BIN)/hello.exe $(BIN)/hello_mac $(BIN)/hello_mac_universal diff --git a/syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/Makefile b/syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/Makefile index e9b3860bb7b..af1fb12d94d 100644 --- a/syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/Makefile +++ b/syft/file/cataloger/executable/test-fixtures/shared-info/project/libhello/Makefile @@ -2,13 +2,13 @@ BIN=../../bin -all: $(BIN)/libhello.so $(BIN)/libhello.dll $(BIN)/libhello.dylib +all: $(BIN)/libhello.so $(BIN)/libhello.dll $(BIN)/libhello.dylib $(BIN)/libhello_universal.dylib linux: $(BIN)/libhello.so windows: $(BIN)/libhello.dll -mac: $(BIN)/libhello.dylib +mac: $(BIN)/libhello.dylib $(BIN)/libhello_universal.dylib $(BIN)/libhello.so: gcc -shared -fPIC -o $(BIN)/libhello.so hello.c @@ -19,5 +19,8 @@ $(BIN)/libhello.dll: $(BIN)/libhello.dylib: o64-clang -dynamiclib -o $(BIN)/libhello.dylib hello.c +$(BIN)/libhello_universal.dylib: + o64-clang -dynamiclib -arch arm64 -arch x86_64 hello.c -o $(BIN)/libhello_universal.dylib + clean: - rm -f $(BIN)/libhello.so $(BIN)/hello.dll $(BIN)/libhello.dylib $(BIN)/libhello.a + rm -f $(BIN)/libhello.so $(BIN)/hello.dll $(BIN)/libhello.dylib $(BIN)/libhello.a $(BIN)/libhello_universal.dylib