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