michael@0: // Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "base/win/scoped_process_information.h" michael@0: #include "base/win/windows_version.h" michael@0: #include "sandbox/win/src/sandbox.h" michael@0: #include "sandbox/win/src/sandbox_factory.h" michael@0: #include "sandbox/win/src/sandbox_utils.h" michael@0: #include "sandbox/win/src/target_services.h" michael@0: #include "sandbox/win/tests/common/controller.h" michael@0: #include "testing/gtest/include/gtest/gtest.h" michael@0: michael@0: namespace sandbox { michael@0: michael@0: #define BINDNTDLL(name) \ michael@0: name ## Function name = reinterpret_cast( \ michael@0: ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), #name)) michael@0: michael@0: // Reverts to self and verify that SetInformationToken was faked. Returns michael@0: // SBOX_TEST_SUCCEEDED if faked and SBOX_TEST_FAILED if not faked. michael@0: SBOX_TESTS_COMMAND int PolicyTargetTest_token(int argc, wchar_t **argv) { michael@0: HANDLE thread_token; michael@0: // Get the thread token, using impersonation. michael@0: if (!::OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | michael@0: TOKEN_DUPLICATE, FALSE, &thread_token)) michael@0: return ::GetLastError(); michael@0: michael@0: ::RevertToSelf(); michael@0: ::CloseHandle(thread_token); michael@0: michael@0: int ret = SBOX_TEST_FAILED; michael@0: if (::OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | TOKEN_DUPLICATE, michael@0: FALSE, &thread_token)) { michael@0: ret = SBOX_TEST_SUCCEEDED; michael@0: ::CloseHandle(thread_token); michael@0: } michael@0: return ret; michael@0: } michael@0: michael@0: // Stores the high privilege token on a static variable, change impersonation michael@0: // again to that one and verify that we are not interfering anymore with michael@0: // RevertToSelf. michael@0: SBOX_TESTS_COMMAND int PolicyTargetTest_steal(int argc, wchar_t **argv) { michael@0: static HANDLE thread_token; michael@0: if (!SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) { michael@0: if (!::OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | michael@0: TOKEN_DUPLICATE, FALSE, &thread_token)) michael@0: return ::GetLastError(); michael@0: } else { michael@0: if (!::SetThreadToken(NULL, thread_token)) michael@0: return ::GetLastError(); michael@0: michael@0: // See if we fake the call again. michael@0: int ret = PolicyTargetTest_token(argc, argv); michael@0: ::CloseHandle(thread_token); michael@0: return ret; michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: // Opens the thread token with and without impersonation. michael@0: SBOX_TESTS_COMMAND int PolicyTargetTest_token2(int argc, wchar_t **argv) { michael@0: HANDLE thread_token; michael@0: // Get the thread token, using impersonation. michael@0: if (!::OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | michael@0: TOKEN_DUPLICATE, FALSE, &thread_token)) michael@0: return ::GetLastError(); michael@0: ::CloseHandle(thread_token); michael@0: michael@0: // Get the thread token, without impersonation. michael@0: if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | TOKEN_DUPLICATE, michael@0: TRUE, &thread_token)) michael@0: return ::GetLastError(); michael@0: ::CloseHandle(thread_token); michael@0: return SBOX_TEST_SUCCEEDED; michael@0: } michael@0: michael@0: // Opens the thread token with and without impersonation, using michael@0: // NtOpenThreadTokenEX. michael@0: SBOX_TESTS_COMMAND int PolicyTargetTest_token3(int argc, wchar_t **argv) { michael@0: BINDNTDLL(NtOpenThreadTokenEx); michael@0: if (!NtOpenThreadTokenEx) michael@0: return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; michael@0: michael@0: HANDLE thread_token; michael@0: // Get the thread token, using impersonation. michael@0: NTSTATUS status = NtOpenThreadTokenEx(GetCurrentThread(), michael@0: TOKEN_IMPERSONATE | TOKEN_DUPLICATE, michael@0: FALSE, 0, &thread_token); michael@0: if (status == STATUS_NO_TOKEN) michael@0: return ERROR_NO_TOKEN; michael@0: if (!NT_SUCCESS(status)) michael@0: return SBOX_TEST_FAILED; michael@0: michael@0: ::CloseHandle(thread_token); michael@0: michael@0: // Get the thread token, without impersonation. michael@0: status = NtOpenThreadTokenEx(GetCurrentThread(), michael@0: TOKEN_IMPERSONATE | TOKEN_DUPLICATE, TRUE, 0, michael@0: &thread_token); michael@0: if (!NT_SUCCESS(status)) michael@0: return SBOX_TEST_FAILED; michael@0: michael@0: ::CloseHandle(thread_token); michael@0: return SBOX_TEST_SUCCEEDED; michael@0: } michael@0: michael@0: // Tests that we can open the current thread. michael@0: SBOX_TESTS_COMMAND int PolicyTargetTest_thread(int argc, wchar_t **argv) { michael@0: DWORD thread_id = ::GetCurrentThreadId(); michael@0: HANDLE thread = ::OpenThread(SYNCHRONIZE, FALSE, thread_id); michael@0: if (!thread) michael@0: return ::GetLastError(); michael@0: if (!::CloseHandle(thread)) michael@0: return ::GetLastError(); michael@0: michael@0: return SBOX_TEST_SUCCEEDED; michael@0: } michael@0: michael@0: // New thread entry point: do nothing. michael@0: DWORD WINAPI PolicyTargetTest_thread_main(void* param) { michael@0: ::Sleep(INFINITE); michael@0: return 0; michael@0: } michael@0: michael@0: // Tests that we can create a new thread, and open it. michael@0: SBOX_TESTS_COMMAND int PolicyTargetTest_thread2(int argc, wchar_t **argv) { michael@0: // Use default values to create a new thread. michael@0: DWORD thread_id; michael@0: HANDLE thread = ::CreateThread(NULL, 0, &PolicyTargetTest_thread_main, 0, 0, michael@0: &thread_id); michael@0: if (!thread) michael@0: return ::GetLastError(); michael@0: if (!::CloseHandle(thread)) michael@0: return ::GetLastError(); michael@0: michael@0: thread = ::OpenThread(SYNCHRONIZE, FALSE, thread_id); michael@0: if (!thread) michael@0: return ::GetLastError(); michael@0: michael@0: if (!::CloseHandle(thread)) michael@0: return ::GetLastError(); michael@0: michael@0: return SBOX_TEST_SUCCEEDED; michael@0: } michael@0: michael@0: // Tests that we can call CreateProcess. michael@0: SBOX_TESTS_COMMAND int PolicyTargetTest_process(int argc, wchar_t **argv) { michael@0: // Use default values to create a new process. michael@0: STARTUPINFO startup_info = {0}; michael@0: startup_info.cb = sizeof(startup_info); michael@0: base::win::ScopedProcessInformation process_info; michael@0: if (!::CreateProcessW(L"foo.exe", L"foo.exe", NULL, NULL, FALSE, 0, michael@0: NULL, NULL, &startup_info, process_info.Receive())) michael@0: return SBOX_TEST_SUCCEEDED; michael@0: return SBOX_TEST_FAILED; michael@0: } michael@0: michael@0: TEST(PolicyTargetTest, SetInformationThread) { michael@0: TestRunner runner; michael@0: if (base::win::GetVersion() >= base::win::VERSION_XP) { michael@0: runner.SetTestState(BEFORE_REVERT); michael@0: EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token")); michael@0: } michael@0: michael@0: runner.SetTestState(AFTER_REVERT); michael@0: EXPECT_EQ(ERROR_NO_TOKEN, runner.RunTest(L"PolicyTargetTest_token")); michael@0: michael@0: runner.SetTestState(EVERY_STATE); michael@0: if (base::win::GetVersion() >= base::win::VERSION_XP) michael@0: EXPECT_EQ(SBOX_TEST_FAILED, runner.RunTest(L"PolicyTargetTest_steal")); michael@0: } michael@0: michael@0: TEST(PolicyTargetTest, OpenThreadToken) { michael@0: TestRunner runner; michael@0: if (base::win::GetVersion() >= base::win::VERSION_XP) { michael@0: runner.SetTestState(BEFORE_REVERT); michael@0: EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token2")); michael@0: } michael@0: michael@0: runner.SetTestState(AFTER_REVERT); michael@0: EXPECT_EQ(ERROR_NO_TOKEN, runner.RunTest(L"PolicyTargetTest_token2")); michael@0: } michael@0: michael@0: TEST(PolicyTargetTest, OpenThreadTokenEx) { michael@0: TestRunner runner; michael@0: if (base::win::GetVersion() < base::win::VERSION_XP) michael@0: return; michael@0: michael@0: runner.SetTestState(BEFORE_REVERT); michael@0: EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token3")); michael@0: michael@0: runner.SetTestState(AFTER_REVERT); michael@0: EXPECT_EQ(ERROR_NO_TOKEN, runner.RunTest(L"PolicyTargetTest_token3")); michael@0: } michael@0: michael@0: TEST(PolicyTargetTest, OpenThread) { michael@0: TestRunner runner; michael@0: EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_thread")) << michael@0: "Opens the current thread"; michael@0: michael@0: EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_thread2")) << michael@0: "Creates a new thread and opens it"; michael@0: } michael@0: michael@0: TEST(PolicyTargetTest, OpenProcess) { michael@0: TestRunner runner; michael@0: EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_process")) << michael@0: "Opens a process"; michael@0: } michael@0: michael@0: // Launches the app in the sandbox and ask it to wait in an michael@0: // infinite loop. Waits for 2 seconds and then check if the michael@0: // desktop associated with the app thread is not the same as the michael@0: // current desktop. michael@0: TEST(PolicyTargetTest, DesktopPolicy) { michael@0: BrokerServices* broker = GetBroker(); michael@0: michael@0: // Precreate the desktop. michael@0: TargetPolicy* temp_policy = broker->CreatePolicy(); michael@0: temp_policy->CreateAlternateDesktop(false); michael@0: temp_policy->Release(); michael@0: michael@0: ASSERT_TRUE(broker != NULL); michael@0: michael@0: // Get the path to the sandboxed app. michael@0: wchar_t prog_name[MAX_PATH]; michael@0: GetModuleFileNameW(NULL, prog_name, MAX_PATH); michael@0: michael@0: std::wstring arguments(L"\""); michael@0: arguments += prog_name; michael@0: arguments += L"\" -child 0 wait"; // Don't care about the "state" argument. michael@0: michael@0: // Launch the app. michael@0: ResultCode result = SBOX_ALL_OK; michael@0: base::win::ScopedProcessInformation target; michael@0: michael@0: TargetPolicy* policy = broker->CreatePolicy(); michael@0: policy->SetAlternateDesktop(false); michael@0: policy->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN); michael@0: result = broker->SpawnTarget(prog_name, arguments.c_str(), policy, michael@0: target.Receive()); michael@0: policy->Release(); michael@0: michael@0: EXPECT_EQ(SBOX_ALL_OK, result); michael@0: michael@0: EXPECT_EQ(1, ::ResumeThread(target.thread_handle())); michael@0: michael@0: EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(target.process_handle(), 2000)); michael@0: michael@0: EXPECT_NE(::GetThreadDesktop(target.thread_id()), michael@0: ::GetThreadDesktop(::GetCurrentThreadId())); michael@0: michael@0: std::wstring desktop_name = policy->GetAlternateDesktop(); michael@0: HDESK desk = ::OpenDesktop(desktop_name.c_str(), 0, FALSE, DESKTOP_ENUMERATE); michael@0: EXPECT_TRUE(NULL != desk); michael@0: EXPECT_TRUE(::CloseDesktop(desk)); michael@0: EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0)); michael@0: michael@0: ::WaitForSingleObject(target.process_handle(), INFINITE); michael@0: michael@0: // Close the desktop handle. michael@0: temp_policy = broker->CreatePolicy(); michael@0: temp_policy->DestroyAlternateDesktop(); michael@0: temp_policy->Release(); michael@0: michael@0: // Make sure the desktop does not exist anymore. michael@0: desk = ::OpenDesktop(desktop_name.c_str(), 0, FALSE, DESKTOP_ENUMERATE); michael@0: EXPECT_TRUE(NULL == desk); michael@0: } michael@0: michael@0: // Launches the app in the sandbox and ask it to wait in an michael@0: // infinite loop. Waits for 2 seconds and then check if the michael@0: // winstation associated with the app thread is not the same as the michael@0: // current desktop. michael@0: TEST(PolicyTargetTest, WinstaPolicy) { michael@0: BrokerServices* broker = GetBroker(); michael@0: michael@0: // Precreate the desktop. michael@0: TargetPolicy* temp_policy = broker->CreatePolicy(); michael@0: temp_policy->CreateAlternateDesktop(true); michael@0: temp_policy->Release(); michael@0: michael@0: ASSERT_TRUE(broker != NULL); michael@0: michael@0: // Get the path to the sandboxed app. michael@0: wchar_t prog_name[MAX_PATH]; michael@0: GetModuleFileNameW(NULL, prog_name, MAX_PATH); michael@0: michael@0: std::wstring arguments(L"\""); michael@0: arguments += prog_name; michael@0: arguments += L"\" -child 0 wait"; // Don't care about the "state" argument. michael@0: michael@0: // Launch the app. michael@0: ResultCode result = SBOX_ALL_OK; michael@0: base::win::ScopedProcessInformation target; michael@0: michael@0: TargetPolicy* policy = broker->CreatePolicy(); michael@0: policy->SetAlternateDesktop(true); michael@0: policy->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN); michael@0: result = broker->SpawnTarget(prog_name, arguments.c_str(), policy, michael@0: target.Receive()); michael@0: policy->Release(); michael@0: michael@0: EXPECT_EQ(SBOX_ALL_OK, result); michael@0: michael@0: EXPECT_EQ(1, ::ResumeThread(target.thread_handle())); michael@0: michael@0: EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(target.process_handle(), 2000)); michael@0: michael@0: EXPECT_NE(::GetThreadDesktop(target.thread_id()), michael@0: ::GetThreadDesktop(::GetCurrentThreadId())); michael@0: michael@0: std::wstring desktop_name = policy->GetAlternateDesktop(); michael@0: ASSERT_FALSE(desktop_name.empty()); michael@0: michael@0: // Make sure there is a backslash, for the window station name. michael@0: EXPECT_NE(desktop_name.find_first_of(L'\\'), std::wstring::npos); michael@0: michael@0: // Isolate the desktop name. michael@0: desktop_name = desktop_name.substr(desktop_name.find_first_of(L'\\') + 1); michael@0: michael@0: HDESK desk = ::OpenDesktop(desktop_name.c_str(), 0, FALSE, DESKTOP_ENUMERATE); michael@0: // This should fail if the desktop is really on another window station. michael@0: EXPECT_FALSE(NULL != desk); michael@0: EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0)); michael@0: michael@0: ::WaitForSingleObject(target.process_handle(), INFINITE); michael@0: michael@0: // Close the desktop handle. michael@0: temp_policy = broker->CreatePolicy(); michael@0: temp_policy->DestroyAlternateDesktop(); michael@0: temp_policy->Release(); michael@0: } michael@0: michael@0: } // namespace sandbox