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