blob: cf6f93aa18a7ccd2866905f03a1302c5cc827611 [file] [log] [blame]
// Copyright 2018 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 "sandbox/policy/win/sandbox_win.h"
#include <algorithm>
#include <string>
#include <vector>
#include <windows.h>
#include <sddl.h>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/ref_counted.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "base/scoped_native_library.h"
#include "base/test/scoped_feature_list.h"
#include "base/win/security_util.h"
#include "base/win/sid.h"
#include "base/win/windows_version.h"
#include "build/build_config.h"
#include "sandbox/policy/features.h"
#include "sandbox/policy/mojom/sandbox.mojom.h"
#include "sandbox/policy/sandbox_type.h"
#include "sandbox/policy/switches.h"
#include "sandbox/policy/win/sandbox_test_utils.h"
#include "sandbox/win/src/sandbox_factory.h"
#include "sandbox/win/src/sandbox_policy.h"
#include "sandbox/win/src/sandbox_policy_diagnostic.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Return;
namespace sandbox {
namespace policy {
namespace {
class TestTargetPolicy : public TargetPolicy {
public:
void AddRef() override {}
void Release() override {}
ResultCode SetTokenLevel(sandbox::TokenLevel initial,
TokenLevel lockdown) override {
return SBOX_ALL_OK;
}
TokenLevel GetInitialTokenLevel() const override { return TokenLevel{}; }
TokenLevel GetLockdownTokenLevel() const override { return TokenLevel{}; }
ResultCode SetJobLevel(sandbox::JobLevel job_level,
uint32_t ui_exceptions) override {
return SBOX_ALL_OK;
}
JobLevel GetJobLevel() const override { return sandbox::JobLevel{}; }
ResultCode SetJobMemoryLimit(size_t memory_limit) override {
return SBOX_ALL_OK;
}
ResultCode SetAlternateDesktop(bool alternate_winstation) override {
return SBOX_ALL_OK;
}
std::wstring GetAlternateDesktop() const override { return std::wstring(); }
ResultCode CreateAlternateDesktop(bool alternate_winstation) override {
return SBOX_ALL_OK;
}
void DestroyAlternateDesktop() override {}
ResultCode SetIntegrityLevel(IntegrityLevel level) override {
return SBOX_ALL_OK;
}
IntegrityLevel GetIntegrityLevel() const override { return IntegrityLevel{}; }
ResultCode SetDelayedIntegrityLevel(IntegrityLevel level) override {
return SBOX_ALL_OK;
}
ResultCode SetLowBox(const wchar_t* sid) override { return SBOX_ALL_OK; }
ResultCode SetProcessMitigations(MitigationFlags flags) override {
return SBOX_ALL_OK;
}
MitigationFlags GetProcessMitigations() override { return MitigationFlags{}; }
ResultCode SetDelayedProcessMitigations(MitigationFlags flags) override {
return SBOX_ALL_OK;
}
MitigationFlags GetDelayedProcessMitigations() const override {
return MitigationFlags{};
}
ResultCode SetDisconnectCsrss() override { return SBOX_ALL_OK; }
void SetStrictInterceptions() override {}
ResultCode SetStdoutHandle(HANDLE handle) override { return SBOX_ALL_OK; }
ResultCode SetStderrHandle(HANDLE handle) override { return SBOX_ALL_OK; }
ResultCode AddRule(SubSystem subsystem,
Semantics semantics,
const wchar_t* pattern) override {
return SBOX_ALL_OK;
}
ResultCode AddDllToUnload(const wchar_t* dll_name) override {
blocklisted_dlls_.push_back(dll_name);
return SBOX_ALL_OK;
}
ResultCode AddKernelObjectToClose(const wchar_t* handle_type,
const wchar_t* handle_name) override {
return SBOX_ALL_OK;
}
void AddHandleToShare(HANDLE handle) override {}
void SetLockdownDefaultDacl() override {}
void AddRestrictingRandomSid() override {}
ResultCode AddAppContainerProfile(const wchar_t* package_name,
bool create_profile) override {
if (create_profile) {
app_container_ =
AppContainerBase::CreateProfile(package_name, L"Sandbox", L"Sandbox");
} else {
app_container_ = AppContainerBase::Open(package_name);
}
if (!app_container_)
return SBOX_ERROR_CREATE_APPCONTAINER;
return SBOX_ALL_OK;
}
scoped_refptr<AppContainer> GetAppContainer() override {
return app_container_;
}
scoped_refptr<AppContainerBase> GetAppContainerBase() {
return app_container_;
}
void SetEffectiveToken(HANDLE token) override {}
const std::vector<std::wstring>& blocklisted_dlls() const {
return blocklisted_dlls_;
}
std::unique_ptr<PolicyInfo> GetPolicyInfo() override { return nullptr; }
void SetAllowNoSandboxJob() override { NOTREACHED(); }
bool GetAllowNoSandboxJob() override { return false; }
private:
std::vector<std::wstring> blocklisted_dlls_;
scoped_refptr<AppContainerBase> app_container_;
};
// Drops a temporary file granting RX access to a list of capabilities.
bool DropTempFileWithSecurity(
const base::ScopedTempDir& temp_dir,
const std::initializer_list<std::wstring>& capabilities,
base::FilePath* path) {
if (!base::CreateTemporaryFileInDir(temp_dir.GetPath(), path))
return false;
auto sddl = GetAccessAllowedForCapabilities(capabilities);
PSECURITY_DESCRIPTOR security_descriptor = nullptr;
if (!::ConvertStringSecurityDescriptorToSecurityDescriptorW(
sddl.c_str(), SDDL_REVISION_1, &security_descriptor, nullptr)) {
return false;
}
BOOL result = ::SetFileSecurityW(
path->value().c_str(), DACL_SECURITY_INFORMATION, security_descriptor);
::LocalFree(security_descriptor);
return !!result;
}
class SandboxWinTest : public ::testing::Test {
public:
SandboxWinTest() {}
void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
void TearDown() override {}
protected:
void CreateProgramFile(std::initializer_list<std::wstring> capabilities,
base::CommandLine* command_line) {
base::FilePath path;
ASSERT_TRUE(DropTempFileWithSecurity(temp_dir_, capabilities, &path));
command_line->SetProgram(path);
}
ResultCode CreateAppContainerProfile(
const base::CommandLine& base_command_line,
bool access_check_fail,
sandbox::mojom::Sandbox sandbox_type,
scoped_refptr<AppContainerBase>* profile) {
base::FilePath path;
base::CommandLine command_line(base_command_line);
if (access_check_fail) {
CreateProgramFile({}, &command_line);
} else {
CreateProgramFile({kChromeInstallFiles, kLpacChromeInstallFiles},
&command_line);
}
std::string appcontainer_id =
testing::UnitTest::GetInstance()->current_test_info()->test_case_name();
appcontainer_id += ".";
appcontainer_id +=
testing::UnitTest::GetInstance()->current_test_info()->name();
TestTargetPolicy policy;
ResultCode result = SandboxWin::AddAppContainerProfileToPolicy(
command_line, sandbox_type, appcontainer_id, &policy);
if (result == SBOX_ALL_OK)
*profile = policy.GetAppContainerBase();
return result;
}
base::ScopedTempDir temp_dir_;
};
} // namespace
TEST_F(SandboxWinTest, IsGpuAppContainerEnabled) {
if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
return;
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
EXPECT_FALSE(SandboxWin::IsAppContainerEnabledForSandbox(
command_line, sandbox::mojom::Sandbox::kGpu));
base::test::ScopedFeatureList features;
features.InitAndEnableFeature(features::kGpuAppContainer);
EXPECT_TRUE(SandboxWin::IsAppContainerEnabledForSandbox(
command_line, sandbox::mojom::Sandbox::kGpu));
EXPECT_FALSE(SandboxWin::IsAppContainerEnabledForSandbox(
command_line, sandbox::mojom::Sandbox::kNoSandbox));
}
TEST_F(SandboxWinTest, AppContainerAccessCheckFail) {
if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
return;
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
scoped_refptr<AppContainerBase> profile;
ResultCode result = CreateAppContainerProfile(
command_line, true, sandbox::mojom::Sandbox::kGpu, &profile);
EXPECT_EQ(SBOX_ERROR_CREATE_APPCONTAINER_ACCESS_CHECK, result);
EXPECT_EQ(nullptr, profile);
}
TEST_F(SandboxWinTest, AppContainerCheckProfile) {
if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
return;
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
scoped_refptr<AppContainerBase> profile;
ResultCode result = CreateAppContainerProfile(
command_line, false, sandbox::mojom::Sandbox::kGpu, &profile);
ASSERT_EQ(SBOX_ALL_OK, result);
ASSERT_NE(nullptr, profile);
absl::optional<base::win::Sid> package_sid = base::win::Sid::FromSddlString(
L"S-1-15-2-2402834154-1919024995-1520873375-1190013510-771931769-"
L"834570634-3212001585");
ASSERT_TRUE(package_sid);
EXPECT_EQ(package_sid, profile->GetPackageSid());
EXPECT_TRUE(profile->GetEnableLowPrivilegeAppContainer());
CheckCapabilities(profile.get(), {});
}
TEST_F(SandboxWinTest, AppContainerCheckProfileDisableLpac) {
if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
return;
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
base::test::ScopedFeatureList features;
features.InitAndDisableFeature(features::kGpuLPAC);
scoped_refptr<AppContainerBase> profile;
ResultCode result = CreateAppContainerProfile(
command_line, false, sandbox::mojom::Sandbox::kGpu, &profile);
ASSERT_EQ(SBOX_ALL_OK, result);
ASSERT_NE(nullptr, profile);
EXPECT_FALSE(profile->GetEnableLowPrivilegeAppContainer());
}
TEST_F(SandboxWinTest, AppContainerCheckProfileAddCapabilities) {
if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
return;
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
command_line.AppendSwitchASCII(switches::kAddGpuAppContainerCaps,
" cap1 , cap2 ,");
scoped_refptr<AppContainerBase> profile;
ResultCode result = CreateAppContainerProfile(
command_line, false, sandbox::mojom::Sandbox::kGpu, &profile);
ASSERT_EQ(SBOX_ALL_OK, result);
ASSERT_NE(nullptr, profile);
CheckCapabilities(profile.get(), {L"cap1", L"cap2"});
}
// Disabled due to crbug.com/1210614
TEST_F(SandboxWinTest, DISABLED_BlocklistAddOneDllCheckInBrowser) {
{ // Block loaded module.
TestTargetPolicy policy;
BlocklistAddOneDllForTesting(L"kernel32.dll", true, &policy);
EXPECT_EQ(policy.blocklisted_dlls(),
std::vector<std::wstring>({L"kernel32.dll"}));
}
{ // Block module which is not loaded.
TestTargetPolicy policy;
BlocklistAddOneDllForTesting(L"notloaded.dll", true, &policy);
EXPECT_TRUE(policy.blocklisted_dlls().empty());
}
{ // Block module loaded by short name.
#if defined(ARCH_CPU_X86)
const std::wstring short_dll_name = L"pe_ima~1.dll";
const std::wstring full_dll_name = L"pe_image_test_32.dll";
#elif defined(ARCH_CPU_X86_64)
const std::wstring short_dll_name = L"pe_ima~2.dll";
const std::wstring full_dll_name = L"pe_image_test_64.dll";
#elif defined(ARCH_CPU_ARM64)
const std::wstring short_dll_name = L"pe_ima~3.dll";
const std::wstring full_dll_name = L"pe_image_test_arm64.dll";
#endif
base::FilePath test_data_dir;
base::PathService::Get(base::DIR_TEST_DATA, &test_data_dir);
auto dll_path =
test_data_dir.AppendASCII("pe_image").Append(short_dll_name);
base::ScopedNativeLibrary library(dll_path);
EXPECT_TRUE(library.is_valid());
TestTargetPolicy policy;
BlocklistAddOneDllForTesting(full_dll_name.c_str(), true, &policy);
EXPECT_EQ(policy.blocklisted_dlls(),
std::vector<std::wstring>({short_dll_name, full_dll_name}));
}
}
TEST_F(SandboxWinTest, BlocklistAddOneDllDontCheckInBrowser) {
{ // Block module with short name.
TestTargetPolicy policy;
BlocklistAddOneDllForTesting(L"short.dll", false, &policy);
EXPECT_EQ(policy.blocklisted_dlls(),
std::vector<std::wstring>({L"short.dll"}));
}
{ // Block module with long name.
TestTargetPolicy policy;
BlocklistAddOneDllForTesting(L"thelongname.dll", false, &policy);
EXPECT_EQ(policy.blocklisted_dlls(),
std::vector<std::wstring>({L"thelongname.dll", L"thelon~1.dll",
L"thelon~2.dll", L"thelon~3.dll"}));
}
}
// Sandbox can't reach into content to pull the real policies, so these tests
// merely verifies that various parts of the delegate are called correctly and a
// policy can be generated.
class TestSandboxDelegate : public SandboxDelegate {
public:
TestSandboxDelegate(sandbox::mojom::Sandbox sandbox_type)
: sandbox_type_(sandbox_type) {}
sandbox::mojom::Sandbox GetSandboxType() override { return sandbox_type_; }
bool DisableDefaultPolicy() override { return false; }
bool GetAppContainerId(std::string* appcontainer_id) override {
NOTREACHED();
return false;
}
MOCK_METHOD1(PreSpawnTarget, bool(TargetPolicy* policy));
void PostSpawnTarget(base::ProcessHandle process) override {}
bool ShouldUnsandboxedRunInJob() override { return false; }
bool CetCompatible() override { return true; }
private:
sandbox::mojom::Sandbox sandbox_type_;
};
TEST_F(SandboxWinTest, GeneratedPolicyTest) {
TestSandboxDelegate test_renderer_delegate(
sandbox::mojom::Sandbox::kRenderer);
base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM);
base::HandlesToInheritVector handles_to_inherit;
BrokerServices* broker = SandboxFactory::GetBrokerServices();
scoped_refptr<TargetPolicy> policy = broker->CreatePolicy();
// PreSpawn should get called, but not modifying the policy for this test.
EXPECT_CALL(test_renderer_delegate, PreSpawnTarget(_)).WillOnce(Return(true));
ResultCode result = SandboxWin::GeneratePolicyForSandboxedProcess(
cmd_line, switches::kRendererProcess, handles_to_inherit,
&test_renderer_delegate, policy);
ASSERT_EQ(ResultCode::SBOX_ALL_OK, result);
// Check some default values come back. No need to check the exact policy in
// detail, but just that GeneratePolicyForSandboxedProcess generated some kind
// of valid policy.
EXPECT_EQ(IntegrityLevel::INTEGRITY_LEVEL_LOW, policy->GetIntegrityLevel());
EXPECT_EQ(JobLevel::kLockdown, policy->GetJobLevel());
EXPECT_EQ(TokenLevel::USER_LOCKDOWN, policy->GetLockdownTokenLevel());
}
TEST_F(SandboxWinTest, GeneratedPolicyTestNoSandbox) {
TestSandboxDelegate test_unsandboxed_delegate(
sandbox::mojom::Sandbox::kNoSandbox);
base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM);
base::HandlesToInheritVector handles_to_inherit;
BrokerServices* broker = SandboxFactory::GetBrokerServices();
scoped_refptr<TargetPolicy> policy = broker->CreatePolicy();
// Unsandboxed processes never call the delegate prespawn as there is no
// policy.
EXPECT_CALL(test_unsandboxed_delegate, PreSpawnTarget(_)).Times(0);
ResultCode result = SandboxWin::GeneratePolicyForSandboxedProcess(
cmd_line, switches::kRendererProcess, handles_to_inherit,
&test_unsandboxed_delegate, policy);
ASSERT_EQ(ResultCode::SBOX_ERROR_UNSANDBOXED_PROCESS, result);
}
} // namespace policy
} // namespace sandbox