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 "sandbox/win/tests/common/controller.h"
6
7#include <string>
8
9#include "base/process/process.h"
10#include "base/strings/sys_string_conversions.h"
11#include "base/win/windows_version.h"
12#include "sandbox/win/src/sandbox_factory.h"
13
14namespace {
15
16static const int kDefaultTimeout = 60000;
17
18// Constructs a full path to a file inside the system32 folder.
19base::string16 MakePathToSys32(const wchar_t* name, bool is_obj_man_path) {
20  wchar_t windows_path[MAX_PATH] = {0};
21  if (0 == ::GetSystemWindowsDirectoryW(windows_path, MAX_PATH))
22    return base::string16();
23
24  base::string16 full_path(windows_path);
25  if (full_path.empty())
26    return full_path;
27
28  if (is_obj_man_path)
29    full_path.insert(0, L"\\??\\");
30
31  full_path += L"\\system32\\";
32  full_path += name;
33  return full_path;
34}
35
36// Constructs a full path to a file inside the syswow64 folder.
37base::string16 MakePathToSysWow64(const wchar_t* name, bool is_obj_man_path) {
38  wchar_t windows_path[MAX_PATH] = {0};
39  if (0 == ::GetSystemWindowsDirectoryW(windows_path, MAX_PATH))
40    return base::string16();
41
42  base::string16 full_path(windows_path);
43  if (full_path.empty())
44    return full_path;
45
46  if (is_obj_man_path)
47    full_path.insert(0, L"\\??\\");
48
49  full_path += L"\\SysWOW64\\";
50  full_path += name;
51  return full_path;
52}
53
54bool IsProcessRunning(HANDLE process) {
55  DWORD exit_code = 0;
56  if (::GetExitCodeProcess(process, &exit_code))
57    return exit_code == STILL_ACTIVE;
58  return false;
59}
60
61}  // namespace
62
63namespace sandbox {
64
65base::string16 MakePathToSys(const wchar_t* name, bool is_obj_man_path) {
66  return (base::win::OSInfo::GetInstance()->wow64_status() ==
67      base::win::OSInfo::WOW64_ENABLED) ?
68      MakePathToSysWow64(name, is_obj_man_path) :
69      MakePathToSys32(name, is_obj_man_path);
70}
71
72BrokerServices* GetBroker() {
73  static BrokerServices* broker = SandboxFactory::GetBrokerServices();
74  static bool is_initialized = false;
75
76  if (!broker) {
77    return NULL;
78  }
79
80  if (!is_initialized) {
81    if (SBOX_ALL_OK != broker->Init())
82      return NULL;
83
84    is_initialized = true;
85  }
86
87  return broker;
88}
89
90TestRunner::TestRunner(JobLevel job_level, TokenLevel startup_token,
91                       TokenLevel main_token)
92    : is_init_(false), is_async_(false), no_sandbox_(false),
93      target_process_id_(0) {
94  Init(job_level, startup_token, main_token);
95}
96
97TestRunner::TestRunner()
98    : is_init_(false), is_async_(false), no_sandbox_(false),
99      target_process_id_(0) {
100  Init(JOB_LOCKDOWN, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN);
101}
102
103void TestRunner::Init(JobLevel job_level, TokenLevel startup_token,
104                      TokenLevel main_token) {
105  broker_ = NULL;
106  policy_ = NULL;
107  timeout_ = kDefaultTimeout;
108  state_ = AFTER_REVERT;
109  is_async_= false;
110  kill_on_destruction_ = true;
111  target_process_id_ = 0;
112
113  broker_ = GetBroker();
114  if (!broker_)
115    return;
116
117  policy_ = broker_->CreatePolicy();
118  if (!policy_)
119    return;
120
121  policy_->SetJobLevel(job_level, 0);
122  policy_->SetTokenLevel(startup_token, main_token);
123
124  is_init_ = true;
125}
126
127TargetPolicy* TestRunner::GetPolicy() {
128  return policy_;
129}
130
131TestRunner::~TestRunner() {
132  if (target_process_.IsValid() && kill_on_destruction_)
133    ::TerminateProcess(target_process_.Get(), 0);
134
135  if (policy_)
136    policy_->Release();
137}
138
139bool TestRunner::AddRule(TargetPolicy::SubSystem subsystem,
140                         TargetPolicy::Semantics semantics,
141                         const wchar_t* pattern) {
142  if (!is_init_)
143    return false;
144
145  return (SBOX_ALL_OK == policy_->AddRule(subsystem, semantics, pattern));
146}
147
148bool TestRunner::AddRuleSys32(TargetPolicy::Semantics semantics,
149                              const wchar_t* pattern) {
150  if (!is_init_)
151    return false;
152
153  base::string16 win32_path = MakePathToSys32(pattern, false);
154  if (win32_path.empty())
155    return false;
156
157  if (!AddRule(TargetPolicy::SUBSYS_FILES, semantics, win32_path.c_str()))
158    return false;
159
160  if (base::win::OSInfo::GetInstance()->wow64_status() !=
161      base::win::OSInfo::WOW64_ENABLED)
162    return true;
163
164  win32_path = MakePathToSysWow64(pattern, false);
165  if (win32_path.empty())
166    return false;
167
168  return AddRule(TargetPolicy::SUBSYS_FILES, semantics, win32_path.c_str());
169}
170
171bool TestRunner::AddFsRule(TargetPolicy::Semantics semantics,
172                           const wchar_t* pattern) {
173  if (!is_init_)
174    return false;
175
176  return AddRule(TargetPolicy::SUBSYS_FILES, semantics, pattern);
177}
178
179int TestRunner::RunTest(const wchar_t* command) {
180  if (MAX_STATE > 10)
181    return SBOX_TEST_INVALID_PARAMETER;
182
183  wchar_t state_number[2];
184  state_number[0] = L'0' + state_;
185  state_number[1] = L'\0';
186  base::string16 full_command(state_number);
187  full_command += L" ";
188  full_command += command;
189
190  return InternalRunTest(full_command.c_str());
191}
192
193int TestRunner::InternalRunTest(const wchar_t* command) {
194  if (!is_init_)
195    return SBOX_TEST_FAILED_TO_RUN_TEST;
196
197  // For simplicity TestRunner supports only one process per instance.
198  if (target_process_.IsValid()) {
199    if (IsProcessRunning(target_process_.Get()))
200      return SBOX_TEST_FAILED_TO_RUN_TEST;
201    target_process_.Close();
202    target_process_id_ = 0;
203  }
204
205  // Get the path to the sandboxed process.
206  wchar_t prog_name[MAX_PATH];
207  GetModuleFileNameW(NULL, prog_name, MAX_PATH);
208
209  // Launch the sandboxed process.
210  ResultCode result = SBOX_ALL_OK;
211  PROCESS_INFORMATION target = {0};
212
213  base::string16 arguments(L"\"");
214  arguments += prog_name;
215  arguments += L"\" -child";
216  arguments += no_sandbox_ ? L"-no-sandbox " : L" ";
217  arguments += command;
218
219  if (no_sandbox_) {
220    STARTUPINFO startup_info = {sizeof(STARTUPINFO)};
221    if (!::CreateProcessW(prog_name, &arguments[0], NULL, NULL, FALSE, 0,
222                          NULL, NULL, &startup_info, &target)) {
223      return SBOX_ERROR_GENERIC;
224    }
225    broker_->AddTargetPeer(target.hProcess);
226  } else {
227    result = broker_->SpawnTarget(prog_name, arguments.c_str(), policy_,
228                                  &target);
229  }
230
231  if (SBOX_ALL_OK != result)
232    return SBOX_TEST_FAILED_TO_RUN_TEST;
233
234  ::ResumeThread(target.hThread);
235
236  // For an asynchronous run we don't bother waiting.
237  if (is_async_) {
238    target_process_.Set(target.hProcess);
239    target_process_id_ = target.dwProcessId;
240    ::CloseHandle(target.hThread);
241    return SBOX_TEST_SUCCEEDED;
242  }
243
244  if (::IsDebuggerPresent()) {
245    // Don't kill the target process on a time-out while we are debugging.
246    timeout_ = INFINITE;
247  }
248
249  if (WAIT_TIMEOUT == ::WaitForSingleObject(target.hProcess, timeout_)) {
250    ::TerminateProcess(target.hProcess, static_cast<UINT>(SBOX_TEST_TIMED_OUT));
251    ::CloseHandle(target.hProcess);
252    ::CloseHandle(target.hThread);
253    return SBOX_TEST_TIMED_OUT;
254  }
255
256  DWORD exit_code = static_cast<DWORD>(SBOX_TEST_LAST_RESULT);
257  if (!::GetExitCodeProcess(target.hProcess, &exit_code)) {
258    ::CloseHandle(target.hProcess);
259    ::CloseHandle(target.hThread);
260    return SBOX_TEST_FAILED_TO_RUN_TEST;
261  }
262
263  ::CloseHandle(target.hProcess);
264  ::CloseHandle(target.hThread);
265
266  return exit_code;
267}
268
269void TestRunner::SetTimeout(DWORD timeout_ms) {
270  timeout_ = timeout_ms;
271}
272
273void TestRunner::SetTestState(SboxTestsState desired_state) {
274  state_ = desired_state;
275}
276
277// This is the main procedure for the target (child) application. We'll find out
278// the target test and call it.
279// We expect the arguments to be:
280//  argv[1] = "-child"
281//  argv[2] = SboxTestsState when to run the command
282//  argv[3] = command to run
283//  argv[4...] = command arguments.
284int DispatchCall(int argc, wchar_t **argv) {
285  if (argc < 4)
286    return SBOX_TEST_INVALID_PARAMETER;
287
288  // We hard code two tests to avoid dispatch failures.
289  if (0 == _wcsicmp(argv[3], L"wait")) {
290      Sleep(INFINITE);
291      return SBOX_TEST_TIMED_OUT;
292  }
293
294  if (0 == _wcsicmp(argv[3], L"ping"))
295      return SBOX_TEST_PING_OK;
296
297  SboxTestsState state = static_cast<SboxTestsState>(_wtoi(argv[2]));
298  if ((state <= MIN_STATE) || (state >= MAX_STATE))
299    return SBOX_TEST_INVALID_PARAMETER;
300
301  HMODULE module;
302  if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
303                             GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
304                         reinterpret_cast<wchar_t*>(&DispatchCall), &module))
305    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
306
307  std::string command_name = base::SysWideToMultiByte(argv[3], CP_UTF8);
308  CommandFunction command = reinterpret_cast<CommandFunction>(
309                                ::GetProcAddress(module, command_name.c_str()));
310  if (!command)
311    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
312
313  if (BEFORE_INIT == state)
314    return command(argc - 4, argv + 4);
315  else if (EVERY_STATE == state)
316    command(argc - 4, argv + 4);
317
318  TargetServices* target = SandboxFactory::GetTargetServices();
319  if (target) {
320    if (SBOX_ALL_OK != target->Init())
321      return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
322
323    if (BEFORE_REVERT == state)
324      return command(argc - 4, argv + 4);
325    else if (EVERY_STATE == state)
326      command(argc - 4, argv + 4);
327
328    target->LowerToken();
329  } else if (0 != _wcsicmp(argv[1], L"-child-no-sandbox")) {
330    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
331  }
332
333  return command(argc - 4, argv + 4);
334}
335
336}  // namespace sandbox
337