// 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 "sandbox/win/src/app_container_base.h"
#include <windows.h>
#include <userenv.h>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/types/expected.h"
#include "base/win/scoped_handle.h"
#include "base/win/security_descriptor.h"
#include "sandbox/win/src/acl.h"
#include "sandbox/win/src/restricted_token_utils.h"
#include "sandbox/win/src/win_utils.h"
namespace sandbox {
namespace {
struct FreeSidDeleter {
inline void operator()(void* ptr) const { ::FreeSid(ptr); }
};
std::optional<base::win::Sid> DerivePackageSid(const wchar_t* package_name) {
PSID package_sid_ptr = nullptr;
HRESULT hr = ::DeriveAppContainerSidFromAppContainerName(package_name,
&package_sid_ptr);
if (FAILED(hr)) {
return std::nullopt;
}
std::unique_ptr<void, FreeSidDeleter> sid_deleter(package_sid_ptr);
return base::win::Sid::FromPSID(package_sid_ptr);
}
HRESULT WINAPI AppContainerRegisterSid(PSID, LPCWSTR, LPCWSTR);
HRESULT WINAPI AppContainerUnregisterSid(PSID);
HRESULT WINAPI AppContainerLookupMoniker(PSID, LPWSTR*);
void WINAPI AppContainerFreeMemory(void*);
template <typename T>
T BindFunc(const char* name) {
T fn = reinterpret_cast<T>(
::GetProcAddress(::GetModuleHandle(L"kernelbase.dll"), name));
CHECK(fn);
return fn;
}
HRESULT RegisterSid(const base::win::Sid& package_sid,
const wchar_t* moniker,
const wchar_t* display_name) {
static auto register_sid_fn =
BindFunc<decltype(&AppContainerRegisterSid)>("AppContainerRegisterSid");
return register_sid_fn(package_sid.GetPSID(), moniker, display_name);
}
HRESULT UnregisterSid(const base::win::Sid& package_sid) {
static auto unregister_sid_fn =
BindFunc<decltype(&AppContainerUnregisterSid)>(
"AppContainerUnregisterSid");
return unregister_sid_fn(package_sid.GetPSID());
}
base::expected<std::wstring, HRESULT> LookupMoniker(
const base::win::Sid& package_sid) {
static auto lookup_moniker_fn =
BindFunc<decltype(&AppContainerLookupMoniker)>(
"AppContainerLookupMoniker");
static auto free_memory_fn =
BindFunc<decltype(&AppContainerFreeMemory)>("AppContainerFreeMemory");
LPWSTR moniker_p;
HRESULT hr = lookup_moniker_fn(package_sid.GetPSID(), &moniker_p);
if (FAILED(hr)) {
return base::unexpected(hr);
}
std::wstring moniker = moniker_p;
free_memory_fn(moniker_p);
return moniker;
}
std::optional<base::FilePath> GetProfilePath(const wchar_t* package_name) {
base::FilePath local_app_data;
if (!base::PathService::Get(base::DIR_LOCAL_APP_DATA, &local_app_data)) {
return std::nullopt;
}
return local_app_data.Append(L"Packages").Append(package_name);
}
bool CreateAppContainerDirectory(const base::FilePath& profile_path,
const base::win::Sid& package_sid) {
base::FilePath ac_path = profile_path.Append(L"AC");
if (!base::CreateDirectory(ac_path)) {
return false;
}
std::optional<base::win::SecurityDescriptor> sd =
base::win::SecurityDescriptor::FromFile(ac_path,
DACL_SECURITY_INFORMATION);
if (!sd) {
return false;
}
if (!sd->SetMandatoryLabel(SECURITY_MANDATORY_LOW_RID,
OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE,
SYSTEM_MANDATORY_LABEL_NO_WRITE_UP)) {
return false;
}
if (!sd->SetDaclEntry(package_sid, base::win::SecurityAccessMode::kGrant,
FILE_ALL_ACCESS,
OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE)) {
return false;
}
if (!sd->WriteToFile(
ac_path, DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION)) {
return false;
}
return base::CreateDirectory(ac_path.Append(L"Temp"));
}
bool CreateProfileDirectory(const wchar_t* package_name,
const base::win::Sid& package_sid) {
std::optional<base::FilePath> profile_path = GetProfilePath(package_name);
if (!profile_path) {
return false;
}
if (base::DirectoryExists(*profile_path)) {
return true;
}
if (!base::CreateDirectory(*profile_path)) {
return false;
}
if (!CreateAppContainerDirectory(*profile_path, package_sid)) {
base::DeletePathRecursively(*profile_path);
return false;
}
return true;
}
class ProfileLock {
public:
ProfileLock()
: handle_(::CreateMutex(nullptr,
FALSE,
L"_app_container_profile_lock_0278d671-c445-4dfa-"
L"a8b4-d5ccf66d4cc3")) {
locked_ = ::WaitForSingleObject(handle_.get(), INFINITE) == WAIT_OBJECT_0;
}
ProfileLock(const ProfileLock&) = delete;
ProfileLock& operator=(const ProfileLock&) = delete;
~ProfileLock() {
if (locked_) {
::ReleaseMutex(handle_.get());
}
}
bool locked() { return locked_; }
private:
base::win::ScopedHandle handle_;
bool locked_;
};
} // namespace
// static
std::unique_ptr<AppContainerBase> AppContainerBase::CreateProfile(
const wchar_t* package_name,
const wchar_t* display_name,
const wchar_t* description) {
PSID package_sid_ptr = nullptr;
HRESULT hr = ::CreateAppContainerProfile(
package_name, display_name, description, nullptr, 0, &package_sid_ptr);
if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS))
return Open(package_name);
if (FAILED(hr))
return nullptr;
std::unique_ptr<void, FreeSidDeleter> sid_deleter(package_sid_ptr);
auto package_sid = base::win::Sid::FromPSID(package_sid_ptr);
if (!package_sid)
return nullptr;
return std::make_unique<AppContainerBase>(
package_name, std::move(*package_sid), AppContainerType::kProfile);
}
// static
std::unique_ptr<AppContainerBase> AppContainerBase::CreateProfileNoFirewall(
const wchar_t* package_name,
const wchar_t* display_name) {
auto package_sid = DerivePackageSid(package_name);
if (!package_sid) {
return nullptr;
}
ProfileLock lock;
if (!lock.locked()) {
return nullptr;
}
HRESULT hr = RegisterSid(*package_sid, package_name, display_name);
if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)) {
return std::make_unique<AppContainerBase>(
package_name, std::move(*package_sid), AppContainerType::kProfile);
}
if (FAILED(hr)) {
return nullptr;
}
if (!CreateProfileDirectory(package_name, *package_sid)) {
UnregisterSid(*package_sid);
return nullptr;
}
return std::make_unique<AppContainerBase>(
package_name, std::move(*package_sid), AppContainerType::kProfile);
}
// static
std::unique_ptr<AppContainerBase> AppContainerBase::Open(
const wchar_t* package_name) {
auto package_sid = DerivePackageSid(package_name);
if (!package_sid) {
return nullptr;
}
return std::make_unique<AppContainerBase>(
package_name, std::move(*package_sid), AppContainerType::kDerived);
}
// static
std::unique_ptr<AppContainerBase> AppContainerBase::CreateLowbox(
const wchar_t* sid) {
auto package_sid = base::win::Sid::FromSddlString(sid);
if (!package_sid)
return nullptr;
return std::make_unique<AppContainerBase>(L"lowbox", std::move(*package_sid),
AppContainerType::kLowbox);
}
// static
bool AppContainerBase::ProfileExists(const wchar_t* package_name) {
auto package_sid = DerivePackageSid(package_name);
if (!package_sid) {
return false;
}
return LookupMoniker(*package_sid).has_value();
}
// static
bool AppContainerBase::Delete(const wchar_t* package_name) {
return SUCCEEDED(::DeleteAppContainerProfile(package_name));
}
// static
bool AppContainerBase::DeleteNoFirewall(const wchar_t* package_name) {
auto package_sid = DerivePackageSid(package_name);
if (!package_sid) {
return false;
}
auto profile_path = GetProfilePath(package_name);
if (!profile_path) {
return false;
}
ProfileLock lock;
if (!lock.locked()) {
return false;
}
bool result = LookupMoniker(*package_sid).has_value() &&
SUCCEEDED(UnregisterSid(*package_sid));
return base::DirectoryExists(*profile_path) &&
base::DeletePathRecursively(*profile_path) && result;
}
AppContainerBase::AppContainerBase(const wchar_t* package_name,
base::win::Sid package_sid,
AppContainerType type)
: package_name_(package_name),
package_sid_(std::move(package_sid)),
enable_low_privilege_app_container_(false),
type_(type) {}
AppContainerBase::~AppContainerBase() {}
bool AppContainerBase::AccessCheck(const wchar_t* object_name,
base::win::SecurityObjectType object_type,
DWORD desired_access,
DWORD* granted_access,
BOOL* access_status) {
if (object_type != base::win::SecurityObjectType::kFile &&
object_type != base::win::SecurityObjectType::kRegistry) {
::SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
std::optional<base::win::SecurityDescriptor> sd =
base::win::SecurityDescriptor::FromName(
object_name, object_type,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION);
if (!sd) {
return false;
}
if (enable_low_privilege_app_container_) {
// We can't create a LPAC token directly, so modify the DACL to simulate it.
// Remove any ACEs for all application package SID.
if (!sd->SetDaclEntry(base::win::WellKnownSid::kAllApplicationPackages,
base::win::SecurityAccessMode::kRevoke, 0, 0)) {
return false;
}
}
std::optional<base::win::AccessToken> primary =
base::win::AccessToken::FromCurrentProcess(
/*impersonation=*/false, TOKEN_DUPLICATE);
if (!primary.has_value()) {
return false;
}
std::optional<base::win::AccessToken> lowbox = BuildPrimaryToken(*primary);
if (!lowbox) {
return false;
}
std::optional<base::win::AccessToken> token_query =
lowbox->DuplicateImpersonation(
base::win::SecurityImpersonationLevel::kIdentification);
if (!token_query) {
return false;
}
std::optional<base::win::AccessCheckResult> result =
sd->AccessCheck(*token_query, desired_access, object_type);
if (!result) {
return false;
}
*granted_access = result->granted_access;
*access_status = result->access_status;
return true;
}
void AppContainerBase::AddCapability(const wchar_t* capability_name) {
AddCapability(base::win::Sid::FromNamedCapability(capability_name), false);
}
void AppContainerBase::AddCapability(
base::win::WellKnownCapability capability) {
AddCapability(base::win::Sid::FromKnownCapability(capability), false);
}
bool AppContainerBase::AddCapabilitySddl(const wchar_t* sddl_sid) {
return AddCapability(base::win::Sid::FromSddlString(sddl_sid), false);
}
bool AppContainerBase::AddCapability(
const std::optional<base::win::Sid>& capability_sid,
bool impersonation_only) {
if (!capability_sid)
return false;
if (!impersonation_only)
capabilities_.push_back(capability_sid->Clone());
impersonation_capabilities_.push_back(capability_sid->Clone());
return true;
}
void AppContainerBase::AddImpersonationCapability(
const wchar_t* capability_name) {
AddCapability(base::win::Sid::FromNamedCapability(capability_name), true);
}
void AppContainerBase::AddImpersonationCapability(
base::win::WellKnownCapability capability) {
AddCapability(base::win::Sid::FromKnownCapability(capability), true);
}
bool AppContainerBase::AddImpersonationCapabilitySddl(const wchar_t* sddl_sid) {
return AddCapability(base::win::Sid::FromSddlString(sddl_sid), true);
}
const std::vector<base::win::Sid>& AppContainerBase::GetCapabilities() {
return capabilities_;
}
const std::vector<base::win::Sid>&
AppContainerBase::GetImpersonationCapabilities() {
return impersonation_capabilities_;
}
const base::win::Sid& AppContainerBase::GetPackageSid() const {
return package_sid_;
}
const wchar_t* AppContainerBase::GetPackageName() const {
return package_name_.c_str();
}
void AppContainerBase::SetEnableLowPrivilegeAppContainer(bool enable) {
enable_low_privilege_app_container_ = enable;
}
bool AppContainerBase::GetEnableLowPrivilegeAppContainer() {
return enable_low_privilege_app_container_;
}
AppContainerType AppContainerBase::GetAppContainerType() {
return type_;
}
std::unique_ptr<SecurityCapabilities>
AppContainerBase::GetSecurityCapabilities() {
return std::make_unique<SecurityCapabilities>(package_sid_, capabilities_);
}
std::optional<base::win::AccessToken> AppContainerBase::BuildImpersonationToken(
const base::win::AccessToken& token) {
std::optional<base::win::AccessToken> lowbox = token.CreateAppContainer(
package_sid_, impersonation_capabilities_, TOKEN_ALL_ACCESS);
if (!lowbox.has_value()) {
return std::nullopt;
}
std::optional<base::win::SecurityDescriptor> sd =
base::win::SecurityDescriptor::FromHandle(
lowbox->get(), base::win::SecurityObjectType::kKernel,
DACL_SECURITY_INFORMATION);
if (!sd) {
return std::nullopt;
}
lowbox = lowbox->DuplicateImpersonation(
base::win::SecurityImpersonationLevel::kImpersonation, TOKEN_ALL_ACCESS);
if (!lowbox.has_value()) {
return std::nullopt;
}
if (!sd->WriteToHandle(lowbox->get(), base::win::SecurityObjectType::kKernel,
DACL_SECURITY_INFORMATION)) {
return std::nullopt;
}
return lowbox;
}
std::optional<base::win::AccessToken> AppContainerBase::BuildPrimaryToken(
const base::win::AccessToken& token) {
return token.CreateAppContainer(package_sid_, capabilities_,
TOKEN_ALL_ACCESS);
}
} // namespace sandbox