1// Copyright (c) 2012 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 <memory>
6#include <string>
7
8#include "base/strings/string16.h"
9#include "base/strings/sys_string_conversions.h"
10#include "base/win/scoped_handle.h"
11#include "base/win/scoped_process_information.h"
12#include "base/win/windows_version.h"
13#include "sandbox/win/src/sandbox.h"
14#include "sandbox/win/src/sandbox_factory.h"
15#include "sandbox/win/src/sandbox_policy.h"
16#include "sandbox/win/tests/common/controller.h"
17#include "testing/gtest/include/gtest/gtest.h"
18
19namespace {
20
21// While the shell API provides better calls than this home brew function
22// we use GetSystemWindowsDirectoryW which does not query the registry so
23// it is safe to use after revert.
24base::string16 MakeFullPathToSystem32(const wchar_t* name) {
25  wchar_t windows_path[MAX_PATH] = {0};
26  ::GetSystemWindowsDirectoryW(windows_path, MAX_PATH);
27  base::string16 full_path(windows_path);
28  if (full_path.empty()) {
29    return full_path;
30  }
31  full_path += L"\\system32\\";
32  full_path += name;
33  return full_path;
34}
35
36// Creates a process with the |exe| and |command| parameter using the
37// unicode and ascii version of the api.
38sandbox::SboxTestResult CreateProcessHelper(const base::string16& exe,
39                                            const base::string16& command) {
40  base::win::ScopedProcessInformation pi;
41  STARTUPINFOW si = {sizeof(si)};
42
43  const wchar_t *exe_name = NULL;
44  if (!exe.empty())
45    exe_name = exe.c_str();
46
47  const wchar_t *cmd_line = NULL;
48  if (!command.empty())
49    cmd_line = command.c_str();
50
51  // Create the process with the unicode version of the API.
52  sandbox::SboxTestResult ret1 = sandbox::SBOX_TEST_FAILED;
53  PROCESS_INFORMATION temp_process_info = {};
54  if (::CreateProcessW(exe_name, const_cast<wchar_t*>(cmd_line), NULL, NULL,
55                       FALSE, 0, NULL, NULL, &si, &temp_process_info)) {
56    pi.Set(temp_process_info);
57    ret1 = sandbox::SBOX_TEST_SUCCEEDED;
58  } else {
59    DWORD last_error = GetLastError();
60    if ((ERROR_NOT_ENOUGH_QUOTA == last_error) ||
61        (ERROR_ACCESS_DENIED == last_error) ||
62        (ERROR_FILE_NOT_FOUND == last_error)) {
63      ret1 = sandbox::SBOX_TEST_DENIED;
64    } else {
65      ret1 = sandbox::SBOX_TEST_FAILED;
66    }
67  }
68
69  pi.Close();
70
71  // Do the same with the ansi version of the api
72  STARTUPINFOA sia = {sizeof(sia)};
73  sandbox::SboxTestResult ret2 = sandbox::SBOX_TEST_FAILED;
74
75  std::string narrow_cmd_line;
76  if (cmd_line)
77    narrow_cmd_line = base::SysWideToMultiByte(cmd_line, CP_UTF8);
78  if (::CreateProcessA(
79        exe_name ? base::SysWideToMultiByte(exe_name, CP_UTF8).c_str() : NULL,
80        cmd_line ? const_cast<char*>(narrow_cmd_line.c_str()) : NULL,
81        NULL, NULL, FALSE, 0, NULL, NULL, &sia, &temp_process_info)) {
82    pi.Set(temp_process_info);
83    ret2 = sandbox::SBOX_TEST_SUCCEEDED;
84  } else {
85    DWORD last_error = GetLastError();
86    if ((ERROR_NOT_ENOUGH_QUOTA == last_error) ||
87        (ERROR_ACCESS_DENIED == last_error) ||
88        (ERROR_FILE_NOT_FOUND == last_error)) {
89      ret2 = sandbox::SBOX_TEST_DENIED;
90    } else {
91      ret2 = sandbox::SBOX_TEST_FAILED;
92    }
93  }
94
95  if (ret1 == ret2)
96    return ret1;
97
98  return sandbox::SBOX_TEST_FAILED;
99}
100
101}  // namespace
102
103namespace sandbox {
104
105SBOX_TESTS_COMMAND int Process_RunApp1(int argc, wchar_t **argv) {
106  if (argc != 1) {
107    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
108  }
109  if ((NULL == argv) || (NULL == argv[0])) {
110    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
111  }
112  base::string16 path = MakeFullPathToSystem32(argv[0]);
113
114  // TEST 1: Try with the path in the app_name.
115  return CreateProcessHelper(path, base::string16());
116}
117
118SBOX_TESTS_COMMAND int Process_RunApp2(int argc, wchar_t **argv) {
119  if (argc != 1) {
120    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
121  }
122  if ((NULL == argv) || (NULL == argv[0])) {
123    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
124  }
125  base::string16 path = MakeFullPathToSystem32(argv[0]);
126
127  // TEST 2: Try with the path in the cmd_line.
128  base::string16 cmd_line = L"\"";
129  cmd_line += path;
130  cmd_line += L"\"";
131  return CreateProcessHelper(base::string16(), cmd_line);
132}
133
134SBOX_TESTS_COMMAND int Process_RunApp3(int argc, wchar_t **argv) {
135  if (argc != 1) {
136    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
137  }
138  if ((NULL == argv) || (NULL == argv[0])) {
139    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
140  }
141
142  // TEST 3: Try file name in the cmd_line.
143  return CreateProcessHelper(base::string16(), argv[0]);
144}
145
146SBOX_TESTS_COMMAND int Process_RunApp4(int argc, wchar_t **argv) {
147  if (argc != 1) {
148    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
149  }
150  if ((NULL == argv) || (NULL == argv[0])) {
151    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
152  }
153
154  // TEST 4: Try file name in the app_name and current directory sets correctly.
155  base::string16 system32 = MakeFullPathToSystem32(L"");
156  wchar_t current_directory[MAX_PATH + 1];
157  int result4;
158  bool test_succeeded = false;
159  DWORD ret = ::GetCurrentDirectory(MAX_PATH, current_directory);
160  if (!ret)
161    return SBOX_TEST_FIRST_ERROR;
162
163  if (ret < MAX_PATH) {
164    current_directory[ret] = L'\\';
165    current_directory[ret+1] = L'\0';
166    if (::SetCurrentDirectory(system32.c_str())) {
167      result4 = CreateProcessHelper(argv[0], base::string16());
168      if (::SetCurrentDirectory(current_directory)) {
169        test_succeeded = true;
170      }
171    } else {
172      return SBOX_TEST_SECOND_ERROR;
173    }
174  }
175  if (!test_succeeded)
176    result4 = SBOX_TEST_FAILED;
177
178  return result4;
179}
180
181SBOX_TESTS_COMMAND int Process_RunApp5(int argc, wchar_t **argv) {
182  if (argc != 1) {
183    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
184  }
185  if ((NULL == argv) || (NULL == argv[0])) {
186    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
187  }
188  base::string16 path = MakeFullPathToSystem32(argv[0]);
189
190  // TEST 5: Try with the path in the cmd_line and arguments.
191  base::string16 cmd_line = L"\"";
192  cmd_line += path;
193  cmd_line += L"\" /I";
194  return CreateProcessHelper(base::string16(), cmd_line);
195}
196
197SBOX_TESTS_COMMAND int Process_RunApp6(int argc, wchar_t **argv) {
198  if (argc != 1) {
199    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
200  }
201  if ((NULL == argv) || (NULL == argv[0])) {
202    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
203  }
204
205  // TEST 6: Try with the file_name in the cmd_line and arguments.
206  base::string16 cmd_line = argv[0];
207  cmd_line += L" /I";
208  return CreateProcessHelper(base::string16(), cmd_line);
209}
210
211// Creates a process and checks if it's possible to get a handle to it's token.
212SBOX_TESTS_COMMAND int Process_GetChildProcessToken(int argc, wchar_t **argv) {
213  if (argc != 1)
214    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
215
216  if ((NULL == argv) || (NULL == argv[0]))
217    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
218
219  base::string16 path = MakeFullPathToSystem32(argv[0]);
220
221  STARTUPINFOW si = {sizeof(si)};
222
223  PROCESS_INFORMATION temp_process_info = {};
224  if (!::CreateProcessW(path.c_str(), NULL, NULL, NULL, FALSE, CREATE_SUSPENDED,
225                        NULL, NULL, &si, &temp_process_info)) {
226      return SBOX_TEST_FAILED;
227  }
228  base::win::ScopedProcessInformation pi(temp_process_info);
229
230  HANDLE token = NULL;
231  BOOL result =
232      ::OpenProcessToken(pi.process_handle(), TOKEN_IMPERSONATE, &token);
233  DWORD error = ::GetLastError();
234
235  base::win::ScopedHandle token_handle(token);
236
237  if (!::TerminateProcess(pi.process_handle(), 0))
238    return SBOX_TEST_FAILED;
239
240  if (result && token)
241    return SBOX_TEST_SUCCEEDED;
242
243  if (ERROR_ACCESS_DENIED == error)
244    return SBOX_TEST_DENIED;
245
246  return SBOX_TEST_FAILED;
247}
248
249
250SBOX_TESTS_COMMAND int Process_OpenToken(int argc, wchar_t **argv) {
251  HANDLE token;
252  if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)) {
253    if (ERROR_ACCESS_DENIED == ::GetLastError()) {
254      return SBOX_TEST_DENIED;
255    }
256  } else {
257    ::CloseHandle(token);
258    return SBOX_TEST_SUCCEEDED;
259  }
260
261  return SBOX_TEST_FAILED;
262}
263
264TEST(ProcessPolicyTest, TestAllAccess) {
265  // Check if the "all access" rule fails to be added when the token is too
266  // powerful.
267  TestRunner runner;
268
269  // Check the failing case.
270  runner.GetPolicy()->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN);
271  EXPECT_EQ(SBOX_ERROR_UNSUPPORTED,
272            runner.GetPolicy()->AddRule(TargetPolicy::SUBSYS_PROCESS,
273                                        TargetPolicy::PROCESS_ALL_EXEC,
274                                        L"this is not important"));
275
276  // Check the working case.
277  runner.GetPolicy()->SetTokenLevel(USER_INTERACTIVE, USER_INTERACTIVE);
278
279  EXPECT_EQ(SBOX_ALL_OK,
280            runner.GetPolicy()->AddRule(TargetPolicy::SUBSYS_PROCESS,
281                                        TargetPolicy::PROCESS_ALL_EXEC,
282                                        L"this is not important"));
283}
284
285TEST(ProcessPolicyTest, CreateProcessAW) {
286  TestRunner runner;
287  base::string16 exe_path = MakeFullPathToSystem32(L"findstr.exe");
288  base::string16 system32 = MakeFullPathToSystem32(L"");
289  ASSERT_TRUE(!exe_path.empty());
290  EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS,
291                             TargetPolicy::PROCESS_MIN_EXEC,
292                             exe_path.c_str()));
293
294  // Need to add directory rules for the directories that we use in
295  // SetCurrentDirectory.
296  EXPECT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_DIR_ANY,
297                               system32.c_str()));
298
299  wchar_t current_directory[MAX_PATH];
300  DWORD ret = ::GetCurrentDirectory(MAX_PATH, current_directory);
301  ASSERT_TRUE(0 != ret && ret < MAX_PATH);
302
303  wcscat_s(current_directory, MAX_PATH, L"\\");
304  EXPECT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_DIR_ANY,
305                               current_directory));
306
307  EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp1 calc.exe"));
308  EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp2 calc.exe"));
309  EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp3 calc.exe"));
310  EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp5 calc.exe"));
311  EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp6 calc.exe"));
312
313  EXPECT_EQ(SBOX_TEST_SUCCEEDED,
314            runner.RunTest(L"Process_RunApp1 findstr.exe"));
315  EXPECT_EQ(SBOX_TEST_SUCCEEDED,
316            runner.RunTest(L"Process_RunApp2 findstr.exe"));
317  EXPECT_EQ(SBOX_TEST_SUCCEEDED,
318            runner.RunTest(L"Process_RunApp3 findstr.exe"));
319  EXPECT_EQ(SBOX_TEST_SUCCEEDED,
320            runner.RunTest(L"Process_RunApp5 findstr.exe"));
321  EXPECT_EQ(SBOX_TEST_SUCCEEDED,
322            runner.RunTest(L"Process_RunApp6 findstr.exe"));
323
324#if !defined(_WIN64)
325  if (base::win::OSInfo::GetInstance()->version() >= base::win::VERSION_VISTA) {
326    // WinXP results are not reliable.
327    EXPECT_EQ(SBOX_TEST_SECOND_ERROR,
328        runner.RunTest(L"Process_RunApp4 calc.exe"));
329    EXPECT_EQ(SBOX_TEST_SECOND_ERROR,
330        runner.RunTest(L"Process_RunApp4 findstr.exe"));
331  }
332#endif
333}
334
335TEST(ProcessPolicyTest, OpenToken) {
336  TestRunner runner;
337  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Process_OpenToken"));
338}
339
340TEST(ProcessPolicyTest, TestGetProcessTokenMinAccess) {
341  TestRunner runner;
342  base::string16 exe_path = MakeFullPathToSystem32(L"findstr.exe");
343  ASSERT_TRUE(!exe_path.empty());
344  EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS,
345                             TargetPolicy::PROCESS_MIN_EXEC,
346                             exe_path.c_str()));
347
348  EXPECT_EQ(SBOX_TEST_DENIED,
349            runner.RunTest(L"Process_GetChildProcessToken findstr.exe"));
350}
351
352TEST(ProcessPolicyTest, TestGetProcessTokenMaxAccess) {
353  TestRunner runner(JOB_UNPROTECTED, USER_INTERACTIVE, USER_INTERACTIVE);
354  base::string16 exe_path = MakeFullPathToSystem32(L"findstr.exe");
355  ASSERT_TRUE(!exe_path.empty());
356  EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS,
357                             TargetPolicy::PROCESS_ALL_EXEC,
358                             exe_path.c_str()));
359
360  EXPECT_EQ(SBOX_TEST_SUCCEEDED,
361            runner.RunTest(L"Process_GetChildProcessToken findstr.exe"));
362}
363
364TEST(ProcessPolicyTest, TestGetProcessTokenMinAccessNoJob) {
365  TestRunner runner(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN);
366  base::string16 exe_path = MakeFullPathToSystem32(L"findstr.exe");
367  ASSERT_TRUE(!exe_path.empty());
368  EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS,
369                             TargetPolicy::PROCESS_MIN_EXEC,
370                             exe_path.c_str()));
371
372  EXPECT_EQ(SBOX_TEST_DENIED,
373            runner.RunTest(L"Process_GetChildProcessToken findstr.exe"));
374}
375
376TEST(ProcessPolicyTest, TestGetProcessTokenMaxAccessNoJob) {
377  TestRunner runner(JOB_NONE, USER_INTERACTIVE, USER_INTERACTIVE);
378  base::string16 exe_path = MakeFullPathToSystem32(L"findstr.exe");
379  ASSERT_TRUE(!exe_path.empty());
380  EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS,
381                             TargetPolicy::PROCESS_ALL_EXEC,
382                             exe_path.c_str()));
383
384  EXPECT_EQ(SBOX_TEST_SUCCEEDED,
385            runner.RunTest(L"Process_GetChildProcessToken findstr.exe"));
386}
387
388}  // namespace sandbox
389