1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/common/safe_browsing/mach_o_image_reader_mac.h"
8 #include <libkern/OSByteOrder.h>
9 #include <mach-o/fat.h>
10 #include <mach-o/loader.h>
12 #include <uuid/uuid.h>
14 #include "base/files/file_path.h"
15 #include "base/files/memory_mapped_file.h"
16 #include "base/path_service.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/stringprintf.h"
19 #include "chrome/common/chrome_paths.h"
20 #include "testing/gtest/include/gtest/gtest.h"
22 namespace safe_browsing
{
26 // <http://opensource.apple.com/source/xnu/xnu-2782.1.97/bsd/sys/codesign.h>.
29 CSMAGIC_CODEDIRECTORY
= 0xfade0c02,
30 CSMAGIC_EMBEDDED_SIGNATURE
= 0xfade0cc0,
32 CSSLOT_CODEDIRECTORY
= 0,
35 struct CodeSigningBlob
{
40 struct CodeSigningSuperBlob
{
44 CodeSigningBlob index
[];
47 struct CodeSigningDirectory
{
54 uint32_t nSpecialSlots
;
63 uint32_t scatterOffset
;
68 class MachOImageReaderTest
: public testing::Test
{
70 void OpenTestFile(const char* file_name
, base::MemoryMappedFile
* file
) {
71 base::FilePath test_data
;
72 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA
, &test_data
));
74 base::FilePath path
= test_data
.AppendASCII("safe_browsing")
75 .AppendASCII("mach_o")
76 .AppendASCII(file_name
);
78 ASSERT_TRUE(file
->Initialize(path
));
81 // Returns the identity of the signed code data.
82 void GetSigningIdentity(const std::vector
<uint8_t>& signature
,
83 std::string
* identity
) {
85 reinterpret_cast<const CodeSigningSuperBlob
*>(&signature
[0]);
86 EXPECT_EQ(CSMAGIC_EMBEDDED_SIGNATURE
, ntohl(super_blob
->magic
));
87 ASSERT_EQ(CSSLOT_CODEDIRECTORY
, ntohl(super_blob
->index
[0].type
));
88 size_t dir_offset
= ntohl(super_blob
->index
[0].offset
);
90 reinterpret_cast<const CodeSigningDirectory
*>(&signature
[dir_offset
]);
91 ASSERT_EQ(CSMAGIC_CODEDIRECTORY
, ntohl(directory
->magic
));
92 size_t ident_offset
= ntohl(directory
->identOffset
) + dir_offset
;
94 std::string(reinterpret_cast<const char*>(&signature
[ident_offset
]));
97 // Returns the hash of the code data itself. Note that this is not the
98 // CDHash, but is instead the hash in the CodeDirectory blob, which is
99 // over the contents of the signed data. This is visible as hash #0
100 // when using `codesign -d -vvvvvv`.
101 void GetCodeSignatureHash(const std::vector
<uint8_t>& signature
,
102 std::vector
<uint8_t>* hash
) {
104 reinterpret_cast<const CodeSigningSuperBlob
*>(&signature
[0]);
105 EXPECT_EQ(CSMAGIC_EMBEDDED_SIGNATURE
, ntohl(super_blob
->magic
));
106 ASSERT_EQ(CSSLOT_CODEDIRECTORY
, ntohl(super_blob
->index
[0].type
));
107 size_t dir_offset
= ntohl(super_blob
->index
[0].offset
);
109 reinterpret_cast<const CodeSigningDirectory
*>(&signature
[dir_offset
]);
110 ASSERT_EQ(CSMAGIC_CODEDIRECTORY
, ntohl(directory
->magic
));
111 size_t hash_offset
= ntohl(directory
->hashOffset
) + dir_offset
;
112 std::vector
<uint8_t> actual_hash(&signature
[hash_offset
],
113 &signature
[hash_offset
+ directory
->hashSize
]);
114 EXPECT_EQ(20u, actual_hash
.size());
118 void ExpectCodeSignatureHash(const std::vector
<uint8_t>& signature
,
119 const char* expected
) {
120 std::vector
<uint8_t> actual_hash
;
121 GetCodeSignatureHash(signature
, &actual_hash
);
123 std::vector
<uint8_t> expected_hash
;
124 ASSERT_TRUE(base::HexStringToBytes(expected
, &expected_hash
));
125 EXPECT_EQ(expected_hash
, actual_hash
);
129 TEST_F(MachOImageReaderTest
, Executable32
) {
130 base::MemoryMappedFile file
;
131 ASSERT_NO_FATAL_FAILURE(OpenTestFile("executable32", &file
));
132 MachOImageReader reader
;
133 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
135 EXPECT_FALSE(reader
.IsFat());
136 EXPECT_FALSE(reader
.Is64Bit());
137 EXPECT_TRUE(reader
.GetMachHeader());
138 EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE
), reader
.GetFileType());
139 EXPECT_EQ(15u, reader
.GetLoadCommands().size());
141 std::vector
<uint8_t> signature
;
142 EXPECT_FALSE(reader
.GetCodeSignatureInfo(&signature
));
143 EXPECT_TRUE(signature
.empty());
145 // Test an arbitrary load command.
146 auto commands
= reader
.GetLoadCommands();
147 ASSERT_EQ(15u, commands
.size());
148 auto command
= commands
[11];
149 ASSERT_EQ(static_cast<uint32_t>(LC_LOAD_DYLIB
), command
.cmd());
150 auto actual
= command
.as_command
<dylib_command
>();
151 EXPECT_EQ(2u, actual
->dylib
.timestamp
);
152 EXPECT_EQ(0x4ad0101u
, actual
->dylib
.current_version
);
153 EXPECT_EQ(0x10000u
, actual
->dylib
.compatibility_version
);
156 TEST_F(MachOImageReaderTest
, Executable64
) {
157 base::MemoryMappedFile file
;
158 ASSERT_NO_FATAL_FAILURE(OpenTestFile("executable64", &file
));
159 MachOImageReader reader
;
160 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
162 EXPECT_FALSE(reader
.IsFat());
163 EXPECT_TRUE(reader
.Is64Bit());
164 EXPECT_TRUE(reader
.GetMachHeader());
165 EXPECT_TRUE(reader
.GetMachHeader64());
166 EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE
), reader
.GetFileType());
167 EXPECT_EQ(15u, reader
.GetLoadCommands().size());
169 std::vector
<uint8_t> signature
;
170 EXPECT_FALSE(reader
.GetCodeSignatureInfo(&signature
));
171 EXPECT_TRUE(signature
.empty());
174 TEST_F(MachOImageReaderTest
, ExecutableFat
) {
175 base::MemoryMappedFile file
;
176 ASSERT_NO_FATAL_FAILURE(OpenTestFile("executablefat", &file
));
177 MachOImageReader reader
;
178 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
180 EXPECT_TRUE(reader
.IsFat());
181 auto images
= reader
.GetFatImages();
182 ASSERT_EQ(2u, images
.size());
184 // Note: this image is crafted to have 32-bit first.
186 EXPECT_FALSE(images
[0]->IsFat());
187 EXPECT_FALSE(images
[0]->Is64Bit());
188 EXPECT_TRUE(images
[0]->GetMachHeader());
189 EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE
), images
[0]->GetFileType());
191 std::vector
<uint8_t> signature
;
192 EXPECT_FALSE(images
[0]->GetCodeSignatureInfo(&signature
));
193 EXPECT_TRUE(signature
.empty());
197 EXPECT_FALSE(images
[1]->IsFat());
198 EXPECT_TRUE(images
[1]->Is64Bit());
199 EXPECT_TRUE(images
[1]->GetMachHeader());
200 EXPECT_TRUE(images
[1]->GetMachHeader64());
201 EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE
), images
[1]->GetFileType());
203 std::vector
<uint8_t> signature
;
204 EXPECT_FALSE(images
[1]->GetCodeSignatureInfo(&signature
));
205 EXPECT_TRUE(signature
.empty());
207 // Test an arbitrary load command.
208 auto commands
= images
[1]->GetLoadCommands();
209 ASSERT_EQ(15u, commands
.size());
210 auto command
= commands
[1];
211 ASSERT_EQ(static_cast<uint32_t>(LC_SEGMENT_64
), command
.cmd());
212 auto actual
= command
.as_command
<segment_command_64
>();
213 EXPECT_EQ("__TEXT", std::string(actual
->segname
));
214 EXPECT_EQ(0u, actual
->fileoff
);
215 EXPECT_EQ(4096u, actual
->filesize
);
216 EXPECT_EQ(0x7, actual
->maxprot
);
217 EXPECT_EQ(0x5, actual
->initprot
);
218 EXPECT_EQ(3u, actual
->nsects
);
219 EXPECT_EQ(0u, actual
->flags
);
223 TEST_F(MachOImageReaderTest
, ExecutablePPC
) {
224 base::MemoryMappedFile file
;
225 ASSERT_NO_FATAL_FAILURE(OpenTestFile("executableppc", &file
));
226 MachOImageReader reader
;
227 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
229 EXPECT_FALSE(reader
.IsFat());
230 EXPECT_FALSE(reader
.Is64Bit());
231 EXPECT_TRUE(reader
.GetMachHeader());
232 EXPECT_EQ(OSSwapInt32(MH_EXECUTE
), reader
.GetFileType());
233 EXPECT_EQ(10u, reader
.GetLoadCommands().size());
235 std::vector
<uint8_t> signature
;
236 EXPECT_FALSE(reader
.GetCodeSignatureInfo(&signature
));
237 EXPECT_TRUE(signature
.empty());
240 TEST_F(MachOImageReaderTest
, Dylib32
) {
241 base::MemoryMappedFile file
;
242 ASSERT_NO_FATAL_FAILURE(OpenTestFile("lib32.dylib", &file
));
243 MachOImageReader reader
;
244 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
246 EXPECT_FALSE(reader
.IsFat());
247 EXPECT_FALSE(reader
.Is64Bit());
248 EXPECT_TRUE(reader
.GetMachHeader());
249 EXPECT_EQ(static_cast<uint32_t>(MH_DYLIB
), reader
.GetFileType());
250 EXPECT_EQ(13u, reader
.GetLoadCommands().size());
252 std::vector
<uint8_t> signature
;
253 EXPECT_FALSE(reader
.GetCodeSignatureInfo(&signature
));
254 EXPECT_TRUE(signature
.empty());
257 TEST_F(MachOImageReaderTest
, Dylib64
) {
258 base::MemoryMappedFile file
;
259 ASSERT_NO_FATAL_FAILURE(OpenTestFile("lib64.dylib", &file
));
260 MachOImageReader reader
;
261 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
263 EXPECT_FALSE(reader
.IsFat());
264 EXPECT_TRUE(reader
.Is64Bit());
265 EXPECT_TRUE(reader
.GetMachHeader());
266 EXPECT_TRUE(reader
.GetMachHeader64());
267 EXPECT_EQ(static_cast<uint32_t>(MH_DYLIB
), reader
.GetFileType());
268 EXPECT_EQ(13u, reader
.GetLoadCommands().size());
270 std::vector
<uint8_t> signature
;
271 EXPECT_FALSE(reader
.GetCodeSignatureInfo(&signature
));
272 EXPECT_TRUE(signature
.empty());
274 // Test an arbitrary load command.
275 auto commands
= reader
.GetLoadCommands();
276 ASSERT_EQ(13u, commands
.size());
277 auto command
= commands
[6];
278 ASSERT_EQ(static_cast<uint32_t>(LC_UUID
), command
.cmd());
279 uuid_t expected
= {0xB6, 0xB5, 0x12, 0xD7,
283 0x87, 0x46, 0x36, 0x76, 0x87, 0x47};
284 EXPECT_EQ(0, uuid_compare(expected
,
285 command
.as_command
<uuid_command
>()->uuid
));
288 TEST_F(MachOImageReaderTest
, DylibFat
) {
289 base::MemoryMappedFile file
;
290 ASSERT_NO_FATAL_FAILURE(OpenTestFile("libfat.dylib", &file
));
291 MachOImageReader reader
;
292 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
294 EXPECT_TRUE(reader
.IsFat());
295 auto images
= reader
.GetFatImages();
296 ASSERT_EQ(2u, images
.size());
298 // Note: this image is crafted to have 64-bit first.
300 EXPECT_FALSE(images
[0]->IsFat());
301 EXPECT_TRUE(images
[0]->Is64Bit());
302 EXPECT_TRUE(images
[0]->GetMachHeader());
303 EXPECT_TRUE(images
[0]->GetMachHeader64());
304 EXPECT_EQ(static_cast<uint32_t>(MH_DYLIB
), images
[0]->GetFileType());
306 std::vector
<uint8_t> signature
;
307 EXPECT_FALSE(images
[0]->GetCodeSignatureInfo(&signature
));
308 EXPECT_TRUE(signature
.empty());
312 EXPECT_FALSE(images
[1]->IsFat());
313 EXPECT_FALSE(images
[1]->Is64Bit());
314 EXPECT_TRUE(images
[1]->GetMachHeader());
315 EXPECT_EQ(static_cast<uint32_t>(MH_DYLIB
), images
[1]->GetFileType());
317 std::vector
<uint8_t> signature
;
318 EXPECT_FALSE(images
[1]->GetCodeSignatureInfo(&signature
));
319 EXPECT_TRUE(signature
.empty());
323 TEST_F(MachOImageReaderTest
, SignedExecutable32
) {
324 base::MemoryMappedFile file
;
325 ASSERT_NO_FATAL_FAILURE(OpenTestFile("signedexecutable32", &file
));
326 MachOImageReader reader
;
327 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
329 EXPECT_FALSE(reader
.IsFat());
330 EXPECT_FALSE(reader
.Is64Bit());
331 EXPECT_TRUE(reader
.GetMachHeader());
332 EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE
), reader
.GetFileType());
333 EXPECT_EQ(16u, reader
.GetLoadCommands().size());
335 std::vector
<uint8_t> signature
;
336 EXPECT_TRUE(reader
.GetCodeSignatureInfo(&signature
));
337 EXPECT_EQ(9344u, signature
.size());
339 std::string identity
;
340 GetSigningIdentity(signature
, &identity
);
341 EXPECT_EQ("signedexecutable32", identity
);
343 ExpectCodeSignatureHash(signature
,
344 "11fb88eb63c10dfc3d24a2545ea2a9c50c2921b5");
347 TEST_F(MachOImageReaderTest
, SignedExecutableFat
) {
348 base::MemoryMappedFile file
;
349 ASSERT_NO_FATAL_FAILURE(OpenTestFile("signedexecutablefat", &file
));
350 MachOImageReader reader
;
351 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
353 EXPECT_TRUE(reader
.IsFat());
354 auto images
= reader
.GetFatImages();
355 ASSERT_EQ(2u, images
.size());
357 // Note: this image is crafted to have 32-bit first.
359 EXPECT_FALSE(images
[0]->IsFat());
360 EXPECT_FALSE(images
[0]->Is64Bit());
361 EXPECT_TRUE(images
[0]->GetMachHeader());
362 EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE
), images
[0]->GetFileType());
364 std::vector
<uint8_t> signature
;
365 EXPECT_TRUE(images
[0]->GetCodeSignatureInfo(&signature
));
366 EXPECT_EQ(9344u, signature
.size());
368 std::string identity
;
369 GetSigningIdentity(signature
, &identity
);
370 EXPECT_EQ("signedexecutablefat", identity
);
372 ExpectCodeSignatureHash(signature
,
373 "11fb88eb63c10dfc3d24a2545ea2a9c50c2921b5");
377 EXPECT_FALSE(images
[1]->IsFat());
378 EXPECT_TRUE(images
[1]->Is64Bit());
379 EXPECT_TRUE(images
[1]->GetMachHeader());
380 EXPECT_TRUE(images
[1]->GetMachHeader64());
381 EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE
), images
[1]->GetFileType());
383 std::vector
<uint8_t> signature
;
384 EXPECT_TRUE(images
[1]->GetCodeSignatureInfo(&signature
));
385 EXPECT_EQ(9344u, signature
.size());
387 std::string identity
;
388 GetSigningIdentity(signature
, &identity
);
389 EXPECT_EQ("signedexecutablefat", identity
);
391 ExpectCodeSignatureHash(signature
,
392 "750a57326ba85857371094900475defd837f5e14");
396 TEST_F(MachOImageReaderTest
, SignedDylib64
) {
397 base::MemoryMappedFile file
;
398 ASSERT_NO_FATAL_FAILURE(OpenTestFile("libsigned64.dylib", &file
));
399 MachOImageReader reader
;
400 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
402 EXPECT_FALSE(reader
.IsFat());
403 EXPECT_TRUE(reader
.Is64Bit());
404 EXPECT_TRUE(reader
.GetMachHeader());
405 EXPECT_TRUE(reader
.GetMachHeader64());
406 EXPECT_EQ(static_cast<uint32_t>(MH_DYLIB
), reader
.GetFileType());
407 EXPECT_EQ(14u, reader
.GetLoadCommands().size());
409 std::vector
<uint8_t> signature
;
410 EXPECT_TRUE(reader
.GetCodeSignatureInfo(&signature
));
411 EXPECT_EQ(9328u, signature
.size());
413 std::string identity
;
414 GetSigningIdentity(signature
, &identity
);
415 EXPECT_EQ("libsigned64", identity
);
417 ExpectCodeSignatureHash(signature
,
418 "8b1c79b60bb53a7f17b5618d5feb10dc8b88d806");
421 TEST_F(MachOImageReaderTest
, NotMachO
) {
422 base::MemoryMappedFile file
;
423 ASSERT_NO_FATAL_FAILURE(OpenTestFile("src.c", &file
));
424 MachOImageReader reader
;
425 EXPECT_FALSE(reader
.Initialize(file
.data(), file
.length()));
428 TEST_F(MachOImageReaderTest
, IsMachOMagicValue
) {
429 static const uint32_t kMagics
[] = { MH_MAGIC
, MH_MAGIC
, FAT_MAGIC
};
430 for (uint32_t magic
: kMagics
) {
431 SCOPED_TRACE(base::StringPrintf("0x%x", magic
));
432 EXPECT_TRUE(MachOImageReader::IsMachOMagicValue(magic
));
433 EXPECT_TRUE(MachOImageReader::IsMachOMagicValue(OSSwapInt32(magic
)));
437 // https://crbug.com/524044
438 TEST_F(MachOImageReaderTest
, CmdsizeSmallerThanLoadCommand
) {
439 #pragma pack(push, 1)
441 mach_header_64 header
;
442 segment_command_64 page_zero
;
443 load_command small_sized
;
444 segment_command_64 fake_code
;
448 TestImage test_image
= {};
450 test_image
.header
.magic
= MH_MAGIC_64
;
451 test_image
.header
.cputype
= CPU_TYPE_X86_64
;
452 test_image
.header
.filetype
= MH_EXECUTE
;
453 test_image
.header
.ncmds
= 3;
454 test_image
.header
.sizeofcmds
= sizeof(test_image
) - sizeof(test_image
.header
);
456 test_image
.page_zero
.cmd
= LC_SEGMENT
;
457 test_image
.page_zero
.cmdsize
= sizeof(test_image
.page_zero
);
458 strcpy(test_image
.page_zero
.segname
, SEG_PAGEZERO
);
459 test_image
.page_zero
.vmsize
= PAGE_SIZE
;
461 test_image
.small_sized
.cmd
= LC_SYMSEG
;
462 test_image
.small_sized
.cmdsize
= sizeof(test_image
.small_sized
) - 3;
464 test_image
.fake_code
.cmd
= LC_SEGMENT
;
465 test_image
.fake_code
.cmdsize
= sizeof(test_image
.fake_code
);
466 strcpy(test_image
.fake_code
.segname
, SEG_TEXT
);
468 MachOImageReader reader
;
469 EXPECT_TRUE(reader
.Initialize(reinterpret_cast<const uint8_t*>(&test_image
),
470 sizeof(test_image
)));
472 EXPECT_FALSE(reader
.IsFat());
473 EXPECT_TRUE(reader
.Is64Bit());
475 const auto& load_commands
= reader
.GetLoadCommands();
476 EXPECT_EQ(3u, load_commands
.size());
478 EXPECT_EQ(static_cast<uint32_t>(LC_SEGMENT
), load_commands
[0].cmd());
479 EXPECT_EQ(static_cast<uint32_t>(LC_SYMSEG
), load_commands
[1].cmd());
480 EXPECT_EQ(sizeof(load_command
) - 3, load_commands
[1].cmdsize());
481 EXPECT_EQ(static_cast<uint32_t>(LC_SEGMENT
), load_commands
[2].cmd());
485 } // namespace safe_browsing