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/loader.h>
11 #include <uuid/uuid.h>
13 #include "base/files/file_path.h"
14 #include "base/files/memory_mapped_file.h"
15 #include "base/path_service.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "chrome/common/chrome_paths.h"
18 #include "testing/gtest/include/gtest/gtest.h"
20 namespace safe_browsing
{
24 // <http://opensource.apple.com/source/xnu/xnu-2782.1.97/bsd/sys/codesign.h>.
27 CSMAGIC_CODEDIRECTORY
= 0xfade0c02,
28 CSMAGIC_EMBEDDED_SIGNATURE
= 0xfade0cc0,
30 CSSLOT_CODEDIRECTORY
= 0,
33 struct CodeSigningBlob
{
38 struct CodeSigningSuperBlob
{
42 CodeSigningBlob index
[];
45 struct CodeSigningDirectory
{
52 uint32_t nSpecialSlots
;
61 uint32_t scatterOffset
;
66 class MachOImageReaderTest
: public testing::Test
{
68 void OpenTestFile(const char* file_name
, base::MemoryMappedFile
* file
) {
69 base::FilePath test_data
;
70 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA
, &test_data
));
72 base::FilePath path
= test_data
.AppendASCII("safe_browsing")
73 .AppendASCII("mach_o")
74 .AppendASCII(file_name
);
76 ASSERT_TRUE(file
->Initialize(path
));
79 // Returns the identity of the signed code data.
80 void GetSigningIdentity(const std::vector
<uint8_t>& signature
,
81 std::string
* identity
) {
83 reinterpret_cast<const CodeSigningSuperBlob
*>(&signature
[0]);
84 EXPECT_EQ(CSMAGIC_EMBEDDED_SIGNATURE
, ntohl(super_blob
->magic
));
85 ASSERT_EQ(CSSLOT_CODEDIRECTORY
, ntohl(super_blob
->index
[0].type
));
86 size_t dir_offset
= ntohl(super_blob
->index
[0].offset
);
88 reinterpret_cast<const CodeSigningDirectory
*>(&signature
[dir_offset
]);
89 ASSERT_EQ(CSMAGIC_CODEDIRECTORY
, ntohl(directory
->magic
));
90 size_t ident_offset
= ntohl(directory
->identOffset
) + dir_offset
;
92 std::string(reinterpret_cast<const char*>(&signature
[ident_offset
]));
95 // Returns the hash of the code data itself. Note that this is not the
96 // CDHash, but is instead the hash in the CodeDirectory blob, which is
97 // over the contents of the signed data. This is visible as hash #0
98 // when using `codesign -d -vvvvvv`.
99 void GetCodeSignatureHash(const std::vector
<uint8_t>& signature
,
100 std::vector
<uint8_t>* hash
) {
102 reinterpret_cast<const CodeSigningSuperBlob
*>(&signature
[0]);
103 EXPECT_EQ(CSMAGIC_EMBEDDED_SIGNATURE
, ntohl(super_blob
->magic
));
104 ASSERT_EQ(CSSLOT_CODEDIRECTORY
, ntohl(super_blob
->index
[0].type
));
105 size_t dir_offset
= ntohl(super_blob
->index
[0].offset
);
107 reinterpret_cast<const CodeSigningDirectory
*>(&signature
[dir_offset
]);
108 ASSERT_EQ(CSMAGIC_CODEDIRECTORY
, ntohl(directory
->magic
));
109 size_t hash_offset
= ntohl(directory
->hashOffset
) + dir_offset
;
110 std::vector
<uint8_t> actual_hash(&signature
[hash_offset
],
111 &signature
[hash_offset
+ directory
->hashSize
]);
112 EXPECT_EQ(20u, actual_hash
.size());
116 void ExpectCodeSignatureHash(const std::vector
<uint8_t>& signature
,
117 const char* expected
) {
118 std::vector
<uint8_t> actual_hash
;
119 GetCodeSignatureHash(signature
, &actual_hash
);
121 std::vector
<uint8_t> expected_hash
;
122 ASSERT_TRUE(base::HexStringToBytes(expected
, &expected_hash
));
123 EXPECT_EQ(expected_hash
, actual_hash
);
127 TEST_F(MachOImageReaderTest
, Executable32
) {
128 base::MemoryMappedFile file
;
129 ASSERT_NO_FATAL_FAILURE(OpenTestFile("executable32", &file
));
130 MachOImageReader reader
;
131 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
133 EXPECT_FALSE(reader
.IsFat());
134 EXPECT_FALSE(reader
.Is64Bit());
135 EXPECT_TRUE(reader
.GetMachHeader());
136 EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE
), reader
.GetFileType());
137 EXPECT_EQ(15u, reader
.GetLoadCommands().size());
139 std::vector
<uint8_t> signature
;
140 EXPECT_FALSE(reader
.GetCodeSignatureInfo(&signature
));
141 EXPECT_TRUE(signature
.empty());
143 // Test an arbitrary load command.
144 auto commands
= reader
.GetLoadCommands();
145 ASSERT_EQ(15u, commands
.size());
146 auto command
= commands
[11];
147 ASSERT_EQ(static_cast<uint32_t>(LC_LOAD_DYLIB
), command
.cmd());
148 auto actual
= command
.as_command
<dylib_command
>();
149 EXPECT_EQ(2u, actual
->dylib
.timestamp
);
150 EXPECT_EQ(0x4ad0101u
, actual
->dylib
.current_version
);
151 EXPECT_EQ(0x10000u
, actual
->dylib
.compatibility_version
);
154 TEST_F(MachOImageReaderTest
, Executable64
) {
155 base::MemoryMappedFile file
;
156 ASSERT_NO_FATAL_FAILURE(OpenTestFile("executable64", &file
));
157 MachOImageReader reader
;
158 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
160 EXPECT_FALSE(reader
.IsFat());
161 EXPECT_TRUE(reader
.Is64Bit());
162 EXPECT_TRUE(reader
.GetMachHeader());
163 EXPECT_TRUE(reader
.GetMachHeader64());
164 EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE
), reader
.GetFileType());
165 EXPECT_EQ(15u, reader
.GetLoadCommands().size());
167 std::vector
<uint8_t> signature
;
168 EXPECT_FALSE(reader
.GetCodeSignatureInfo(&signature
));
169 EXPECT_TRUE(signature
.empty());
172 TEST_F(MachOImageReaderTest
, ExecutableFat
) {
173 base::MemoryMappedFile file
;
174 ASSERT_NO_FATAL_FAILURE(OpenTestFile("executablefat", &file
));
175 MachOImageReader reader
;
176 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
178 EXPECT_TRUE(reader
.IsFat());
179 auto images
= reader
.GetFatImages();
180 ASSERT_EQ(2u, images
.size());
182 // Note: this image is crafted to have 32-bit first.
184 EXPECT_FALSE(images
[0]->IsFat());
185 EXPECT_FALSE(images
[0]->Is64Bit());
186 EXPECT_TRUE(images
[0]->GetMachHeader());
187 EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE
), images
[0]->GetFileType());
189 std::vector
<uint8_t> signature
;
190 EXPECT_FALSE(images
[0]->GetCodeSignatureInfo(&signature
));
191 EXPECT_TRUE(signature
.empty());
195 EXPECT_FALSE(images
[1]->IsFat());
196 EXPECT_TRUE(images
[1]->Is64Bit());
197 EXPECT_TRUE(images
[1]->GetMachHeader());
198 EXPECT_TRUE(images
[1]->GetMachHeader64());
199 EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE
), images
[1]->GetFileType());
201 std::vector
<uint8_t> signature
;
202 EXPECT_FALSE(images
[1]->GetCodeSignatureInfo(&signature
));
203 EXPECT_TRUE(signature
.empty());
205 // Test an arbitrary load command.
206 auto commands
= images
[1]->GetLoadCommands();
207 ASSERT_EQ(15u, commands
.size());
208 auto command
= commands
[1];
209 ASSERT_EQ(static_cast<uint32_t>(LC_SEGMENT_64
), command
.cmd());
210 auto actual
= command
.as_command
<segment_command_64
>();
211 EXPECT_EQ("__TEXT", std::string(actual
->segname
));
212 EXPECT_EQ(0u, actual
->fileoff
);
213 EXPECT_EQ(4096u, actual
->filesize
);
214 EXPECT_EQ(0x7, actual
->maxprot
);
215 EXPECT_EQ(0x5, actual
->initprot
);
216 EXPECT_EQ(3u, actual
->nsects
);
217 EXPECT_EQ(0u, actual
->flags
);
221 TEST_F(MachOImageReaderTest
, ExecutablePPC
) {
222 base::MemoryMappedFile file
;
223 ASSERT_NO_FATAL_FAILURE(OpenTestFile("executableppc", &file
));
224 MachOImageReader reader
;
225 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
227 EXPECT_FALSE(reader
.IsFat());
228 EXPECT_FALSE(reader
.Is64Bit());
229 EXPECT_TRUE(reader
.GetMachHeader());
230 EXPECT_EQ(OSSwapInt32(MH_EXECUTE
), reader
.GetFileType());
231 EXPECT_EQ(10u, reader
.GetLoadCommands().size());
233 std::vector
<uint8_t> signature
;
234 EXPECT_FALSE(reader
.GetCodeSignatureInfo(&signature
));
235 EXPECT_TRUE(signature
.empty());
238 TEST_F(MachOImageReaderTest
, Dylib32
) {
239 base::MemoryMappedFile file
;
240 ASSERT_NO_FATAL_FAILURE(OpenTestFile("lib32.dylib", &file
));
241 MachOImageReader reader
;
242 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
244 EXPECT_FALSE(reader
.IsFat());
245 EXPECT_FALSE(reader
.Is64Bit());
246 EXPECT_TRUE(reader
.GetMachHeader());
247 EXPECT_EQ(static_cast<uint32_t>(MH_DYLIB
), reader
.GetFileType());
248 EXPECT_EQ(13u, reader
.GetLoadCommands().size());
250 std::vector
<uint8_t> signature
;
251 EXPECT_FALSE(reader
.GetCodeSignatureInfo(&signature
));
252 EXPECT_TRUE(signature
.empty());
255 TEST_F(MachOImageReaderTest
, Dylib64
) {
256 base::MemoryMappedFile file
;
257 ASSERT_NO_FATAL_FAILURE(OpenTestFile("lib64.dylib", &file
));
258 MachOImageReader reader
;
259 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
261 EXPECT_FALSE(reader
.IsFat());
262 EXPECT_TRUE(reader
.Is64Bit());
263 EXPECT_TRUE(reader
.GetMachHeader());
264 EXPECT_TRUE(reader
.GetMachHeader64());
265 EXPECT_EQ(static_cast<uint32_t>(MH_DYLIB
), reader
.GetFileType());
266 EXPECT_EQ(13u, reader
.GetLoadCommands().size());
268 std::vector
<uint8_t> signature
;
269 EXPECT_FALSE(reader
.GetCodeSignatureInfo(&signature
));
270 EXPECT_TRUE(signature
.empty());
272 // Test an arbitrary load command.
273 auto commands
= reader
.GetLoadCommands();
274 ASSERT_EQ(13u, commands
.size());
275 auto command
= commands
[6];
276 ASSERT_EQ(static_cast<uint32_t>(LC_UUID
), command
.cmd());
277 uuid_t expected
= {0xB6, 0xB5, 0x12, 0xD7,
281 0x87, 0x46, 0x36, 0x76, 0x87, 0x47};
282 EXPECT_EQ(0, uuid_compare(expected
,
283 command
.as_command
<uuid_command
>()->uuid
));
286 TEST_F(MachOImageReaderTest
, DylibFat
) {
287 base::MemoryMappedFile file
;
288 ASSERT_NO_FATAL_FAILURE(OpenTestFile("libfat.dylib", &file
));
289 MachOImageReader reader
;
290 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
292 EXPECT_TRUE(reader
.IsFat());
293 auto images
= reader
.GetFatImages();
294 ASSERT_EQ(2u, images
.size());
296 // Note: this image is crafted to have 64-bit first.
298 EXPECT_FALSE(images
[0]->IsFat());
299 EXPECT_TRUE(images
[0]->Is64Bit());
300 EXPECT_TRUE(images
[0]->GetMachHeader());
301 EXPECT_TRUE(images
[0]->GetMachHeader64());
302 EXPECT_EQ(static_cast<uint32_t>(MH_DYLIB
), images
[0]->GetFileType());
304 std::vector
<uint8_t> signature
;
305 EXPECT_FALSE(images
[0]->GetCodeSignatureInfo(&signature
));
306 EXPECT_TRUE(signature
.empty());
310 EXPECT_FALSE(images
[1]->IsFat());
311 EXPECT_FALSE(images
[1]->Is64Bit());
312 EXPECT_TRUE(images
[1]->GetMachHeader());
313 EXPECT_EQ(static_cast<uint32_t>(MH_DYLIB
), images
[1]->GetFileType());
315 std::vector
<uint8_t> signature
;
316 EXPECT_FALSE(images
[1]->GetCodeSignatureInfo(&signature
));
317 EXPECT_TRUE(signature
.empty());
321 TEST_F(MachOImageReaderTest
, SignedExecutable32
) {
322 base::MemoryMappedFile file
;
323 ASSERT_NO_FATAL_FAILURE(OpenTestFile("signedexecutable32", &file
));
324 MachOImageReader reader
;
325 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
327 EXPECT_FALSE(reader
.IsFat());
328 EXPECT_FALSE(reader
.Is64Bit());
329 EXPECT_TRUE(reader
.GetMachHeader());
330 EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE
), reader
.GetFileType());
331 EXPECT_EQ(16u, reader
.GetLoadCommands().size());
333 std::vector
<uint8_t> signature
;
334 EXPECT_TRUE(reader
.GetCodeSignatureInfo(&signature
));
335 EXPECT_EQ(9344u, signature
.size());
337 std::string identity
;
338 GetSigningIdentity(signature
, &identity
);
339 EXPECT_EQ("signedexecutable32", identity
);
341 ExpectCodeSignatureHash(signature
,
342 "11fb88eb63c10dfc3d24a2545ea2a9c50c2921b5");
345 TEST_F(MachOImageReaderTest
, SignedExecutableFat
) {
346 base::MemoryMappedFile file
;
347 ASSERT_NO_FATAL_FAILURE(OpenTestFile("signedexecutablefat", &file
));
348 MachOImageReader reader
;
349 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
351 EXPECT_TRUE(reader
.IsFat());
352 auto images
= reader
.GetFatImages();
353 ASSERT_EQ(2u, images
.size());
355 // Note: this image is crafted to have 32-bit first.
357 EXPECT_FALSE(images
[0]->IsFat());
358 EXPECT_FALSE(images
[0]->Is64Bit());
359 EXPECT_TRUE(images
[0]->GetMachHeader());
360 EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE
), images
[0]->GetFileType());
362 std::vector
<uint8_t> signature
;
363 EXPECT_TRUE(images
[0]->GetCodeSignatureInfo(&signature
));
364 EXPECT_EQ(9344u, signature
.size());
366 std::string identity
;
367 GetSigningIdentity(signature
, &identity
);
368 EXPECT_EQ("signedexecutablefat", identity
);
370 ExpectCodeSignatureHash(signature
,
371 "11fb88eb63c10dfc3d24a2545ea2a9c50c2921b5");
375 EXPECT_FALSE(images
[1]->IsFat());
376 EXPECT_TRUE(images
[1]->Is64Bit());
377 EXPECT_TRUE(images
[1]->GetMachHeader());
378 EXPECT_TRUE(images
[1]->GetMachHeader64());
379 EXPECT_EQ(static_cast<uint32_t>(MH_EXECUTE
), images
[1]->GetFileType());
381 std::vector
<uint8_t> signature
;
382 EXPECT_TRUE(images
[1]->GetCodeSignatureInfo(&signature
));
383 EXPECT_EQ(9344u, signature
.size());
385 std::string identity
;
386 GetSigningIdentity(signature
, &identity
);
387 EXPECT_EQ("signedexecutablefat", identity
);
389 ExpectCodeSignatureHash(signature
,
390 "750a57326ba85857371094900475defd837f5e14");
394 TEST_F(MachOImageReaderTest
, SignedDylib64
) {
395 base::MemoryMappedFile file
;
396 ASSERT_NO_FATAL_FAILURE(OpenTestFile("libsigned64.dylib", &file
));
397 MachOImageReader reader
;
398 ASSERT_TRUE(reader
.Initialize(file
.data(), file
.length()));
400 EXPECT_FALSE(reader
.IsFat());
401 EXPECT_TRUE(reader
.Is64Bit());
402 EXPECT_TRUE(reader
.GetMachHeader());
403 EXPECT_TRUE(reader
.GetMachHeader64());
404 EXPECT_EQ(static_cast<uint32_t>(MH_DYLIB
), reader
.GetFileType());
405 EXPECT_EQ(14u, reader
.GetLoadCommands().size());
407 std::vector
<uint8_t> signature
;
408 EXPECT_TRUE(reader
.GetCodeSignatureInfo(&signature
));
409 EXPECT_EQ(9328u, signature
.size());
411 std::string identity
;
412 GetSigningIdentity(signature
, &identity
);
413 EXPECT_EQ("libsigned64", identity
);
415 ExpectCodeSignatureHash(signature
,
416 "8b1c79b60bb53a7f17b5618d5feb10dc8b88d806");
419 TEST_F(MachOImageReaderTest
, NotMachO
) {
420 base::MemoryMappedFile file
;
421 ASSERT_NO_FATAL_FAILURE(OpenTestFile("src.c", &file
));
422 MachOImageReader reader
;
423 EXPECT_FALSE(reader
.Initialize(file
.data(), file
.length()));
427 } // namespace safe_browsing