blob: d6e3d0e94cb5929ebf18ef433d43c433900b417e [file] [log] [blame]
// Copyright (c) 2012 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.
#include <windows.h>
#include <sddl.h>
#include <memory>
#include <string>
#include <vector>
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "base/win/scoped_handle.h"
#include "base/win/scoped_process_information.h"
#include "base/win/windows_version.h"
#include "sandbox/win/src/app_container_profile_base.h"
#include "sandbox/win/src/sync_policy_test.h"
#include "sandbox/win/src/win_utils.h"
#include "sandbox/win/tests/common/controller.h"
#include "sandbox/win/tests/common/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace sandbox {
namespace {
const wchar_t kAppContainerSid[] =
L"S-1-15-2-3251537155-1984446955-2931258699-841473695-1938553385-"
L"924012148-2839372144";
std::wstring GenerateRandomPackageName() {
return base::StringPrintf(L"%016lX%016lX", base::RandUint64(),
base::RandUint64());
}
const char* TokenTypeToName(TOKEN_TYPE token_type) {
return token_type == ::TokenPrimary ? "Primary Token" : "Impersonation Token";
}
void CheckToken(HANDLE token,
TOKEN_TYPE token_type,
PSECURITY_CAPABILITIES security_capabilities,
BOOL restricted) {
ASSERT_EQ(restricted, ::IsTokenRestricted(token))
<< TokenTypeToName(token_type);
DWORD appcontainer;
DWORD return_length;
ASSERT_TRUE(::GetTokenInformation(token, ::TokenIsAppContainer, &appcontainer,
sizeof(appcontainer), &return_length))
<< TokenTypeToName(token_type);
ASSERT_TRUE(appcontainer) << TokenTypeToName(token_type);
TOKEN_TYPE token_type_real;
ASSERT_TRUE(::GetTokenInformation(token, ::TokenType, &token_type_real,
sizeof(token_type_real), &return_length))
<< TokenTypeToName(token_type);
ASSERT_EQ(token_type_real, token_type) << TokenTypeToName(token_type);
if (token_type == ::TokenImpersonation) {
SECURITY_IMPERSONATION_LEVEL imp_level;
ASSERT_TRUE(::GetTokenInformation(token, ::TokenImpersonationLevel,
&imp_level, sizeof(imp_level),
&return_length))
<< TokenTypeToName(token_type);
ASSERT_EQ(imp_level, ::SecurityImpersonation)
<< TokenTypeToName(token_type);
}
std::unique_ptr<Sid> package_sid;
ASSERT_TRUE(GetTokenAppContainerSid(token, &package_sid))
<< TokenTypeToName(token_type);
EXPECT_TRUE(::EqualSid(security_capabilities->AppContainerSid,
package_sid->GetPSID()))
<< TokenTypeToName(token_type);
std::vector<SidAndAttributes> capabilities;
ASSERT_TRUE(GetTokenGroups(token, ::TokenCapabilities, &capabilities))
<< TokenTypeToName(token_type);
ASSERT_EQ(capabilities.size(), security_capabilities->CapabilityCount)
<< TokenTypeToName(token_type);
for (size_t index = 0; index < capabilities.size(); ++index) {
EXPECT_EQ(capabilities[index].GetAttributes(),
security_capabilities->Capabilities[index].Attributes)
<< TokenTypeToName(token_type);
EXPECT_TRUE(::EqualSid(capabilities[index].GetPSID(),
security_capabilities->Capabilities[index].Sid))
<< TokenTypeToName(token_type);
}
}
void CheckProcessToken(HANDLE process,
PSECURITY_CAPABILITIES security_capabilities,
bool restricted) {
HANDLE token_handle;
ASSERT_TRUE(::OpenProcessToken(process, TOKEN_ALL_ACCESS, &token_handle));
base::win::ScopedHandle token(token_handle);
CheckToken(token_handle, ::TokenPrimary, security_capabilities, restricted);
}
void CheckThreadToken(HANDLE thread,
PSECURITY_CAPABILITIES security_capabilities,
bool restricted) {
HANDLE token_handle;
ASSERT_TRUE(::OpenThreadToken(thread, TOKEN_ALL_ACCESS, TRUE, &token_handle));
base::win::ScopedHandle token(token_handle);
CheckToken(token_handle, ::TokenImpersonation, security_capabilities,
restricted);
}
// Check for LPAC using an access check. We could query for a security attribute
// but that's undocumented and has the potential to change.
void CheckLpacToken(HANDLE process) {
HANDLE token_handle;
ASSERT_TRUE(::OpenProcessToken(process, TOKEN_ALL_ACCESS, &token_handle));
base::win::ScopedHandle token(token_handle);
ASSERT_TRUE(
::DuplicateToken(token.Get(), ::SecurityImpersonation, &token_handle));
token.Set(token_handle);
PSECURITY_DESCRIPTOR security_desc_ptr;
// AC is AllPackages, S-1-15-2-2 is AllRestrictedPackages. An LPAC token
// will get granted access of 2, where as a normal AC token will get 3.
ASSERT_TRUE(::ConvertStringSecurityDescriptorToSecurityDescriptor(
L"O:SYG:SYD:(A;;0x3;;;WD)(A;;0x1;;;AC)(A;;0x2;;;S-1-15-2-2)",
SDDL_REVISION_1, &security_desc_ptr, nullptr));
std::unique_ptr<void, LocalFreeDeleter> security_desc(security_desc_ptr);
GENERIC_MAPPING generic_mapping = {};
PRIVILEGE_SET priv_set = {};
DWORD priv_set_length = sizeof(PRIVILEGE_SET);
DWORD granted_access;
BOOL access_status;
ASSERT_TRUE(::AccessCheck(security_desc_ptr, token.Get(), MAXIMUM_ALLOWED,
&generic_mapping, &priv_set, &priv_set_length,
&granted_access, &access_status));
ASSERT_TRUE(access_status);
ASSERT_EQ(DWORD{2}, granted_access);
}
class AppContainerProfileTest : public ::testing::Test {
public:
void SetUp() override {
if (base::win::GetVersion() < base::win::Version::WIN8)
return;
package_name_ = GenerateRandomPackageName();
broker_services_ = GetBroker();
policy_ = broker_services_->CreatePolicy();
ASSERT_EQ(SBOX_ALL_OK,
policy_->AddAppContainerProfile(package_name_.c_str(), true));
// For testing purposes we known the base class so cast directly.
profile_ = static_cast<AppContainerProfileBase*>(
policy_->GetAppContainerProfile().get());
}
void TearDown() override {
if (scoped_process_info_.IsValid())
::TerminateProcess(scoped_process_info_.process_handle(), 0);
if (profile_)
AppContainerProfileBase::Delete(package_name_.c_str());
}
protected:
void CreateProcess() {
// Get the path to the sandboxed app.
wchar_t prog_name[MAX_PATH] = {};
ASSERT_NE(DWORD{0}, ::GetModuleFileNameW(nullptr, prog_name, MAX_PATH));
PROCESS_INFORMATION process_info = {};
ResultCode last_warning = SBOX_ALL_OK;
DWORD last_error = 0;
ResultCode result = broker_services_->SpawnTarget(
prog_name, prog_name, policy_, &last_warning, &last_error,
&process_info);
ASSERT_EQ(SBOX_ALL_OK, result) << "Last Error: " << last_error;
scoped_process_info_.Set(process_info);
}
std::wstring package_name_;
BrokerServices* broker_services_;
scoped_refptr<AppContainerProfileBase> profile_;
scoped_refptr<TargetPolicy> policy_;
base::win::ScopedProcessInformation scoped_process_info_;
};
} // namespace
TEST(AppContainerTest, DenyOpenEventForLowBox) {
if (base::win::GetVersion() < base::win::Version::WIN8)
return;
TestRunner runner(JOB_UNPROTECTED, USER_UNPROTECTED, USER_UNPROTECTED);
EXPECT_EQ(SBOX_ALL_OK, runner.GetPolicy()->SetLowBox(kAppContainerSid));
// Run test once, this ensures the app container directory exists, we
// ignore the result.
runner.RunTest(L"Event_Open f test");
std::wstring event_name = L"AppContainerNamedObjects\\";
event_name += kAppContainerSid;
event_name += L"\\test";
base::win::ScopedHandle event(
::CreateEvent(nullptr, false, false, event_name.c_str()));
ASSERT_TRUE(event.IsValid());
EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_Open f test"));
}
TEST_F(AppContainerProfileTest, CheckIncompatibleOptions) {
if (!profile_)
return;
EXPECT_EQ(SBOX_ERROR_BAD_PARAMS,
policy_->SetIntegrityLevel(INTEGRITY_LEVEL_UNTRUSTED));
EXPECT_EQ(SBOX_ERROR_BAD_PARAMS, policy_->SetLowBox(kAppContainerSid));
EXPECT_EQ(SBOX_ERROR_BAD_PARAMS,
policy_->SetProcessMitigations(MITIGATION_HEAP_TERMINATE));
}
TEST_F(AppContainerProfileTest, NoCapabilities) {
if (!profile_)
return;
policy_->SetTokenLevel(USER_UNPROTECTED, USER_UNPROTECTED);
policy_->SetJobLevel(JOB_NONE, 0);
CreateProcess();
auto security_capabilities = profile_->GetSecurityCapabilities();
CheckProcessToken(scoped_process_info_.process_handle(),
security_capabilities.get(), FALSE);
CheckThreadToken(scoped_process_info_.thread_handle(),
security_capabilities.get(), FALSE);
}
TEST_F(AppContainerProfileTest, NoCapabilitiesRestricted) {
if (!profile_)
return;
policy_->SetTokenLevel(USER_LOCKDOWN, USER_RESTRICTED_SAME_ACCESS);
policy_->SetJobLevel(JOB_NONE, 0);
CreateProcess();
auto security_capabilities = profile_->GetSecurityCapabilities();
CheckProcessToken(scoped_process_info_.process_handle(),
security_capabilities.get(), TRUE);
CheckThreadToken(scoped_process_info_.thread_handle(),
security_capabilities.get(), TRUE);
}
TEST_F(AppContainerProfileTest, WithCapabilities) {
if (!profile_)
return;
profile_->AddCapability(kInternetClient);
profile_->AddCapability(kInternetClientServer);
policy_->SetTokenLevel(USER_UNPROTECTED, USER_UNPROTECTED);
policy_->SetJobLevel(JOB_NONE, 0);
CreateProcess();
auto security_capabilities = profile_->GetSecurityCapabilities();
CheckProcessToken(scoped_process_info_.process_handle(),
security_capabilities.get(), FALSE);
CheckThreadToken(scoped_process_info_.thread_handle(),
security_capabilities.get(), FALSE);
}
TEST_F(AppContainerProfileTest, WithCapabilitiesRestricted) {
if (!profile_)
return;
profile_->AddCapability(kInternetClient);
profile_->AddCapability(kInternetClientServer);
policy_->SetTokenLevel(USER_LOCKDOWN, USER_RESTRICTED_SAME_ACCESS);
policy_->SetJobLevel(JOB_NONE, 0);
CreateProcess();
auto security_capabilities = profile_->GetSecurityCapabilities();
CheckProcessToken(scoped_process_info_.process_handle(),
security_capabilities.get(), TRUE);
CheckThreadToken(scoped_process_info_.thread_handle(),
security_capabilities.get(), TRUE);
}
TEST_F(AppContainerProfileTest, WithImpersonationCapabilities) {
if (!profile_)
return;
profile_->AddCapability(kInternetClient);
profile_->AddCapability(kInternetClientServer);
profile_->AddImpersonationCapability(kPrivateNetworkClientServer);
profile_->AddImpersonationCapability(kPicturesLibrary);
policy_->SetTokenLevel(USER_UNPROTECTED, USER_UNPROTECTED);
policy_->SetJobLevel(JOB_NONE, 0);
CreateProcess();
auto security_capabilities = profile_->GetSecurityCapabilities();
CheckProcessToken(scoped_process_info_.process_handle(),
security_capabilities.get(), FALSE);
SecurityCapabilities impersonation_security_capabilities(
profile_->GetPackageSid(), profile_->GetImpersonationCapabilities());
CheckThreadToken(scoped_process_info_.thread_handle(),
&impersonation_security_capabilities, FALSE);
}
TEST_F(AppContainerProfileTest, NoCapabilitiesLPAC) {
if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
return;
profile_->SetEnableLowPrivilegeAppContainer(true);
policy_->SetTokenLevel(USER_UNPROTECTED, USER_UNPROTECTED);
policy_->SetJobLevel(JOB_NONE, 0);
CreateProcess();
auto security_capabilities = profile_->GetSecurityCapabilities();
CheckProcessToken(scoped_process_info_.process_handle(),
security_capabilities.get(), FALSE);
CheckThreadToken(scoped_process_info_.thread_handle(),
security_capabilities.get(), FALSE);
CheckLpacToken(scoped_process_info_.process_handle());
}
} // namespace sandbox