| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Integration tests for restricted tokens. |
| |
| #include <stddef.h> |
| #include <string> |
| |
| #include "base/strings/stringprintf.h" |
| #include "base/win/access_token.h" |
| #include "base/win/scoped_handle.h" |
| #include "sandbox/win/src/sandbox.h" |
| #include "sandbox/win/src/sandbox_factory.h" |
| #include "sandbox/win/src/target_services.h" |
| #include "sandbox/win/tests/common/controller.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace sandbox { |
| |
| namespace { |
| |
| int RunOpenProcessTest(bool unsandboxed, |
| bool lockdown_dacl, |
| DWORD access_mask) { |
| TestRunner runner(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); |
| runner.GetPolicy()->SetDelayedIntegrityLevel(INTEGRITY_LEVEL_UNTRUSTED); |
| runner.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); |
| if (lockdown_dacl) |
| runner.GetPolicy()->SetLockdownDefaultDacl(); |
| runner.SetAsynchronous(true); |
| // This spins up a renderer level process, we don't care about the result. |
| runner.RunTest(L"IntegrationTestsTest_args 1"); |
| |
| TestRunner runner2(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LIMITED); |
| runner2.GetPolicy()->SetDelayedIntegrityLevel(INTEGRITY_LEVEL_LOW); |
| runner2.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); |
| runner2.SetUnsandboxed(unsandboxed); |
| return runner2.RunTest( |
| base::StringPrintf(L"RestrictedTokenTest_openprocess %d 0x%08X", |
| runner.process_id(), access_mask) |
| .c_str()); |
| } |
| |
| int RunRestrictedOpenProcessTest(bool unsandboxed, |
| bool lockdown_dacl, |
| DWORD access_mask) { |
| TestRunner runner(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LIMITED); |
| runner.GetPolicy()->SetDelayedIntegrityLevel(INTEGRITY_LEVEL_LOW); |
| runner.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); |
| if (lockdown_dacl) { |
| runner.GetPolicy()->SetLockdownDefaultDacl(); |
| runner.GetPolicy()->AddRestrictingRandomSid(); |
| } |
| runner.SetAsynchronous(true); |
| // This spins up a GPU level process, we don't care about the result. |
| runner.RunTest(L"IntegrationTestsTest_args 1"); |
| |
| TestRunner runner2(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LIMITED); |
| runner2.GetPolicy()->SetDelayedIntegrityLevel(INTEGRITY_LEVEL_LOW); |
| runner2.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); |
| runner2.SetUnsandboxed(unsandboxed); |
| return runner2.RunTest( |
| base::StringPrintf(L"RestrictedTokenTest_openprocess %d 0x%08X", |
| runner.process_id(), access_mask) |
| .c_str()); |
| } |
| |
| int RunRestrictedSelfOpenProcessTest(bool add_random_sid, DWORD access_mask) { |
| TestRunner runner(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LIMITED); |
| runner.GetPolicy()->SetDelayedIntegrityLevel(INTEGRITY_LEVEL_LOW); |
| runner.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); |
| runner.GetPolicy()->SetLockdownDefaultDacl(); |
| if (add_random_sid) |
| runner.GetPolicy()->AddRestrictingRandomSid(); |
| |
| return runner.RunTest( |
| base::StringPrintf(L"RestrictedTokenTest_currentprocess_dup 0x%08X", |
| access_mask) |
| .c_str()); |
| } |
| |
| } // namespace |
| |
| // Opens a process based on a PID and access mask passed on the command line. |
| // Returns SBOX_TEST_SUCCEEDED if process opened successfully. |
| SBOX_TESTS_COMMAND int RestrictedTokenTest_openprocess(int argc, |
| wchar_t** argv) { |
| if (argc < 2) |
| return SBOX_TEST_NOT_FOUND; |
| DWORD pid = _wtoi(argv[0]); |
| if (pid == 0) |
| return SBOX_TEST_NOT_FOUND; |
| DWORD desired_access = wcstoul(argv[1], nullptr, 0); |
| base::win::ScopedHandle process_handle( |
| ::OpenProcess(desired_access, false, pid)); |
| if (process_handle.IsValid()) |
| return SBOX_TEST_SUCCEEDED; |
| |
| return SBOX_TEST_DENIED; |
| } |
| |
| // Opens a process through duplication. This is to avoid the OpenProcess hook. |
| SBOX_TESTS_COMMAND int RestrictedTokenTest_currentprocess_dup(int argc, |
| wchar_t** argv) { |
| if (argc < 1) |
| return SBOX_TEST_NOT_FOUND; |
| DWORD desired_access = wcstoul(argv[0], nullptr, 0); |
| |
| HANDLE dup_handle; |
| if (!::DuplicateHandle(::GetCurrentProcess(), ::GetCurrentProcess(), |
| ::GetCurrentProcess(), &dup_handle, 0, FALSE, 0)) { |
| return SBOX_TEST_FIRST_ERROR; |
| } |
| base::win::ScopedHandle process_handle(dup_handle); |
| if (::DuplicateHandle(::GetCurrentProcess(), process_handle.Get(), |
| ::GetCurrentProcess(), &dup_handle, desired_access, |
| FALSE, 0)) { |
| ::CloseHandle(dup_handle); |
| return SBOX_TEST_SUCCEEDED; |
| } |
| |
| if (::GetLastError() != ERROR_ACCESS_DENIED) |
| return SBOX_TEST_SECOND_ERROR; |
| return SBOX_TEST_DENIED; |
| } |
| |
| // Opens a the process token and checks if it's restricted. |
| SBOX_TESTS_COMMAND int RestrictedTokenTest_IsRestricted(int argc, |
| wchar_t** argv) { |
| absl::optional<base::win::AccessToken> token = |
| base::win::AccessToken::FromCurrentProcess(); |
| if (!token) |
| return SBOX_TEST_FIRST_ERROR; |
| return token->RestrictedSids().size() > 0 ? SBOX_TEST_SUCCEEDED |
| : SBOX_TEST_FAILED; |
| } |
| |
| TEST(RestrictedTokenTest, OpenLowPrivilegedProcess) { |
| // Test limited privilege to renderer open. |
| ASSERT_EQ(SBOX_TEST_SUCCEEDED, |
| RunOpenProcessTest(false, false, GENERIC_READ | GENERIC_WRITE)); |
| // Test limited privilege to renderer open with lockdowned DACL. |
| ASSERT_EQ(SBOX_TEST_DENIED, |
| RunOpenProcessTest(false, true, GENERIC_READ | GENERIC_WRITE)); |
| // Ensure we also can't get any access to the process. |
| ASSERT_EQ(SBOX_TEST_DENIED, RunOpenProcessTest(false, true, MAXIMUM_ALLOWED)); |
| // Also check for explicit owner allowed WRITE_DAC right. |
| ASSERT_EQ(SBOX_TEST_DENIED, RunOpenProcessTest(false, true, WRITE_DAC)); |
| // Ensure unsandboxed process can still open the renderer for all access. |
| ASSERT_EQ(SBOX_TEST_SUCCEEDED, |
| RunOpenProcessTest(true, true, PROCESS_ALL_ACCESS)); |
| } |
| |
| TEST(RestrictedTokenTest, CheckNonAdminRestricted) { |
| TestRunner runner(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, |
| USER_RESTRICTED_NON_ADMIN); |
| EXPECT_EQ(SBOX_TEST_SUCCEEDED, |
| runner.RunTest(L"RestrictedTokenTest_IsRestricted")); |
| } |
| |
| TEST(RestrictedTokenTest, OpenProcessSameSandboxRandomSid) { |
| // Test process to process open when not using random SID. |
| ASSERT_EQ(SBOX_TEST_SUCCEEDED, |
| RunRestrictedOpenProcessTest(false, false, GENERIC_ALL)); |
| // Test process to process open when using random SID. |
| ASSERT_EQ(SBOX_TEST_DENIED, |
| RunRestrictedOpenProcessTest(false, true, MAXIMUM_ALLOWED)); |
| // Test process to process open when not using random SID and opening from |
| // unsandboxed. |
| ASSERT_EQ(SBOX_TEST_SUCCEEDED, |
| RunRestrictedOpenProcessTest(true, false, GENERIC_ALL)); |
| // Test process to process open when using random SID and opening from |
| // unsandboxed. |
| ASSERT_EQ(SBOX_TEST_SUCCEEDED, |
| RunRestrictedOpenProcessTest(true, true, GENERIC_ALL)); |
| } |
| |
| TEST(RestrictedTokenTest, OpenProcessSelfRandomSid) { |
| // Test process can't open self when not using random SID. |
| ASSERT_EQ(SBOX_TEST_DENIED, |
| RunRestrictedSelfOpenProcessTest(false, PROCESS_ALL_ACCESS)); |
| // Test process can open self when using random SID. |
| ASSERT_EQ(SBOX_TEST_SUCCEEDED, |
| RunRestrictedSelfOpenProcessTest(true, PROCESS_ALL_ACCESS)); |
| } |
| |
| } // namespace sandbox |