OLD | NEW |
---|---|
(Empty) | |
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. | |
4 | |
5 #include "chrome/browser/safe_browsing/signature_evaluator_mac.h" | |
6 | |
7 #include <CoreFoundation/CoreFoundation.h> | |
8 #include <sys/xattr.h> | |
9 | |
10 #include <string> | |
11 #include <vector> | |
12 | |
13 #include "base/files/file_path.h" | |
14 #include "base/files/file_util.h" | |
15 #include "base/files/scoped_temp_dir.h" | |
16 #include "base/mac/mac_util.h" | |
17 #include "base/mac/scoped_cftyperef.h" | |
18 #include "base/path_service.h" | |
19 #include "base/strings/sys_string_conversions.h" | |
20 #include "base/test/scoped_path_override.h" | |
21 #include "chrome/browser/safe_browsing/incident_reporting/incident.h" | |
22 #include "chrome/browser/safe_browsing/incident_reporting/mock_incident_receiver .h" | |
23 #include "chrome/common/chrome_paths.h" | |
24 #include "chrome/common/safe_browsing/csd.pb.h" | |
25 #include "testing/gmock/include/gmock/gmock-matchers.h" | |
26 #include "testing/gmock/include/gmock/gmock.h" | |
27 #include "testing/gtest/include/gtest/gtest.h" | |
28 | |
29 using ::testing::_; | |
30 using ::testing::StrictMock; | |
31 | |
32 namespace safe_browsing { | |
33 | |
34 namespace { | |
35 | |
36 const char* const xattrs[] = { | |
37 "com.apple.cs.CodeDirectory", | |
38 "com.apple.cs.CodeSignature", | |
39 "com.apple.cs.CodeRequirements", | |
40 "com.apple.cs.CodeResources", | |
41 "com.apple.cs.CodeApplication", | |
42 "com.apple.cs.CodeEntitlements", | |
43 }; | |
44 | |
45 } // namespace | |
46 | |
47 class MacSignatureEvaluatorTest : public testing::Test { | |
48 protected: | |
49 void SetUp() override { | |
50 base::FilePath source_path; | |
51 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_path)); | |
52 testdata_path_ = | |
53 source_path.AppendASCII("safe_browsing").AppendASCII("mach_o"); | |
54 | |
55 base::FilePath dir_exe; | |
56 ASSERT_TRUE(PathService::Get(base::DIR_EXE, &dir_exe)); | |
57 base::FilePath file_exe; | |
58 ASSERT_TRUE(PathService::Get(base::FILE_EXE, &file_exe)); | |
59 | |
60 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); | |
61 } | |
62 | |
63 bool SetupXattrs(const base::FilePath& path) { | |
64 char sentinel = 'A'; | |
65 for (const auto& xattr : xattrs) { | |
66 std::vector<uint8_t> buf(10); | |
67 memset(&buf[0], sentinel++, buf.size()); | |
68 if (setxattr(path.value().c_str(), xattr, &buf[0], buf.size(), 0, 0) != 0) | |
69 return false; | |
70 } | |
71 return true; | |
72 } | |
73 | |
74 base::FilePath testdata_path_; | |
75 base::ScopedTempDir temp_dir_; | |
76 }; | |
77 | |
78 TEST_F(MacSignatureEvaluatorTest, RelativePathComponentTest) { | |
79 EXPECT_FALSE(MacSignatureEvaluator::GetRelativePathComponent( | |
80 base::FilePath("/foo"), | |
81 base::FilePath("/bar"), | |
82 nullptr)); | |
83 EXPECT_FALSE(MacSignatureEvaluator::GetRelativePathComponent( | |
84 base::FilePath("/foo/bar"), | |
85 base::FilePath("/bar/baz"), | |
86 nullptr)); | |
87 EXPECT_FALSE(MacSignatureEvaluator::GetRelativePathComponent( | |
88 base::FilePath("/foo/x"), | |
89 base::FilePath("/foo/y"), | |
90 nullptr)); | |
91 | |
92 std::string output1; | |
93 EXPECT_TRUE(MacSignatureEvaluator::GetRelativePathComponent( | |
94 base::FilePath("/foo/bar"), | |
95 base::FilePath("/foo/bar/y"), | |
96 &output1)); | |
97 EXPECT_EQ(output1, "y"); | |
98 | |
99 std::string output2; | |
100 EXPECT_TRUE(MacSignatureEvaluator::GetRelativePathComponent( | |
101 base::FilePath("/Applications/Google Chrome.app"), | |
102 base::FilePath("/Applications/Google Chrome.app/Contents/MacOS/foo"), | |
103 &output2)); | |
104 EXPECT_EQ(output2, "Contents/MacOS/foo"); | |
105 } | |
106 | |
107 TEST_F(MacSignatureEvaluatorTest, SimpleTest) { | |
108 // This is a simple test that checks the validity of a signed executable. | |
109 // There is no designated requirement: we only check the embedded signature. | |
110 base::FilePath path = testdata_path_.AppendASCII("signedexecutablefat"); | |
111 safe_browsing::MacSignatureEvaluator evaluator(path); | |
112 ASSERT_TRUE(evaluator.Initialize()); | |
113 | |
114 ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident; | |
115 EXPECT_TRUE(evaluator.PerformEvaluation(&incident)); | |
116 EXPECT_EQ(0, incident.contained_file_size()); | |
117 } | |
118 | |
119 TEST_F(MacSignatureEvaluatorTest, SimpleTestWithDR) { | |
120 // This test checks the signer against a designated requirement description. | |
121 base::FilePath path = testdata_path_.AppendASCII("signedexecutablefat"); | |
122 std::string requirement( | |
123 "certificate leaf[subject.CN]=\"untrusted@goat.local\""); | |
124 safe_browsing::MacSignatureEvaluator evaluator(path, requirement); | |
125 ASSERT_TRUE(evaluator.Initialize()); | |
126 | |
127 ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident; | |
128 EXPECT_TRUE(evaluator.PerformEvaluation(&incident)); | |
129 EXPECT_EQ(0, incident.contained_file_size()); | |
130 } | |
131 | |
132 TEST_F(MacSignatureEvaluatorTest, SimpleTestWithBadDR) { | |
133 // Now test with a designated requirement that does not describe the signer. | |
134 base::FilePath path = testdata_path_.AppendASCII("signedexecutablefat"); | |
135 safe_browsing::MacSignatureEvaluator evaluator(path, "anchor apple"); | |
136 ASSERT_TRUE(evaluator.Initialize()); | |
137 | |
138 ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident; | |
139 EXPECT_FALSE(evaluator.PerformEvaluation(&incident)); | |
140 EXPECT_EQ(-67050, incident.sec_error()); | |
141 EXPECT_TRUE(incident.has_signature()); | |
142 ASSERT_TRUE(incident.has_file_basename()); | |
143 EXPECT_EQ("signedexecutablefat", incident.file_basename()); | |
144 } | |
145 | |
146 TEST_F(MacSignatureEvaluatorTest, SimpleBundleTest) { | |
147 // Now test a simple, validly signed bundle. | |
148 base::FilePath path = testdata_path_.AppendASCII("test-bundle.app"); | |
149 | |
150 std::string requirement( | |
151 "certificate leaf[subject.CN]=\"untrusted@goat.local\""); | |
152 safe_browsing::MacSignatureEvaluator evaluator(path, requirement); | |
153 ASSERT_TRUE(evaluator.Initialize()); | |
154 | |
155 ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident; | |
156 EXPECT_TRUE(evaluator.PerformEvaluation(&incident)); | |
157 EXPECT_EQ(0, incident.contained_file_size()); | |
158 } | |
159 | |
160 TEST_F(MacSignatureEvaluatorTest, ModifiedMainExecTest32) { | |
161 // Now to a test modified, signed bundle. | |
162 base::FilePath path = testdata_path_.AppendASCII("modified-main-exec32.app"); | |
163 | |
164 std::string requirement( | |
165 "certificate leaf[subject.CN]=\"untrusted@goat.local\""); | |
166 safe_browsing::MacSignatureEvaluator evaluator(path, requirement); | |
167 ASSERT_TRUE(evaluator.Initialize()); | |
168 | |
169 ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident; | |
170 EXPECT_FALSE(evaluator.PerformEvaluation(&incident)); | |
171 EXPECT_EQ(-67061, incident.sec_error()); | |
172 EXPECT_EQ(path.BaseName().value(), incident.file_basename()); | |
173 EXPECT_FALSE(incident.has_signature()); | |
174 EXPECT_FALSE(incident.has_image_headers()); | |
175 ASSERT_EQ(1, incident.contained_file_size()); | |
176 | |
177 const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile& | |
178 contained_file = incident.contained_file(0); | |
Robert Sesek
2015/11/02 19:42:12
nit: +2
| |
179 EXPECT_EQ(contained_file.relative_path(), "Contents/MacOS/test-bundle"); | |
180 EXPECT_TRUE(contained_file.has_signature()); | |
181 EXPECT_TRUE(contained_file.has_image_headers()); | |
182 } | |
183 | |
184 TEST_F(MacSignatureEvaluatorTest, ModifiedMainExecTest64) { | |
185 // Snow Leopard does not know about the 64-bit slice so this test is | |
186 // irrelevant. | |
187 if (!base::mac::IsOSLionOrLater()) | |
188 return; | |
189 | |
190 // Now to a test modified, signed bundle. | |
191 base::FilePath path = | |
192 testdata_path_.AppendASCII("modified-main-exec64.app"); | |
193 | |
194 std::string requirement( | |
195 "certificate leaf[subject.CN]=\"untrusted@goat.local\""); | |
196 safe_browsing::MacSignatureEvaluator evaluator(path, requirement); | |
197 ASSERT_TRUE(evaluator.Initialize()); | |
198 | |
199 ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident; | |
200 EXPECT_FALSE(evaluator.PerformEvaluation(&incident)); | |
201 | |
202 EXPECT_EQ(-67061, incident.sec_error()); | |
203 EXPECT_EQ(path.BaseName().value(), incident.file_basename()); | |
204 EXPECT_FALSE(incident.has_signature()); | |
205 EXPECT_FALSE(incident.has_image_headers()); | |
206 ASSERT_EQ(1, incident.contained_file_size()); | |
207 | |
208 const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile& | |
209 contained_file = incident.contained_file(0); | |
Robert Sesek
2015/11/02 19:42:12
nit: +2
| |
210 EXPECT_EQ(contained_file.relative_path(), "Contents/MacOS/test-bundle"); | |
211 EXPECT_TRUE(contained_file.has_signature()); | |
212 EXPECT_TRUE(contained_file.has_image_headers()); | |
213 } | |
214 | |
215 TEST_F(MacSignatureEvaluatorTest, ModifiedBundleAndExecTest) { | |
216 // Now test a modified, signed bundle with resources added and the main | |
217 // executable modified. | |
218 base::FilePath path = | |
219 testdata_path_.AppendASCII("modified-bundle-and-exec.app"); | |
220 | |
221 std::string requirement( | |
222 "certificate leaf[subject.CN]=\"untrusted@goat.local\""); | |
223 safe_browsing::MacSignatureEvaluator evaluator(path, requirement); | |
224 ASSERT_TRUE(evaluator.Initialize()); | |
225 | |
226 ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident; | |
227 EXPECT_FALSE(evaluator.PerformEvaluation(&incident)); | |
228 EXPECT_EQ(-67061, incident.sec_error()); | |
229 EXPECT_FALSE(incident.has_signature()); | |
230 EXPECT_FALSE(incident.has_image_headers()); | |
231 EXPECT_EQ(path.BaseName().value(), incident.file_basename()); | |
232 ASSERT_EQ(1, incident.contained_file_size()); | |
233 | |
234 | |
235 const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile& | |
236 contained_file = incident.contained_file(0); | |
Robert Sesek
2015/11/02 19:42:12
nit: +2
| |
237 EXPECT_EQ(contained_file.relative_path(), "Contents/MacOS/test-bundle"); | |
238 EXPECT_TRUE(contained_file.has_signature()); | |
239 EXPECT_TRUE(contained_file.has_image_headers()); | |
240 } | |
241 | |
242 TEST_F(MacSignatureEvaluatorTest, ModifiedBundleTest) { | |
243 // Now test a modified, signed bundle. This bundle has | |
244 // the following problems: | |
245 // 1) A file was added (This should not be reported) | |
246 // 2) libsigned64.dylib was modified | |
247 // 3) executable32 was modified | |
248 | |
249 base::FilePath orig_path = testdata_path_.AppendASCII("modified-bundle.app"); | |
250 base::FilePath copied_path = | |
251 temp_dir_.path().AppendASCII("modified-bundle.app"); | |
252 CHECK(base::CopyDirectory(orig_path, copied_path, true)); | |
253 | |
254 // Setup the extended attributes, which don't persist in the git repo. | |
255 ASSERT_TRUE(SetupXattrs( | |
256 copied_path.AppendASCII("Contents/Resources/Base.lproj/MainMenu.nib"))); | |
257 | |
258 std::string requirement( | |
259 "certificate leaf[subject.CN]=\"untrusted@goat.local\""); | |
260 safe_browsing::MacSignatureEvaluator evaluator(copied_path, requirement); | |
261 ASSERT_TRUE(evaluator.Initialize()); | |
262 | |
263 ClientIncidentReport_IncidentData_BinaryIntegrityIncident incident; | |
264 EXPECT_FALSE(evaluator.PerformEvaluation(&incident)); | |
265 | |
266 EXPECT_TRUE(incident.has_file_basename()); | |
267 EXPECT_EQ(copied_path.BaseName().value(), incident.file_basename()); | |
268 EXPECT_FALSE(incident.has_signature()); | |
269 EXPECT_FALSE(incident.has_image_headers()); | |
270 EXPECT_EQ(-67054, incident.sec_error()); | |
271 ASSERT_EQ(4, incident.contained_file_size()); | |
272 | |
273 const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile* | |
274 main_exec = nullptr; | |
275 const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile* | |
276 libsigned64 = nullptr; | |
277 const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile* | |
278 executable32 = nullptr; | |
279 const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile* | |
280 mainmenunib = nullptr; | |
281 const ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile* | |
282 codesign_cfg = nullptr; | |
283 | |
284 for (const auto& contained_file : incident.contained_file()) { | |
285 if (contained_file.relative_path() == "Contents/MacOS/test-bundle") { | |
286 main_exec = &contained_file; | |
287 } else if (contained_file.relative_path() == | |
288 "Contents/Frameworks/libsigned64.dylib") { | |
289 libsigned64 = &contained_file; | |
290 } else if (contained_file.relative_path() == | |
291 "Contents/Resources/executable32") { | |
292 executable32 = &contained_file; | |
293 } else if (contained_file.relative_path() == | |
294 "Contents/Resources/Base.lproj/MainMenu.nib") { | |
295 mainmenunib = &contained_file; | |
296 } else if (contained_file.relative_path() == | |
297 "Contents/Resources/codesign.cfg") { | |
298 codesign_cfg = &contained_file; | |
299 } | |
300 } | |
301 | |
302 ASSERT_NE(main_exec, nullptr); | |
303 ASSERT_NE(mainmenunib, nullptr); | |
304 ASSERT_NE(libsigned64, nullptr); | |
305 ASSERT_NE(executable32, nullptr); | |
306 // This is important. Do not collect information on extra files added. | |
307 EXPECT_EQ(codesign_cfg, nullptr); | |
308 | |
309 | |
310 EXPECT_TRUE(libsigned64->has_relative_path()); | |
311 EXPECT_EQ("Contents/Frameworks/libsigned64.dylib", | |
312 libsigned64->relative_path()); | |
Robert Sesek
2015/11/02 19:42:12
nit: align with the "
| |
313 EXPECT_TRUE(libsigned64->has_signature()); | |
314 | |
315 EXPECT_TRUE(executable32->has_relative_path()); | |
316 EXPECT_EQ("Contents/Resources/executable32", executable32->relative_path()); | |
317 EXPECT_TRUE(executable32->has_signature()); | |
318 EXPECT_TRUE(executable32->has_image_headers()); | |
319 | |
320 EXPECT_TRUE(mainmenunib->has_relative_path()); | |
321 EXPECT_EQ("Contents/Resources/Base.lproj/MainMenu.nib", | |
322 mainmenunib->relative_path()); | |
323 EXPECT_TRUE(mainmenunib->has_signature()); | |
324 EXPECT_EQ(6, mainmenunib->signature().xattr_size()); | |
325 // Manually convert the global xattrs array to a vector | |
326 std::vector<std::string> xattrs_known; | |
327 for (const auto& xattr : xattrs) | |
328 xattrs_known.push_back(xattr); | |
329 | |
330 std::vector<std::string> xattrs_seen; | |
331 for (const auto& xattr : mainmenunib->signature().xattr()) { | |
332 ASSERT_TRUE(xattr.has_key()); | |
333 EXPECT_TRUE(xattr.has_value()); | |
334 xattrs_seen.push_back(xattr.key()); | |
335 } | |
336 EXPECT_THAT(xattrs_known, ::testing::ContainerEq(xattrs_seen)); | |
337 } | |
338 | |
339 } // namespace safe_browsing | |
OLD | NEW |