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