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.
5 #include "sandbox/win/tests/common/controller.h"
9 #include "base/memory/shared_memory.h"
10 #include "base/process/process.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/win/windows_version.h"
14 #include "sandbox/win/src/sandbox_factory.h"
18 static const int kDefaultTimeout
= 60000;
20 // Constructs a full path to a file inside the system32 folder.
21 base::string16
MakePathToSys32(const wchar_t* name
, bool is_obj_man_path
) {
22 wchar_t windows_path
[MAX_PATH
] = {0};
23 if (0 == ::GetSystemWindowsDirectoryW(windows_path
, MAX_PATH
))
24 return base::string16();
26 base::string16
full_path(windows_path
);
27 if (full_path
.empty())
31 full_path
.insert(0, L
"\\??\\");
33 full_path
+= L
"\\system32\\";
38 // Constructs a full path to a file inside the syswow64 folder.
39 base::string16
MakePathToSysWow64(const wchar_t* name
, bool is_obj_man_path
) {
40 wchar_t windows_path
[MAX_PATH
] = {0};
41 if (0 == ::GetSystemWindowsDirectoryW(windows_path
, MAX_PATH
))
42 return base::string16();
44 base::string16
full_path(windows_path
);
45 if (full_path
.empty())
49 full_path
.insert(0, L
"\\??\\");
51 full_path
+= L
"\\SysWOW64\\";
56 bool IsProcessRunning(HANDLE process
) {
58 if (::GetExitCodeProcess(process
, &exit_code
))
59 return exit_code
== STILL_ACTIVE
;
67 base::string16
MakePathToSys(const wchar_t* name
, bool is_obj_man_path
) {
68 return (base::win::OSInfo::GetInstance()->wow64_status() ==
69 base::win::OSInfo::WOW64_ENABLED
) ?
70 MakePathToSysWow64(name
, is_obj_man_path
) :
71 MakePathToSys32(name
, is_obj_man_path
);
74 BrokerServices
* GetBroker() {
75 static BrokerServices
* broker
= SandboxFactory::GetBrokerServices();
76 static bool is_initialized
= false;
82 if (!is_initialized
) {
83 if (SBOX_ALL_OK
!= broker
->Init())
86 is_initialized
= true;
92 TestRunner::TestRunner(JobLevel job_level
, TokenLevel startup_token
,
93 TokenLevel main_token
)
94 : is_init_(false), is_async_(false), no_sandbox_(false),
95 target_process_id_(0) {
96 Init(job_level
, startup_token
, main_token
);
99 TestRunner::TestRunner()
100 : is_init_(false), is_async_(false), no_sandbox_(false),
101 target_process_id_(0) {
102 Init(JOB_LOCKDOWN
, USER_RESTRICTED_SAME_ACCESS
, USER_LOCKDOWN
);
105 void TestRunner::Init(JobLevel job_level
, TokenLevel startup_token
,
106 TokenLevel main_token
) {
109 timeout_
= kDefaultTimeout
;
110 state_
= AFTER_REVERT
;
112 kill_on_destruction_
= true;
113 target_process_id_
= 0;
115 broker_
= GetBroker();
119 policy_
= broker_
->CreatePolicy();
123 policy_
->SetJobLevel(job_level
, 0);
124 policy_
->SetTokenLevel(startup_token
, main_token
);
126 // Close all ALPC ports.
127 if (base::win::GetVersion() >= base::win::VERSION_WIN8
) {
128 policy_
->AddKernelObjectToClose(L
"ALPC Port", NULL
);
134 TargetPolicy
* TestRunner::GetPolicy() {
138 TestRunner::~TestRunner() {
139 if (target_process_
.IsValid() && kill_on_destruction_
)
140 ::TerminateProcess(target_process_
.Get(), 0);
146 bool TestRunner::AddRule(TargetPolicy::SubSystem subsystem
,
147 TargetPolicy::Semantics semantics
,
148 const wchar_t* pattern
) {
152 return (SBOX_ALL_OK
== policy_
->AddRule(subsystem
, semantics
, pattern
));
155 bool TestRunner::AddRuleSys32(TargetPolicy::Semantics semantics
,
156 const wchar_t* pattern
) {
160 base::string16 win32_path
= MakePathToSys32(pattern
, false);
161 if (win32_path
.empty())
164 if (!AddRule(TargetPolicy::SUBSYS_FILES
, semantics
, win32_path
.c_str()))
167 if (base::win::OSInfo::GetInstance()->wow64_status() !=
168 base::win::OSInfo::WOW64_ENABLED
)
171 win32_path
= MakePathToSysWow64(pattern
, false);
172 if (win32_path
.empty())
175 return AddRule(TargetPolicy::SUBSYS_FILES
, semantics
, win32_path
.c_str());
178 bool TestRunner::AddFsRule(TargetPolicy::Semantics semantics
,
179 const wchar_t* pattern
) {
183 return AddRule(TargetPolicy::SUBSYS_FILES
, semantics
, pattern
);
186 int TestRunner::RunTest(const wchar_t* command
) {
188 return SBOX_TEST_INVALID_PARAMETER
;
190 wchar_t state_number
[2];
191 state_number
[0] = static_cast<wchar_t>(L
'0' + state_
);
192 state_number
[1] = L
'\0';
193 base::string16
full_command(state_number
);
194 full_command
+= L
" ";
195 full_command
+= command
;
197 return InternalRunTest(full_command
.c_str());
200 int TestRunner::InternalRunTest(const wchar_t* command
) {
202 return SBOX_TEST_FAILED_TO_RUN_TEST
;
204 // For simplicity TestRunner supports only one process per instance.
205 if (target_process_
.IsValid()) {
206 if (IsProcessRunning(target_process_
.Get()))
207 return SBOX_TEST_FAILED_TO_RUN_TEST
;
208 target_process_
.Close();
209 target_process_id_
= 0;
212 // Get the path to the sandboxed process.
213 wchar_t prog_name
[MAX_PATH
];
214 GetModuleFileNameW(NULL
, prog_name
, MAX_PATH
);
216 // Launch the sandboxed process.
217 ResultCode result
= SBOX_ALL_OK
;
218 PROCESS_INFORMATION target
= {0};
220 base::string16
arguments(L
"\"");
221 arguments
+= prog_name
;
222 arguments
+= L
"\" -child";
223 arguments
+= no_sandbox_
? L
"-no-sandbox " : L
" ";
224 arguments
+= command
;
227 STARTUPINFO startup_info
= {sizeof(STARTUPINFO
)};
228 if (!::CreateProcessW(prog_name
, &arguments
[0], NULL
, NULL
, FALSE
, 0,
229 NULL
, NULL
, &startup_info
, &target
)) {
230 return SBOX_ERROR_GENERIC
;
232 broker_
->AddTargetPeer(target
.hProcess
);
234 result
= broker_
->SpawnTarget(prog_name
, arguments
.c_str(), policy_
,
238 if (SBOX_ALL_OK
!= result
)
239 return SBOX_TEST_FAILED_TO_RUN_TEST
;
241 ::ResumeThread(target
.hThread
);
243 // For an asynchronous run we don't bother waiting.
245 target_process_
.Set(target
.hProcess
);
246 target_process_id_
= target
.dwProcessId
;
247 ::CloseHandle(target
.hThread
);
248 return SBOX_TEST_SUCCEEDED
;
251 if (::IsDebuggerPresent()) {
252 // Don't kill the target process on a time-out while we are debugging.
256 if (WAIT_TIMEOUT
== ::WaitForSingleObject(target
.hProcess
, timeout_
)) {
257 ::TerminateProcess(target
.hProcess
, static_cast<UINT
>(SBOX_TEST_TIMED_OUT
));
258 ::CloseHandle(target
.hProcess
);
259 ::CloseHandle(target
.hThread
);
260 return SBOX_TEST_TIMED_OUT
;
263 DWORD exit_code
= static_cast<DWORD
>(SBOX_TEST_LAST_RESULT
);
264 if (!::GetExitCodeProcess(target
.hProcess
, &exit_code
)) {
265 ::CloseHandle(target
.hProcess
);
266 ::CloseHandle(target
.hThread
);
267 return SBOX_TEST_FAILED_TO_RUN_TEST
;
270 ::CloseHandle(target
.hProcess
);
271 ::CloseHandle(target
.hThread
);
276 void TestRunner::SetTimeout(DWORD timeout_ms
) {
277 timeout_
= timeout_ms
;
280 void TestRunner::SetTestState(SboxTestsState desired_state
) {
281 state_
= desired_state
;
284 // This is the main procedure for the target (child) application. We'll find out
285 // the target test and call it.
286 // We expect the arguments to be:
287 // argv[1] = "-child"
288 // argv[2] = SboxTestsState when to run the command
289 // argv[3] = command to run
290 // argv[4...] = command arguments.
291 int DispatchCall(int argc
, wchar_t **argv
) {
293 return SBOX_TEST_INVALID_PARAMETER
;
295 // We hard code two tests to avoid dispatch failures.
296 if (0 == _wcsicmp(argv
[3], L
"wait")) {
298 return SBOX_TEST_TIMED_OUT
;
301 if (0 == _wcsicmp(argv
[3], L
"ping"))
302 return SBOX_TEST_PING_OK
;
304 // If the caller shared a shared memory handle with us attempt to open it
305 // in read only mode and sleep infinitely if we succeed.
306 if (0 == _wcsicmp(argv
[3], L
"shared_memory_handle")) {
307 base::SharedMemoryHandle shared_handle
= NULL
;
309 argv
[4], reinterpret_cast<unsigned int*>(&shared_handle
));
310 if (shared_handle
== NULL
)
311 return SBOX_TEST_INVALID_PARAMETER
;
312 base::SharedMemory
read_only_view(shared_handle
, true);
313 if (!read_only_view
.Map(0))
314 return SBOX_TEST_INVALID_PARAMETER
;
315 std::string
contents(reinterpret_cast<char*>(read_only_view
.memory()));
316 if (contents
!= "Hello World")
317 return SBOX_TEST_INVALID_PARAMETER
;
319 return SBOX_TEST_TIMED_OUT
;
322 SboxTestsState state
= static_cast<SboxTestsState
>(_wtoi(argv
[2]));
323 if ((state
<= MIN_STATE
) || (state
>= MAX_STATE
))
324 return SBOX_TEST_INVALID_PARAMETER
;
327 if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
|
328 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT
,
329 reinterpret_cast<wchar_t*>(&DispatchCall
), &module
))
330 return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND
;
332 std::string command_name
= base::SysWideToMultiByte(argv
[3], CP_UTF8
);
333 CommandFunction command
= reinterpret_cast<CommandFunction
>(
334 ::GetProcAddress(module
, command_name
.c_str()));
336 return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND
;
338 if (BEFORE_INIT
== state
)
339 return command(argc
- 4, argv
+ 4);
340 else if (EVERY_STATE
== state
)
341 command(argc
- 4, argv
+ 4);
343 TargetServices
* target
= SandboxFactory::GetTargetServices();
345 if (SBOX_ALL_OK
!= target
->Init())
346 return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND
;
348 if (BEFORE_REVERT
== state
)
349 return command(argc
- 4, argv
+ 4);
350 else if (EVERY_STATE
== state
)
351 command(argc
- 4, argv
+ 4);
353 #if defined(ADDRESS_SANITIZER)
354 // Bind and leak dbghelp.dll before the token is lowered, otherwise
355 // AddressSanitizer will crash when trying to symbolize a report.
356 if (!LoadLibraryA("dbghelp.dll"))
357 return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND
;
360 target
->LowerToken();
361 } else if (0 != _wcsicmp(argv
[1], L
"-child-no-sandbox")) {
362 return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND
;
365 return command(argc
- 4, argv
+ 4);
368 } // namespace sandbox