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
14 namespace {
15
16 static const int kDefaultTimeout = 60000;
17
18 // Constructs a full path to a file inside the system32 folder.
MakePathToSys32(const wchar_t * name,bool is_obj_man_path)19 base::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.
MakePathToSysWow64(const wchar_t * name,bool is_obj_man_path)37 base::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
IsProcessRunning(HANDLE process)54 bool 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
63 namespace sandbox {
64
MakePathToSys(const wchar_t * name,bool is_obj_man_path)65 base::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
GetBroker()72 BrokerServices* 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
TestRunner(JobLevel job_level,TokenLevel startup_token,TokenLevel main_token)90 TestRunner::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
TestRunner()97 TestRunner::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
Init(JobLevel job_level,TokenLevel startup_token,TokenLevel main_token)103 void 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
GetPolicy()127 TargetPolicy* TestRunner::GetPolicy() {
128 return policy_;
129 }
130
~TestRunner()131 TestRunner::~TestRunner() {
132 if (target_process_.IsValid() && kill_on_destruction_)
133 ::TerminateProcess(target_process_.Get(), 0);
134
135 if (policy_)
136 policy_->Release();
137 }
138
AddRule(TargetPolicy::SubSystem subsystem,TargetPolicy::Semantics semantics,const wchar_t * pattern)139 bool 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
AddRuleSys32(TargetPolicy::Semantics semantics,const wchar_t * pattern)148 bool 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
AddFsRule(TargetPolicy::Semantics semantics,const wchar_t * pattern)171 bool 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
RunTest(const wchar_t * command)179 int 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
InternalRunTest(const wchar_t * command)193 int 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
SetTimeout(DWORD timeout_ms)269 void TestRunner::SetTimeout(DWORD timeout_ms) {
270 timeout_ = timeout_ms;
271 }
272
SetTestState(SboxTestsState desired_state)273 void 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.
DispatchCall(int argc,wchar_t ** argv)284 int 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