// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/common/safe_browsing/mach_o_image_reader_mac.h" #include #include #include #include #include #include "base/files/file_path.h" #include "base/files/memory_mapped_file.h" #include "base/path_service.h" #include "base/strings/string_number_conversions.h" #include "chrome/common/chrome_paths.h" #include "testing/gtest/include/gtest/gtest.h" namespace safe_browsing { namespace { // Definitions from // . enum { CSMAGIC_CODEDIRECTORY = 0xfade0c02, CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, CSSLOT_CODEDIRECTORY = 0, }; struct CodeSigningBlob { uint32_t type; uint32_t offset; }; struct CodeSigningSuperBlob { uint32_t magic; uint32_t length; uint32_t count; CodeSigningBlob index[]; }; struct CodeSigningDirectory { uint32_t magic; uint32_t length; uint32_t version; uint32_t flags; uint32_t hashOffset; uint32_t identOffset; uint32_t nSpecialSlots; uint32_t nCodeSlots; uint32_t codeLimit; uint8_t hashSize; uint8_t hashType; uint8_t spare1; uint8_t pageSize; uint32_t spare2; // Version 0x20100. uint32_t scatterOffset; // Version 0x20200. uint32_t teamOffset; }; class MachOImageReaderTest : public testing::Test { protected: void OpenTestFile(const char* file_name, base::MemoryMappedFile* file) { base::FilePath test_data; ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data)); base::FilePath path = test_data.AppendASCII("safe_browsing") .AppendASCII("mach_o") .AppendASCII(file_name); ASSERT_TRUE(file->Initialize(path)); } // Returns the identity of the signed code data. void GetSigningIdentity(const std::vector& signature, std::string* identity) { auto super_blob = reinterpret_cast(&signature[0]); EXPECT_EQ(CSMAGIC_EMBEDDED_SIGNATURE, ntohl(super_blob->magic)); ASSERT_EQ(CSSLOT_CODEDIRECTORY, ntohl(super_blob->index[0].type)); size_t dir_offset = ntohl(super_blob->index[0].offset); auto directory = reinterpret_cast(&signature[dir_offset]); ASSERT_EQ(CSMAGIC_CODEDIRECTORY, ntohl(directory->magic)); size_t ident_offset = ntohl(directory->identOffset) + dir_offset; *identity = std::string(reinterpret_cast(&signature[ident_offset])); } // Returns the hash of the code data itself. Note that this is not the // CDHash, but is instead the hash in the CodeDirectory blob, which is // over the contents of the signed data. This is visible as hash #0 // when using `codesign -d -vvvvvv`. void GetCodeSignatureHash(const std::vector& signature, std::vector* hash) { auto super_blob = reinterpret_cast(&signature[0]); EXPECT_EQ(CSMAGIC_EMBEDDED_SIGNATURE, ntohl(super_blob->magic)); ASSERT_EQ(CSSLOT_CODEDIRECTORY, ntohl(super_blob->index[0].type)); size_t dir_offset = ntohl(super_blob->index[0].offset); auto directory = reinterpret_cast(&signature[dir_offset]); ASSERT_EQ(CSMAGIC_CODEDIRECTORY, ntohl(directory->magic)); size_t hash_offset = ntohl(directory->hashOffset) + dir_offset; std::vector actual_hash(&signature[hash_offset], &signature[hash_offset + directory->hashSize]); EXPECT_EQ(20u, actual_hash.size()); *hash = actual_hash; } void ExpectCodeSignatureHash(const std::vector& signature, const char* expected) { std::vector actual_hash; GetCodeSignatureHash(signature, &actual_hash); std::vector expected_hash; ASSERT_TRUE(base::HexStringToBytes(expected, &expected_hash)); EXPECT_EQ(expected_hash, actual_hash); } }; TEST_F(MachOImageReaderTest, Executable32) { base::MemoryMappedFile file; ASSERT_NO_FATAL_FAILURE(OpenTestFile("executable32", &file)); MachOImageReader reader; ASSERT_TRUE(reader.Initialize(file.data(), file.length())); EXPECT_FALSE(reader.IsFat()); EXPECT_FALSE(reader.Is64Bit()); EXPECT_TRUE(reader.GetMachHeader()); EXPECT_EQ(static_cast(MH_EXECUTE), reader.GetFileType()); EXPECT_EQ(15u, reader.GetLoadCommands().size()); std::vector signature; EXPECT_FALSE(reader.GetCodeSignatureInfo(&signature)); EXPECT_TRUE(signature.empty()); // Test an arbitrary load command. auto commands = reader.GetLoadCommands(); ASSERT_EQ(15u, commands.size()); auto command = commands[11]; ASSERT_EQ(static_cast(LC_LOAD_DYLIB), command.cmd()); auto actual = command.as_command(); EXPECT_EQ(2u, actual->dylib.timestamp); EXPECT_EQ(0x4ad0101u, actual->dylib.current_version); EXPECT_EQ(0x10000u, actual->dylib.compatibility_version); } TEST_F(MachOImageReaderTest, Executable64) { base::MemoryMappedFile file; ASSERT_NO_FATAL_FAILURE(OpenTestFile("executable64", &file)); MachOImageReader reader; ASSERT_TRUE(reader.Initialize(file.data(), file.length())); EXPECT_FALSE(reader.IsFat()); EXPECT_TRUE(reader.Is64Bit()); EXPECT_TRUE(reader.GetMachHeader()); EXPECT_TRUE(reader.GetMachHeader64()); EXPECT_EQ(static_cast(MH_EXECUTE), reader.GetFileType()); EXPECT_EQ(15u, reader.GetLoadCommands().size()); std::vector signature; EXPECT_FALSE(reader.GetCodeSignatureInfo(&signature)); EXPECT_TRUE(signature.empty()); } TEST_F(MachOImageReaderTest, ExecutableFat) { base::MemoryMappedFile file; ASSERT_NO_FATAL_FAILURE(OpenTestFile("executablefat", &file)); MachOImageReader reader; ASSERT_TRUE(reader.Initialize(file.data(), file.length())); EXPECT_TRUE(reader.IsFat()); auto images = reader.GetFatImages(); ASSERT_EQ(2u, images.size()); // Note: this image is crafted to have 32-bit first. { EXPECT_FALSE(images[0]->IsFat()); EXPECT_FALSE(images[0]->Is64Bit()); EXPECT_TRUE(images[0]->GetMachHeader()); EXPECT_EQ(static_cast(MH_EXECUTE), images[0]->GetFileType()); std::vector signature; EXPECT_FALSE(images[0]->GetCodeSignatureInfo(&signature)); EXPECT_TRUE(signature.empty()); } { EXPECT_FALSE(images[1]->IsFat()); EXPECT_TRUE(images[1]->Is64Bit()); EXPECT_TRUE(images[1]->GetMachHeader()); EXPECT_TRUE(images[1]->GetMachHeader64()); EXPECT_EQ(static_cast(MH_EXECUTE), images[1]->GetFileType()); std::vector signature; EXPECT_FALSE(images[1]->GetCodeSignatureInfo(&signature)); EXPECT_TRUE(signature.empty()); // Test an arbitrary load command. auto commands = images[1]->GetLoadCommands(); ASSERT_EQ(15u, commands.size()); auto command = commands[1]; ASSERT_EQ(static_cast(LC_SEGMENT_64), command.cmd()); auto actual = command.as_command(); EXPECT_EQ("__TEXT", std::string(actual->segname)); EXPECT_EQ(0u, actual->fileoff); EXPECT_EQ(4096u, actual->filesize); EXPECT_EQ(0x7, actual->maxprot); EXPECT_EQ(0x5, actual->initprot); EXPECT_EQ(3u, actual->nsects); EXPECT_EQ(0u, actual->flags); } } TEST_F(MachOImageReaderTest, ExecutablePPC) { base::MemoryMappedFile file; ASSERT_NO_FATAL_FAILURE(OpenTestFile("executableppc", &file)); MachOImageReader reader; ASSERT_TRUE(reader.Initialize(file.data(), file.length())); EXPECT_FALSE(reader.IsFat()); EXPECT_FALSE(reader.Is64Bit()); EXPECT_TRUE(reader.GetMachHeader()); EXPECT_EQ(OSSwapInt32(MH_EXECUTE), reader.GetFileType()); EXPECT_EQ(10u, reader.GetLoadCommands().size()); std::vector signature; EXPECT_FALSE(reader.GetCodeSignatureInfo(&signature)); EXPECT_TRUE(signature.empty()); } TEST_F(MachOImageReaderTest, Dylib32) { base::MemoryMappedFile file; ASSERT_NO_FATAL_FAILURE(OpenTestFile("lib32.dylib", &file)); MachOImageReader reader; ASSERT_TRUE(reader.Initialize(file.data(), file.length())); EXPECT_FALSE(reader.IsFat()); EXPECT_FALSE(reader.Is64Bit()); EXPECT_TRUE(reader.GetMachHeader()); EXPECT_EQ(static_cast(MH_DYLIB), reader.GetFileType()); EXPECT_EQ(13u, reader.GetLoadCommands().size()); std::vector signature; EXPECT_FALSE(reader.GetCodeSignatureInfo(&signature)); EXPECT_TRUE(signature.empty()); } TEST_F(MachOImageReaderTest, Dylib64) { base::MemoryMappedFile file; ASSERT_NO_FATAL_FAILURE(OpenTestFile("lib64.dylib", &file)); MachOImageReader reader; ASSERT_TRUE(reader.Initialize(file.data(), file.length())); EXPECT_FALSE(reader.IsFat()); EXPECT_TRUE(reader.Is64Bit()); EXPECT_TRUE(reader.GetMachHeader()); EXPECT_TRUE(reader.GetMachHeader64()); EXPECT_EQ(static_cast(MH_DYLIB), reader.GetFileType()); EXPECT_EQ(13u, reader.GetLoadCommands().size()); std::vector signature; EXPECT_FALSE(reader.GetCodeSignatureInfo(&signature)); EXPECT_TRUE(signature.empty()); // Test an arbitrary load command. auto commands = reader.GetLoadCommands(); ASSERT_EQ(13u, commands.size()); auto command = commands[6]; ASSERT_EQ(static_cast(LC_UUID), command.cmd()); uuid_t expected = {0xB6, 0xB5, 0x12, 0xD7, 0x64, 0xE9, 0x3F, 0x7A, 0xAB, 0x4A, 0x87, 0x46, 0x36, 0x76, 0x87, 0x47}; EXPECT_EQ(0, uuid_compare(expected, command.as_command()->uuid)); } TEST_F(MachOImageReaderTest, DylibFat) { base::MemoryMappedFile file; ASSERT_NO_FATAL_FAILURE(OpenTestFile("libfat.dylib", &file)); MachOImageReader reader; ASSERT_TRUE(reader.Initialize(file.data(), file.length())); EXPECT_TRUE(reader.IsFat()); auto images = reader.GetFatImages(); ASSERT_EQ(2u, images.size()); // Note: this image is crafted to have 64-bit first. { EXPECT_FALSE(images[0]->IsFat()); EXPECT_TRUE(images[0]->Is64Bit()); EXPECT_TRUE(images[0]->GetMachHeader()); EXPECT_TRUE(images[0]->GetMachHeader64()); EXPECT_EQ(static_cast(MH_DYLIB), images[0]->GetFileType()); std::vector signature; EXPECT_FALSE(images[0]->GetCodeSignatureInfo(&signature)); EXPECT_TRUE(signature.empty()); } { EXPECT_FALSE(images[1]->IsFat()); EXPECT_FALSE(images[1]->Is64Bit()); EXPECT_TRUE(images[1]->GetMachHeader()); EXPECT_EQ(static_cast(MH_DYLIB), images[1]->GetFileType()); std::vector signature; EXPECT_FALSE(images[1]->GetCodeSignatureInfo(&signature)); EXPECT_TRUE(signature.empty()); } } TEST_F(MachOImageReaderTest, SignedExecutable32) { base::MemoryMappedFile file; ASSERT_NO_FATAL_FAILURE(OpenTestFile("signedexecutable32", &file)); MachOImageReader reader; ASSERT_TRUE(reader.Initialize(file.data(), file.length())); EXPECT_FALSE(reader.IsFat()); EXPECT_FALSE(reader.Is64Bit()); EXPECT_TRUE(reader.GetMachHeader()); EXPECT_EQ(static_cast(MH_EXECUTE), reader.GetFileType()); EXPECT_EQ(16u, reader.GetLoadCommands().size()); std::vector signature; EXPECT_TRUE(reader.GetCodeSignatureInfo(&signature)); EXPECT_EQ(9344u, signature.size()); std::string identity; GetSigningIdentity(signature, &identity); EXPECT_EQ("signedexecutable32", identity); ExpectCodeSignatureHash(signature, "11fb88eb63c10dfc3d24a2545ea2a9c50c2921b5"); } TEST_F(MachOImageReaderTest, SignedExecutableFat) { base::MemoryMappedFile file; ASSERT_NO_FATAL_FAILURE(OpenTestFile("signedexecutablefat", &file)); MachOImageReader reader; ASSERT_TRUE(reader.Initialize(file.data(), file.length())); EXPECT_TRUE(reader.IsFat()); auto images = reader.GetFatImages(); ASSERT_EQ(2u, images.size()); // Note: this image is crafted to have 32-bit first. { EXPECT_FALSE(images[0]->IsFat()); EXPECT_FALSE(images[0]->Is64Bit()); EXPECT_TRUE(images[0]->GetMachHeader()); EXPECT_EQ(static_cast(MH_EXECUTE), images[0]->GetFileType()); std::vector signature; EXPECT_TRUE(images[0]->GetCodeSignatureInfo(&signature)); EXPECT_EQ(9344u, signature.size()); std::string identity; GetSigningIdentity(signature, &identity); EXPECT_EQ("signedexecutablefat", identity); ExpectCodeSignatureHash(signature, "11fb88eb63c10dfc3d24a2545ea2a9c50c2921b5"); } { EXPECT_FALSE(images[1]->IsFat()); EXPECT_TRUE(images[1]->Is64Bit()); EXPECT_TRUE(images[1]->GetMachHeader()); EXPECT_TRUE(images[1]->GetMachHeader64()); EXPECT_EQ(static_cast(MH_EXECUTE), images[1]->GetFileType()); std::vector signature; EXPECT_TRUE(images[1]->GetCodeSignatureInfo(&signature)); EXPECT_EQ(9344u, signature.size()); std::string identity; GetSigningIdentity(signature, &identity); EXPECT_EQ("signedexecutablefat", identity); ExpectCodeSignatureHash(signature, "750a57326ba85857371094900475defd837f5e14"); } } TEST_F(MachOImageReaderTest, SignedDylib64) { base::MemoryMappedFile file; ASSERT_NO_FATAL_FAILURE(OpenTestFile("libsigned64.dylib", &file)); MachOImageReader reader; ASSERT_TRUE(reader.Initialize(file.data(), file.length())); EXPECT_FALSE(reader.IsFat()); EXPECT_TRUE(reader.Is64Bit()); EXPECT_TRUE(reader.GetMachHeader()); EXPECT_TRUE(reader.GetMachHeader64()); EXPECT_EQ(static_cast(MH_DYLIB), reader.GetFileType()); EXPECT_EQ(14u, reader.GetLoadCommands().size()); std::vector signature; EXPECT_TRUE(reader.GetCodeSignatureInfo(&signature)); EXPECT_EQ(9328u, signature.size()); std::string identity; GetSigningIdentity(signature, &identity); EXPECT_EQ("libsigned64", identity); ExpectCodeSignatureHash(signature, "8b1c79b60bb53a7f17b5618d5feb10dc8b88d806"); } TEST_F(MachOImageReaderTest, NotMachO) { base::MemoryMappedFile file; ASSERT_NO_FATAL_FAILURE(OpenTestFile("src.c", &file)); MachOImageReader reader; EXPECT_FALSE(reader.Initialize(file.data(), file.length())); } } // namespace } // namespace safe_browsing