1 // Copyright 2019 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/chrome_cleaner/engines/target/engine_file_requests_proxy.h"
6
7 #include <limits>
8 #include <memory>
9 #include <string>
10 #include <utility>
11
12 #include "base/strings/string_util.h"
13 #include "base/test/task_environment.h"
14 #include "chrome/chrome_cleaner/engines/target/sandboxed_test_helpers.h"
15 #include "chrome/chrome_cleaner/os/pre_fetched_paths.h"
16 #include "chrome/chrome_cleaner/test/test_file_util.h"
17 #include "chrome/chrome_cleaner/test/test_strings.h"
18 #include "chrome/chrome_cleaner/test/test_util.h"
19 #include "components/chrome_cleaner/test/test_name_helper.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21 #include "testing/multiprocess_func_list.h"
22
23 namespace chrome_cleaner {
24
25 namespace {
26
27 // A temporary directory and some file names to create in it.
28 constexpr char kTempDirPathSwitch[] = "temp-dir-path";
29 constexpr wchar_t kTestFile1[] = L"test_file_1.txt";
30 constexpr wchar_t kTestFile2[] = L"test_file_2.txt";
31
32 const FindFileHandle kInvalidFindFileHandle =
33 reinterpret_cast<FindFileHandle>(HandleToHandle64(INVALID_HANDLE_VALUE));
34
35 class TestChildProcess : public SandboxChildProcess {
36 public:
TestChildProcess(scoped_refptr<MojoTaskRunner> mojo_task_runner)37 explicit TestChildProcess(scoped_refptr<MojoTaskRunner> mojo_task_runner)
38 : SandboxChildProcess(std::move(mojo_task_runner)) {}
39
Initialize()40 bool Initialize() {
41 LowerToken();
42
43 temp_dir_path_ = command_line().GetSwitchValuePath(kTempDirPathSwitch);
44 if (temp_dir_path_.empty()) {
45 LOG(ERROR) << "Initialize failed: Missing " << kTempDirPathSwitch
46 << " switch";
47 return false;
48 }
49
50 return true;
51 }
52
temp_dir_path() const53 base::FilePath temp_dir_path() const { return temp_dir_path_; }
54
valid_utf8_path() const55 base::FilePath valid_utf8_path() const {
56 return temp_dir_path_.Append(kValidUtf8Name);
57 }
58
invalid_utf8_path() const59 base::FilePath invalid_utf8_path() const {
60 return temp_dir_path_.Append(kInvalidUtf8Name);
61 }
62
test_file_1_path() const63 base::FilePath test_file_1_path() const {
64 return temp_dir_path_.Append(kTestFile1);
65 }
66
test_file_2_path() const67 base::FilePath test_file_2_path() const {
68 return temp_dir_path_.Append(kTestFile2);
69 }
70
71 private:
72 ~TestChildProcess() override = default;
73
74 base::FilePath temp_dir_path_;
75 };
76
SetupSandboxedChildProcess()77 scoped_refptr<TestChildProcess> SetupSandboxedChildProcess() {
78 scoped_refptr<MojoTaskRunner> mojo_task_runner = MojoTaskRunner::Create();
79 auto child_process = base::MakeRefCounted<TestChildProcess>(mojo_task_runner);
80 if (!child_process->Initialize())
81 return base::MakeRefCounted<TestChildProcess>(nullptr);
82 return child_process;
83 }
84
MULTIPROCESS_TEST_MAIN(FindFirstFileSingle)85 MULTIPROCESS_TEST_MAIN(FindFirstFileSingle) {
86 auto child_process = SetupSandboxedChildProcess();
87 if (!child_process)
88 return 1;
89
90 scoped_refptr<EngineFileRequestsProxy> proxy(
91 child_process->GetFileRequestsProxy());
92
93 // Test invalid inputs first.
94 WIN32_FIND_DATAW data;
95 EXPECT_EQ(
96 SandboxErrorCode::NULL_FIND_HANDLE,
97 proxy->FindFirstFile(child_process->valid_utf8_path(), &data, nullptr));
98
99 FindFileHandle handle;
100 EXPECT_EQ(
101 SandboxErrorCode::NULL_DATA_HANDLE,
102 proxy->FindFirstFile(child_process->valid_utf8_path(), nullptr, &handle));
103 EXPECT_EQ(static_cast<uint32_t>(SandboxErrorCode::NULL_DATA_HANDLE),
104 proxy->FindNextFile(handle, nullptr));
105
106 uint32_t result =
107 proxy->FindFirstFile(child_process->valid_utf8_path(), &data, &handle);
108 if (kInvalidFindFileHandle == handle) {
109 LOG(ERROR) << std::hex
110 << "Didn't get a valid handle when trying to open a unicode "
111 "file path. Return code "
112 << result;
113 return 1;
114 }
115 if (!base::EqualsCaseInsensitiveASCII(kValidUtf8Name, data.cFileName)) {
116 LOG(ERROR) << "Returned file name doesn't match, expected "
117 << kValidUtf8Name << " and got " << data.cFileName;
118 return 1;
119 }
120
121 result = proxy->FindNextFile(handle, &data);
122 if (result != ERROR_NO_MORE_FILES) {
123 LOG(ERROR) << std::hex
124 << "Incorrectly returned additional files. Return code "
125 << result;
126 return 1;
127 }
128
129 result = proxy->FindClose(handle);
130 if (result != ERROR_SUCCESS) {
131 LOG(ERROR) << std::hex
132 << "Failed to close FindFirstFile handle. Return code "
133 << result;
134 return 1;
135 }
136
137 result =
138 proxy->FindFirstFile(child_process->invalid_utf8_path(), &data, &handle);
139 if (kInvalidFindFileHandle == handle) {
140 LOG(ERROR) << std::hex
141 << "Didn't get a valid handle when trying to open an invalid "
142 "utf8 path. Return code "
143 << result;
144 return 1;
145 }
146
147 if (!base::EqualsCaseInsensitiveASCII(kInvalidUtf8Name, data.cFileName)) {
148 LOG(ERROR) << "Returned file name doesn't match, expected "
149 << kInvalidUtf8Name << " and got " << data.cFileName;
150 return 1;
151 }
152
153 result = proxy->FindNextFile(handle, &data);
154 if (result != ERROR_NO_MORE_FILES) {
155 LOG(ERROR) << std::hex
156 << "Incorrectly returned additional files. Return code "
157 << result;
158 return 1;
159 }
160
161 result = proxy->FindClose(handle);
162 if (result != ERROR_SUCCESS) {
163 LOG(ERROR) << std::hex
164 << "Failed to close FindFirstFile handle. Return code "
165 << result;
166 return 1;
167 }
168
169 return ::testing::Test::HasNonfatalFailure();
170 }
171
MULTIPROCESS_TEST_MAIN(FindFirstFileMultiple)172 MULTIPROCESS_TEST_MAIN(FindFirstFileMultiple) {
173 auto child_process = SetupSandboxedChildProcess();
174 if (!child_process)
175 return 1;
176
177 scoped_refptr<EngineFileRequestsProxy> proxy(
178 child_process->GetFileRequestsProxy());
179
180 base::FilePath find_path = child_process->temp_dir_path().Append(L"test_*");
181 FindFileHandle handle;
182 WIN32_FIND_DATAW data;
183 uint32_t result = proxy->FindFirstFile(find_path, &data, &handle);
184 if (kInvalidFindFileHandle == handle) {
185 LOG(ERROR) << std::hex
186 << "Didn't get a valid handle when trying to open a "
187 "file path. Return code "
188 << result;
189 return 1;
190 }
191 std::wstring first_found = data.cFileName;
192
193 std::wstring file_name_1 =
194 child_process->test_file_1_path().BaseName().value();
195 std::wstring file_name_2 =
196 child_process->test_file_2_path().BaseName().value();
197 if (!base::EqualsCaseInsensitiveASCII(file_name_1, data.cFileName) &&
198 !base::EqualsCaseInsensitiveASCII(file_name_2, data.cFileName)) {
199 LOG(ERROR) << "Returned file name doesn't match, expected " << file_name_1
200 << " or " << file_name_2 << " and got " << data.cFileName;
201 return 1;
202 }
203
204 result = proxy->FindNextFile(handle, &data);
205 if (result != ERROR_SUCCESS) {
206 LOG(ERROR) << std::hex << "First call to FindNextFile failed. Return code "
207 << result;
208 return 1;
209 }
210 if (!base::EqualsCaseInsensitiveASCII(file_name_1, data.cFileName) &&
211 !base::EqualsCaseInsensitiveASCII(file_name_2, data.cFileName)) {
212 LOG(ERROR) << "Returned file name doesn't match, expected " << file_name_1
213 << " or " << file_name_2 << " and got " << data.cFileName;
214 return 1;
215 }
216 if (first_found == data.cFileName) {
217 LOG(ERROR) << "Same file name was returned twice. " << first_found;
218 return 1;
219 }
220
221 result = proxy->FindNextFile(handle, &data);
222 if (result != ERROR_NO_MORE_FILES) {
223 LOG(ERROR) << std::hex
224 << "Incorrectly returned additional files. Return code "
225 << result;
226 return 1;
227 }
228
229 result = proxy->FindClose(handle);
230 if (result != ERROR_SUCCESS) {
231 LOG(ERROR) << std::hex
232 << "Failed to close FindFirstFile handle. Return code "
233 << result;
234 return 1;
235 }
236
237 return ::testing::Test::HasNonfatalFailure();
238 }
239
MULTIPROCESS_TEST_MAIN(FindFirstFileNoHangs)240 MULTIPROCESS_TEST_MAIN(FindFirstFileNoHangs) {
241 auto child_process = SetupSandboxedChildProcess();
242 if (!child_process)
243 return 1;
244
245 scoped_refptr<EngineFileRequestsProxy> proxy(
246 child_process->GetFileRequestsProxy());
247
248 WIN32_FIND_DATAW data;
249 EXPECT_EQ(
250 SandboxErrorCode::NULL_FIND_HANDLE,
251 proxy->FindFirstFile(child_process->valid_utf8_path(), &data, nullptr));
252
253 child_process->UnbindRequestsRemotes();
254 FindFileHandle handle;
255 EXPECT_EQ(SandboxErrorCode::INTERNAL_ERROR,
256 proxy->FindFirstFile(base::FilePath(L"Name"), &data, &handle));
257
258 return ::testing::Test::HasNonfatalFailure();
259 }
260
MULTIPROCESS_TEST_MAIN(FindNextFileNoHangs)261 MULTIPROCESS_TEST_MAIN(FindNextFileNoHangs) {
262 auto child_process = SetupSandboxedChildProcess();
263 if (!child_process)
264 return 1;
265 child_process->UnbindRequestsRemotes();
266
267 scoped_refptr<EngineFileRequestsProxy> proxy(
268 child_process->GetFileRequestsProxy());
269
270 FindFileHandle handle = kInvalidFindFileHandle;
271 WIN32_FIND_DATAW data;
272 EXPECT_EQ(SandboxErrorCode::INTERNAL_ERROR,
273 proxy->FindNextFile(handle, &data));
274
275 return ::testing::Test::HasNonfatalFailure();
276 }
277
MULTIPROCESS_TEST_MAIN(FindCloseNoHangs)278 MULTIPROCESS_TEST_MAIN(FindCloseNoHangs) {
279 auto child_process = SetupSandboxedChildProcess();
280 if (!child_process)
281 return 1;
282 child_process->UnbindRequestsRemotes();
283
284 scoped_refptr<EngineFileRequestsProxy> proxy(
285 child_process->GetFileRequestsProxy());
286
287 EXPECT_EQ(SandboxErrorCode::INTERNAL_ERROR, proxy->FindClose(0));
288
289 return ::testing::Test::HasNonfatalFailure();
290 }
291
MULTIPROCESS_TEST_MAIN(OpenReadOnlyFile)292 MULTIPROCESS_TEST_MAIN(OpenReadOnlyFile) {
293 auto child_process = SetupSandboxedChildProcess();
294 if (!child_process)
295 return 1;
296
297 scoped_refptr<EngineFileRequestsProxy> proxy(
298 child_process->GetFileRequestsProxy());
299
300 base::win::ScopedHandle result = proxy->OpenReadOnlyFile(
301 base::FilePath(L"fake_name"), FILE_ATTRIBUTE_NORMAL);
302 if (result.IsValid()) {
303 LOG(ERROR) << "Didn't get an invalid handle when passing an invalid name. "
304 "Expected "
305 << INVALID_HANDLE_VALUE << " but got " << result.Get();
306 return 1;
307 }
308
309 result = proxy->OpenReadOnlyFile(
310 PreFetchedPaths::GetInstance()->GetExecutablePath(),
311 FILE_ATTRIBUTE_NORMAL);
312 if (!result.IsValid()) {
313 LOG(ERROR)
314 << "Didn't get a valid handle when trying to open this executable";
315 return 1;
316 }
317
318 result = proxy->OpenReadOnlyFile(child_process->valid_utf8_path(),
319 FILE_ATTRIBUTE_NORMAL);
320 if (!result.IsValid()) {
321 LOG(ERROR)
322 << "Didn't get a valid handle when trying to open a unicode file path";
323 return 1;
324 }
325 result = proxy->OpenReadOnlyFile(child_process->invalid_utf8_path(),
326 FILE_ATTRIBUTE_NORMAL);
327 if (!result.IsValid()) {
328 LOG(ERROR)
329 << "Didn't get a valid handle when trying to open an invalid utf8 path";
330 return 1;
331 }
332
333 return ::testing::Test::HasNonfatalFailure();
334 }
335
MULTIPROCESS_TEST_MAIN(OpenReadOnlyFileNoHangs)336 MULTIPROCESS_TEST_MAIN(OpenReadOnlyFileNoHangs) {
337 auto child_process = SetupSandboxedChildProcess();
338 if (!child_process)
339 return 1;
340
341 scoped_refptr<EngineFileRequestsProxy> proxy(
342 child_process->GetFileRequestsProxy());
343
344 const std::wstring too_long(std::numeric_limits<int16_t>::max() + 1, '0');
345 EXPECT_FALSE(
346 proxy->OpenReadOnlyFile(base::FilePath(too_long), FILE_ATTRIBUTE_NORMAL)
347 .IsValid());
348
349 child_process->UnbindRequestsRemotes();
350
351 EXPECT_FALSE(proxy->OpenReadOnlyFile(base::FilePath(), FILE_ATTRIBUTE_NORMAL)
352 .IsValid());
353
354 return ::testing::Test::HasNonfatalFailure();
355 }
356
357 using TestParentProcess = MaybeSandboxedParentProcess<SandboxedParentProcess>;
358
359 class EngineFileRequestsProxyTest
360 : public ::testing::TestWithParam<const char*> {
361 public:
SetUp()362 void SetUp() override {
363 mojo_task_runner_ = MojoTaskRunner::Create();
364
365 parent_process_ = base::MakeRefCounted<TestParentProcess>(
366 mojo_task_runner_, TestParentProcess::CallbacksToSetup::kFileRequests);
367 }
368
369 protected:
370 scoped_refptr<MojoTaskRunner> mojo_task_runner_;
371 scoped_refptr<TestParentProcess> parent_process_;
372 };
373
TEST_P(EngineFileRequestsProxyTest,TestRequest)374 TEST_P(EngineFileRequestsProxyTest, TestRequest) {
375 base::test::TaskEnvironment task_environment;
376
377 // Create resources that tests running in the sandbox will not have access to
378 // create for themselves, even before calling LowerToken.
379 base::ScopedTempDir temp_dir;
380 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
381 ASSERT_TRUE(CreateEmptyFile(temp_dir.GetPath().Append(kValidUtf8Name)));
382 ASSERT_TRUE(CreateEmptyFile(temp_dir.GetPath().Append(kInvalidUtf8Name)));
383 ASSERT_TRUE(CreateEmptyFile(temp_dir.GetPath().Append(kTestFile1)));
384 ASSERT_TRUE(CreateEmptyFile(temp_dir.GetPath().Append(kTestFile2)));
385 parent_process_->AppendSwitchPath(kTempDirPathSwitch, temp_dir.GetPath());
386
387 int32_t exit_code = -1;
388 EXPECT_TRUE(
389 parent_process_->LaunchConnectedChildProcess(GetParam(), &exit_code));
390 EXPECT_EQ(0, exit_code);
391 }
392
393 INSTANTIATE_TEST_SUITE_P(ProxyTests,
394 EngineFileRequestsProxyTest,
395 testing::Values("FindFirstFileSingle",
396 "FindFirstFileMultiple",
397 "FindFirstFileNoHangs",
398 "FindNextFileNoHangs",
399 "FindCloseNoHangs",
400 "OpenReadOnlyFile",
401 "OpenReadOnlyFileNoHangs"),
402 GetParamNameForTest());
403
404 } // namespace
405
406 } // namespace chrome_cleaner
407