| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <windows.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/format_macros.h" |
| #include "base/path_service.h" |
| #include "base/rand_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/win/security_descriptor.h" |
| #include "base/win/security_util.h" |
| #include "base/win/sid.h" |
| #include "sandbox/features.h" |
| #include "sandbox/win/src/app_container_base.h" |
| #include "sandbox/win/src/security_capabilities.h" |
| #include "sandbox/win/src/win_utils.h" |
| #include "sandbox/win/tests/common/test_utils.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace sandbox { |
| |
| namespace { |
| |
| bool ValidSecurityCapabilities( |
| PSECURITY_CAPABILITIES security_capabilities, |
| const base::win::Sid& package_sid, |
| const std::vector<base::win::Sid>& capabilities) { |
| if (!security_capabilities) |
| return false; |
| |
| if (!package_sid.Equal(security_capabilities->AppContainerSid)) { |
| return false; |
| } |
| |
| // If empty then count and list of capabilities should be 0 and nullptr. |
| if (capabilities.empty() && !security_capabilities->CapabilityCount && |
| !security_capabilities->Capabilities) { |
| return true; |
| } |
| |
| if (!security_capabilities->Capabilities) |
| return false; |
| |
| if (security_capabilities->CapabilityCount != capabilities.size()) |
| return false; |
| |
| for (DWORD index = 0; index < security_capabilities->CapabilityCount; |
| ++index) { |
| if (!capabilities[index].Equal( |
| security_capabilities->Capabilities[index].Sid)) { |
| return false; |
| } |
| if (security_capabilities->Capabilities[index].Attributes != |
| SE_GROUP_ENABLED) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool CompareSidVectors(const std::vector<base::win::Sid>& left, |
| const std::vector<base::win::Sid>& right) { |
| if (left.size() != right.size()) |
| return false; |
| auto left_interator = left.cbegin(); |
| auto right_interator = right.cbegin(); |
| while (left_interator != left.cend()) { |
| if (*left_interator != *right_interator) |
| return false; |
| ++left_interator; |
| ++right_interator; |
| } |
| return true; |
| } |
| |
| bool GetProfilePath(const std::wstring& package_name, |
| base::FilePath* profile_path) { |
| base::FilePath local_app_data; |
| if (!base::PathService::Get(base::DIR_LOCAL_APP_DATA, &local_app_data)) |
| return false; |
| *profile_path = local_app_data.Append(L"Packages").Append(package_name); |
| return true; |
| } |
| |
| bool ProfileExist(const std::wstring& package_name) { |
| base::FilePath profile_path; |
| if (!GetProfilePath(package_name, &profile_path)) |
| return false; |
| return base::PathExists(profile_path); |
| } |
| |
| std::wstring GenerateRandomPackageName() { |
| return base::ASCIIToWide(base::StringPrintf( |
| "%016" PRIX64 "%016" PRIX64, base::RandUint64(), base::RandUint64())); |
| } |
| |
| base::win::SecurityDescriptor::SelfRelative CreateSdWithSid( |
| const base::win::Sid& sid) { |
| base::win::SecurityDescriptor sd; |
| CHECK(sd.SetDaclEntry(base::win::WellKnownSid::kWorld, |
| base::win::SecurityAccessMode::kGrant, GENERIC_ALL, 0)); |
| CHECK(sd.SetDaclEntry(sid, base::win::SecurityAccessMode::kGrant, GENERIC_ALL, |
| 0)); |
| return *sd.ToSelfRelative(); |
| } |
| |
| base::win::SecurityDescriptor::SelfRelative CreateSdWithSid( |
| base::win::WellKnownSid known_sid) { |
| return CreateSdWithSid(base::win::Sid(known_sid)); |
| } |
| |
| void AccessCheckFile(AppContainer* container, |
| const base::FilePath& path, |
| const base::win::Sid& sid, |
| DWORD desired_access, |
| DWORD expected_access, |
| BOOL expected_status) { |
| auto sd = CreateSdWithSid(sid); |
| SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), sd.get(), FALSE}; |
| base::win::ScopedHandle file_handle(::CreateFile( |
| path.value().c_str(), DELETE, FILE_SHARE_READ | FILE_SHARE_DELETE, &sa, |
| CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, nullptr)); |
| |
| ASSERT_TRUE(file_handle.is_valid()); |
| DWORD granted_access; |
| BOOL access_status; |
| ASSERT_TRUE(container->AccessCheck( |
| path.value().c_str(), base::win::SecurityObjectType::kFile, |
| desired_access, &granted_access, &access_status)); |
| ASSERT_EQ(expected_status, access_status); |
| if (access_status) |
| ASSERT_EQ(expected_access, granted_access); |
| } |
| |
| void AccessCheckFile(AppContainer* container, |
| const base::FilePath& path, |
| base::win::WellKnownSid known_sid, |
| DWORD desired_access, |
| DWORD expected_access, |
| BOOL expected_status) { |
| AccessCheckFile(container, path, base::win::Sid(known_sid), desired_access, |
| expected_access, expected_status); |
| } |
| |
| void AccessCheckFile(AppContainer* container, |
| const base::FilePath& path, |
| base::win::WellKnownCapability known_cap, |
| DWORD desired_access, |
| DWORD expected_access, |
| BOOL expected_status) { |
| AccessCheckFile(container, path, base::win::Sid(known_cap), desired_access, |
| expected_access, expected_status); |
| } |
| |
| void CheckDaclForPackageSid(HANDLE token, |
| const base::win::Sid& package_sid, |
| bool package_sid_required) { |
| auto sd = *base::win::SecurityDescriptor::FromHandle( |
| token, base::win::SecurityObjectType::kKernel, DACL_SECURITY_INFORMATION); |
| |
| EXPECT_EQ(package_sid_required, |
| IsSidInDacl(*sd.dacl(), true, TOKEN_ALL_ACCESS, package_sid)); |
| EXPECT_NE(package_sid_required, |
| IsSidInDacl(*sd.dacl(), true, TOKEN_ALL_ACCESS, |
| base::win::Sid( |
| base::win::WellKnownSid::kAllApplicationPackages))); |
| } |
| |
| void CheckLowBoxToken(AppContainerBase* container, |
| const base::win::AccessToken& base_token, |
| bool impersonation, |
| size_t expected_cap_count) { |
| std::optional<base::win::AccessToken> token = |
| impersonation ? container->BuildImpersonationToken(base_token) |
| : container->BuildPrimaryToken(base_token); |
| ASSERT_TRUE(token); |
| EXPECT_EQ(token->User(), base_token.User()); |
| EXPECT_EQ(base::win::GetGrantedAccess(token->get()), DWORD{TOKEN_ALL_ACCESS}); |
| EXPECT_TRUE(token->IsAppContainer()); |
| EXPECT_EQ(impersonation, token->IsImpersonation()); |
| EXPECT_FALSE(token->IsIdentification()); |
| EXPECT_EQ(token->AppContainerSid(), container->GetPackageSid()); |
| const std::vector<base::win::Sid>& check_capabilities = |
| impersonation ? container->GetImpersonationCapabilities() |
| : container->GetCapabilities(); |
| auto capabilities = token->Capabilities(); |
| ASSERT_EQ(capabilities.size(), check_capabilities.size()); |
| EXPECT_EQ(expected_cap_count, capabilities.size()); |
| for (size_t index = 0; index < capabilities.size(); ++index) { |
| EXPECT_EQ(capabilities[index].GetAttributes(), DWORD{SE_GROUP_ENABLED}); |
| EXPECT_EQ(capabilities[index].GetSid(), check_capabilities[index]); |
| } |
| CheckDaclForPackageSid(token->get(), container->GetPackageSid(), true); |
| } |
| |
| } // namespace |
| |
| TEST(AppContainerTest, SecurityCapabilities) { |
| if (!features::IsAppContainerSandboxSupported()) |
| return; |
| |
| // This isn't a valid package SID but it doesn't matter for this test. |
| base::win::Sid package_sid(base::win::WellKnownSid::kNull); |
| |
| std::vector<base::win::Sid> capabilities; |
| SecurityCapabilities no_capabilities(package_sid); |
| EXPECT_TRUE( |
| ValidSecurityCapabilities(&no_capabilities, package_sid, capabilities)); |
| |
| capabilities.emplace_back(base::win::WellKnownSid::kWorld); |
| SecurityCapabilities one_capability(package_sid, capabilities); |
| EXPECT_TRUE( |
| ValidSecurityCapabilities(&one_capability, package_sid, capabilities)); |
| |
| capabilities.emplace_back(base::win::WellKnownSid::kNetwork); |
| SecurityCapabilities two_capabilities(package_sid, capabilities); |
| EXPECT_TRUE( |
| ValidSecurityCapabilities(&two_capabilities, package_sid, capabilities)); |
| } |
| |
| TEST(AppContainerTest, CreateAndDeleteAppContainerProfile) { |
| if (!features::IsAppContainerSandboxSupported()) |
| return; |
| |
| std::wstring package_name = GenerateRandomPackageName(); |
| EXPECT_FALSE(ProfileExist(package_name)); |
| scoped_refptr<AppContainerBase> profile_container = |
| AppContainerBase::CreateProfile(package_name.c_str(), L"Name", |
| L"Description"); |
| ASSERT_NE(nullptr, profile_container.get()); |
| EXPECT_TRUE(ProfileExist(package_name)); |
| EXPECT_TRUE(AppContainerBase::Delete(package_name.c_str())); |
| EXPECT_FALSE(ProfileExist(package_name)); |
| } |
| |
| TEST(AppContainerTest, CreateAndOpenAppContainer) { |
| if (!features::IsAppContainerSandboxSupported()) |
| return; |
| |
| std::wstring package_name = GenerateRandomPackageName(); |
| EXPECT_FALSE(ProfileExist(package_name)); |
| scoped_refptr<AppContainerBase> profile_container = |
| AppContainerBase::CreateProfile(package_name.c_str(), L"Name", |
| L"Description"); |
| ASSERT_NE(nullptr, profile_container.get()); |
| EXPECT_TRUE(ProfileExist(package_name)); |
| scoped_refptr<AppContainerBase> open_container = |
| AppContainerBase::Open(package_name.c_str()); |
| ASSERT_NE(nullptr, open_container.get()); |
| EXPECT_TRUE(::EqualSid(profile_container->GetPackageSid().GetPSID(), |
| open_container->GetPackageSid().GetPSID())); |
| EXPECT_TRUE(AppContainerBase::Delete(package_name.c_str())); |
| EXPECT_FALSE(ProfileExist(package_name)); |
| scoped_refptr<AppContainerBase> open_container2 = |
| AppContainerBase::Open(package_name.c_str()); |
| EXPECT_FALSE(ProfileExist(package_name)); |
| } |
| |
| TEST(AppContainerTest, SetLowPrivilegeAppContainer) { |
| if (!features::IsAppContainerSandboxSupported()) |
| return; |
| std::wstring package_name = GenerateRandomPackageName(); |
| scoped_refptr<AppContainerBase> container = |
| AppContainerBase::Open(package_name.c_str()); |
| ASSERT_NE(nullptr, container.get()); |
| container->SetEnableLowPrivilegeAppContainer(true); |
| EXPECT_TRUE(container->GetEnableLowPrivilegeAppContainer()); |
| } |
| |
| TEST(AppContainerTest, OpenAppContainerAndGetSecurityCapabilities) { |
| if (!features::IsAppContainerSandboxSupported()) |
| return; |
| |
| std::wstring package_name = GenerateRandomPackageName(); |
| scoped_refptr<AppContainerBase> container = |
| AppContainerBase::Open(package_name.c_str()); |
| ASSERT_NE(nullptr, container.get()); |
| |
| std::vector<base::win::Sid> capabilities; |
| auto no_capabilities = container->GetSecurityCapabilities(); |
| ASSERT_TRUE(ValidSecurityCapabilities( |
| no_capabilities.get(), container->GetPackageSid(), capabilities)); |
| |
| container->AddCapability(L"FakeCapability"); |
| capabilities.push_back( |
| base::win::Sid::FromNamedCapability(L"FakeCapability")); |
| |
| container->AddCapability(base::win::WellKnownCapability::kInternetClient); |
| capabilities.emplace_back(base::win::WellKnownCapability::kInternetClient); |
| const wchar_t kSddlSid[] = L"S-1-15-3-1"; |
| ASSERT_TRUE(container->AddCapabilitySddl(kSddlSid)); |
| capabilities.push_back(*base::win::Sid::FromSddlString(kSddlSid)); |
| auto with_capabilities = container->GetSecurityCapabilities(); |
| ASSERT_TRUE(ValidSecurityCapabilities( |
| with_capabilities.get(), container->GetPackageSid(), capabilities)); |
| } |
| |
| TEST(AppContainerTest, AccessCheckFile) { |
| if (!features::IsAppContainerSandboxSupported()) |
| return; |
| |
| // We don't need a valid profile to do the access check tests. |
| std::wstring package_name = GenerateRandomPackageName(); |
| scoped_refptr<AppContainerBase> container = |
| AppContainerBase::Open(package_name.c_str()); |
| container->AddCapability(base::win::WellKnownCapability::kInternetClient); |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| base::FilePath path = temp_dir.GetPath().Append(package_name); |
| |
| AccessCheckFile(container.get(), path, base::win::WellKnownSid::kNull, |
| FILE_READ_DATA, 0, FALSE); |
| AccessCheckFile(container.get(), path, |
| base::win::WellKnownSid::kAllApplicationPackages, |
| FILE_READ_DATA, FILE_READ_DATA, TRUE); |
| AccessCheckFile(container.get(), path, container->GetPackageSid(), |
| FILE_READ_DATA, FILE_READ_DATA, TRUE); |
| AccessCheckFile(container.get(), path, |
| base::win::WellKnownCapability::kInternetClient, |
| FILE_READ_DATA, FILE_READ_DATA, TRUE); |
| |
| // Check mapping generic access rights. |
| AccessCheckFile(container.get(), path, |
| base::win::WellKnownSid::kAllApplicationPackages, |
| GENERIC_READ | GENERIC_EXECUTE, |
| FILE_GENERIC_READ | FILE_GENERIC_EXECUTE, TRUE); |
| if (!features::IsAppContainerSandboxSupported()) |
| return; |
| container->SetEnableLowPrivilegeAppContainer(true); |
| AccessCheckFile(container.get(), path, |
| base::win::WellKnownSid::kAllApplicationPackages, |
| FILE_READ_DATA, 0, FALSE); |
| AccessCheckFile(container.get(), path, |
| base::win::WellKnownSid::kAllRestrictedApplicationPackages, |
| FILE_READ_DATA, FILE_READ_DATA, TRUE); |
| } |
| |
| TEST(AppContainerTest, AccessCheckRegistry) { |
| if (!features::IsAppContainerSandboxSupported()) |
| return; |
| |
| // We don't need a valid profile to do the access check tests. |
| std::wstring package_name = GenerateRandomPackageName(); |
| scoped_refptr<AppContainerBase> container = |
| AppContainerBase::Open(package_name.c_str()); |
| // Ensure the key doesn't exist. |
| RegDeleteKey(HKEY_CURRENT_USER, package_name.c_str()); |
| auto sd = CreateSdWithSid(base::win::WellKnownSid::kAllApplicationPackages); |
| SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), sd.get(), FALSE}; |
| HKEY key_handle; |
| ASSERT_EQ(ERROR_SUCCESS, |
| RegCreateKeyEx(HKEY_CURRENT_USER, package_name.c_str(), 0, nullptr, |
| REG_OPTION_VOLATILE, KEY_ALL_ACCESS, &sa, |
| &key_handle, nullptr)); |
| base::win::ScopedHandle key(key_handle); |
| std::wstring key_name = L"CURRENT_USER\\"; |
| key_name += package_name; |
| DWORD granted_access; |
| BOOL access_status; |
| |
| ASSERT_TRUE(container->AccessCheck( |
| key_name.c_str(), base::win::SecurityObjectType::kRegistry, |
| KEY_QUERY_VALUE, &granted_access, &access_status)); |
| ASSERT_TRUE(access_status); |
| ASSERT_EQ(DWORD{KEY_QUERY_VALUE}, granted_access); |
| RegDeleteKey(HKEY_CURRENT_USER, package_name.c_str()); |
| } |
| |
| TEST(AppContainerTest, ImpersonationCapabilities) { |
| if (!features::IsAppContainerSandboxSupported()) |
| return; |
| |
| std::wstring package_name = GenerateRandomPackageName(); |
| scoped_refptr<AppContainerBase> container = |
| AppContainerBase::Open(package_name.c_str()); |
| ASSERT_NE(nullptr, container.get()); |
| |
| std::vector<base::win::Sid> capabilities; |
| std::vector<base::win::Sid> impersonation_capabilities; |
| |
| container->AddCapability(base::win::WellKnownCapability::kInternetClient); |
| capabilities.emplace_back(base::win::WellKnownCapability::kInternetClient); |
| impersonation_capabilities.emplace_back( |
| base::win::WellKnownCapability::kInternetClient); |
| |
| ASSERT_TRUE(CompareSidVectors(container->GetCapabilities(), capabilities)); |
| ASSERT_TRUE(CompareSidVectors(container->GetImpersonationCapabilities(), |
| impersonation_capabilities)); |
| |
| container->AddImpersonationCapability( |
| base::win::WellKnownCapability::kPrivateNetworkClientServer); |
| impersonation_capabilities.emplace_back( |
| base::win::WellKnownCapability::kPrivateNetworkClientServer); |
| |
| container->AddImpersonationCapability(L"FakeCapability"); |
| impersonation_capabilities.push_back( |
| base::win::Sid::FromNamedCapability(L"FakeCapability")); |
| |
| const wchar_t kSddlSid[] = L"S-1-15-3-1"; |
| ASSERT_TRUE(container->AddImpersonationCapabilitySddl(kSddlSid)); |
| impersonation_capabilities.push_back( |
| *base::win::Sid::FromSddlString(kSddlSid)); |
| ASSERT_TRUE(CompareSidVectors(container->GetCapabilities(), capabilities)); |
| ASSERT_TRUE(CompareSidVectors(container->GetImpersonationCapabilities(), |
| impersonation_capabilities)); |
| } |
| |
| TEST(AppContainerTest, BuildImpersonationToken) { |
| if (!features::IsAppContainerSandboxSupported()) { |
| return; |
| } |
| std::optional<base::win::AccessToken> base_token = |
| base::win::AccessToken::FromCurrentProcess( |
| /*impersonation=*/false, TOKEN_DUPLICATE); |
| ASSERT_TRUE(base_token); |
| std::wstring package_name = GenerateRandomPackageName(); |
| scoped_refptr<AppContainerBase> container = |
| AppContainerBase::Open(package_name.c_str()); |
| ASSERT_NE(nullptr, container.get()); |
| |
| CheckLowBoxToken(container.get(), *base_token, true, 0); |
| container->AddCapability(base::win::WellKnownCapability::kInternetClient); |
| container->AddImpersonationCapability( |
| base::win::WellKnownCapability::kPrivateNetworkClientServer); |
| CheckLowBoxToken(container.get(), *base_token, true, 2); |
| } |
| |
| TEST(AppContainerTest, BuildPrimaryToken) { |
| if (!features::IsAppContainerSandboxSupported()) { |
| return; |
| } |
| std::optional<base::win::AccessToken> base_token = |
| base::win::AccessToken::FromCurrentProcess( |
| /*impersonation=*/false, TOKEN_DUPLICATE); |
| ASSERT_TRUE(base_token); |
| std::wstring package_name = GenerateRandomPackageName(); |
| scoped_refptr<AppContainerBase> container = |
| AppContainerBase::Open(package_name.c_str()); |
| ASSERT_NE(nullptr, container.get()); |
| |
| CheckLowBoxToken(container.get(), *base_token, false, 0); |
| container->AddCapability(base::win::WellKnownCapability::kInternetClient); |
| container->AddImpersonationCapability( |
| base::win::WellKnownCapability::kPrivateNetworkClientServer); |
| CheckLowBoxToken(container.get(), *base_token, false, 1); |
| } |
| |
| } // namespace sandbox |