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