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