diff options
Diffstat (limited to 'security/sandbox/chromium/sandbox/win/src')
172 files changed, 34722 insertions, 0 deletions
diff --git a/security/sandbox/chromium/sandbox/win/src/acl.cc b/security/sandbox/chromium/sandbox/win/src/acl.cc new file mode 100644 index 0000000000..bd0b181833 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/acl.cc @@ -0,0 +1,171 @@ +// 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 "sandbox/win/src/acl.h" + +#include <aclapi.h> +#include <sddl.h> + +#include "base/logging.h" +#include "base/memory/free_deleter.h" + +namespace sandbox { + +bool GetDefaultDacl( + HANDLE token, + std::unique_ptr<TOKEN_DEFAULT_DACL, base::FreeDeleter>* default_dacl) { + if (!token) + return false; + + DCHECK(default_dacl); + + unsigned long length = 0; + ::GetTokenInformation(token, TokenDefaultDacl, nullptr, 0, &length); + if (length == 0) { + NOTREACHED(); + return false; + } + + TOKEN_DEFAULT_DACL* acl = + reinterpret_cast<TOKEN_DEFAULT_DACL*>(malloc(length)); + default_dacl->reset(acl); + + if (!::GetTokenInformation(token, TokenDefaultDacl, default_dacl->get(), + length, &length)) + return false; + + return true; +} + +bool AddSidToDacl(const Sid& sid, + ACL* old_dacl, + ACCESS_MODE access_mode, + ACCESS_MASK access, + ACL** new_dacl) { + EXPLICIT_ACCESS new_access = {0}; + new_access.grfAccessMode = access_mode; + new_access.grfAccessPermissions = access; + new_access.grfInheritance = NO_INHERITANCE; + + new_access.Trustee.pMultipleTrustee = nullptr; + new_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + new_access.Trustee.TrusteeForm = TRUSTEE_IS_SID; + new_access.Trustee.ptstrName = reinterpret_cast<LPWSTR>(sid.GetPSID()); + + if (ERROR_SUCCESS != ::SetEntriesInAcl(1, &new_access, old_dacl, new_dacl)) + return false; + + return true; +} + +bool AddSidToDefaultDacl(HANDLE token, + const Sid& sid, + ACCESS_MODE access_mode, + ACCESS_MASK access) { + if (!token) + return false; + + std::unique_ptr<TOKEN_DEFAULT_DACL, base::FreeDeleter> default_dacl; + if (!GetDefaultDacl(token, &default_dacl)) + return false; + + ACL* new_dacl = nullptr; + if (!AddSidToDacl(sid, default_dacl->DefaultDacl, access_mode, access, + &new_dacl)) + return false; + + TOKEN_DEFAULT_DACL new_token_dacl = {0}; + new_token_dacl.DefaultDacl = new_dacl; + + bool ret = ::SetTokenInformation(token, TokenDefaultDacl, &new_token_dacl, + sizeof(new_token_dacl)); + ::LocalFree(new_dacl); + return ret; +} + +bool RevokeLogonSidFromDefaultDacl(HANDLE token) { + DWORD size = sizeof(TOKEN_GROUPS) + SECURITY_MAX_SID_SIZE; + TOKEN_GROUPS* logon_sid = reinterpret_cast<TOKEN_GROUPS*>(malloc(size)); + + std::unique_ptr<TOKEN_GROUPS, base::FreeDeleter> logon_sid_ptr(logon_sid); + + if (!::GetTokenInformation(token, TokenLogonSid, logon_sid, size, &size)) { + // If no logon sid, there's nothing to revoke. + if (::GetLastError() == ERROR_NOT_FOUND) + return true; + return false; + } + if (logon_sid->GroupCount < 1) { + ::SetLastError(ERROR_INVALID_TOKEN); + return false; + } + return AddSidToDefaultDacl(token, + reinterpret_cast<SID*>(logon_sid->Groups[0].Sid), + REVOKE_ACCESS, 0); +} + +bool AddUserSidToDefaultDacl(HANDLE token, ACCESS_MASK access) { + DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; + TOKEN_USER* token_user = reinterpret_cast<TOKEN_USER*>(malloc(size)); + + std::unique_ptr<TOKEN_USER, base::FreeDeleter> token_user_ptr(token_user); + + if (!::GetTokenInformation(token, TokenUser, token_user, size, &size)) + return false; + + return AddSidToDefaultDacl(token, + reinterpret_cast<SID*>(token_user->User.Sid), + GRANT_ACCESS, access); +} + +bool AddKnownSidToObject(HANDLE object, + SE_OBJECT_TYPE object_type, + const Sid& sid, + ACCESS_MODE access_mode, + ACCESS_MASK access) { + PSECURITY_DESCRIPTOR descriptor = nullptr; + PACL old_dacl = nullptr; + PACL new_dacl = nullptr; + + if (ERROR_SUCCESS != + ::GetSecurityInfo(object, object_type, DACL_SECURITY_INFORMATION, nullptr, + nullptr, &old_dacl, nullptr, &descriptor)) + return false; + + if (!AddSidToDacl(sid, old_dacl, access_mode, access, &new_dacl)) { + ::LocalFree(descriptor); + return false; + } + + DWORD result = + ::SetSecurityInfo(object, object_type, DACL_SECURITY_INFORMATION, nullptr, + nullptr, new_dacl, nullptr); + + ::LocalFree(new_dacl); + ::LocalFree(descriptor); + + if (ERROR_SUCCESS != result) + return false; + + return true; +} + +bool ReplacePackageSidInDacl(HANDLE object, + SE_OBJECT_TYPE object_type, + const Sid& package_sid, + ACCESS_MASK access) { + if (!AddKnownSidToObject(object, object_type, package_sid, REVOKE_ACCESS, + 0)) { + return false; + } + + Sid any_package_sid(::WinBuiltinAnyPackageSid); + if (!AddKnownSidToObject(object, object_type, any_package_sid, GRANT_ACCESS, + access)) { + return false; + } + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/acl.h b/security/sandbox/chromium/sandbox/win/src/acl.h new file mode 100644 index 0000000000..194edb0988 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/acl.h @@ -0,0 +1,64 @@ +// 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. + +#ifndef SANDBOX_SRC_ACL_H_ +#define SANDBOX_SRC_ACL_H_ + +#include <accctrl.h> +#include <windows.h> + +#include <memory> + +#include "base/memory/free_deleter.h" +#include "sandbox/win/src/sid.h" + +namespace sandbox { + +// Returns the default dacl from the token passed in. +bool GetDefaultDacl( + HANDLE token, + std::unique_ptr<TOKEN_DEFAULT_DACL, base::FreeDeleter>* default_dacl); + +// Appends an ACE represented by |sid|, |access_mode|, and |access| to +// |old_dacl|. If the function succeeds, new_dacl contains the new dacl and +// must be freed using LocalFree. +bool AddSidToDacl(const Sid& sid, + ACL* old_dacl, + ACCESS_MODE access_mode, + ACCESS_MASK access, + ACL** new_dacl); + +// Adds an ACE represented by |sid| and |access| with |access_mode| to the +// default dacl present in the token. +bool AddSidToDefaultDacl(HANDLE token, + const Sid& sid, + ACCESS_MODE access_mode, + ACCESS_MASK access); + +// Revokes access to the logon SID for the default dacl present in the token. +bool RevokeLogonSidFromDefaultDacl(HANDLE token); + +// Adds an ACE represented by the user sid and |access| to the default dacl +// present in the token. +bool AddUserSidToDefaultDacl(HANDLE token, ACCESS_MASK access); + +// Adds an ACE represented by |known_sid|, |access_mode|, and |access| to +// the dacl of the kernel object referenced by |object| and of |object_type|. +bool AddKnownSidToObject(HANDLE object, + SE_OBJECT_TYPE object_type, + const Sid& sid, + ACCESS_MODE access_mode, + ACCESS_MASK access); + +// Replace package SID in DACL to the "any package" SID. It allows Low-IL +// tokens to open the object which is important for warm up when using renderer +// AppContainer. +bool ReplacePackageSidInDacl(HANDLE object, + SE_OBJECT_TYPE object_type, + const Sid& package_sid, + ACCESS_MASK access); + +} // namespace sandbox + +#endif // SANDBOX_SRC_ACL_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/app_container_profile.h b/security/sandbox/chromium/sandbox/win/src/app_container_profile.h new file mode 100644 index 0000000000..c95c68e552 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/app_container_profile.h @@ -0,0 +1,74 @@ +// Copyright 2017 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. + +#ifndef SANDBOX_SRC_APP_CONTAINER_PROFILE_H_ +#define SANDBOX_SRC_APP_CONTAINER_PROFILE_H_ + +#include <windows.h> + +#include <accctrl.h> + +#include "base/files/file_path.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/sid.h" + +namespace sandbox { + +class AppContainerProfile { + public: + // Increments the reference count of this object. The reference count must + // be incremented if this interface is given to another component. + virtual void AddRef() = 0; + + // Decrements the reference count of this object. When the reference count + // is zero the object is automatically destroyed. + // Indicates that the caller is done with this interface. After calling + // release no other method should be called. + virtual void Release() = 0; + + // Get a handle to a registry key for this package. + virtual bool GetRegistryLocation(REGSAM desired_access, + base::win::ScopedHandle* key) = 0; + + // Get a folder path to a location for this package. + virtual bool GetFolderPath(base::FilePath* file_path) = 0; + + // Get a pipe name usable by this AC. + virtual bool GetPipePath(const wchar_t* pipe_name, + base::FilePath* pipe_path) = 0; + + // Do an access check based on this profile for a named object. If method + // returns true then access_status reflects whether access was granted and + // granted_access gives the final access rights. The object_type can be one of + // SE_FILE_OBJECT, SE_REGISTRY_KEY, SE_REGISTRY_WOW64_32KEY. See + // ::GetNamedSecurityInfo for more information about how the enumeration is + // used and what format object_name needs to be. + virtual bool AccessCheck(const wchar_t* object_name, + SE_OBJECT_TYPE object_type, + DWORD desired_access, + DWORD* granted_access, + BOOL* access_status) = 0; + + // Adds a capability by name to this profile. + virtual bool AddCapability(const wchar_t* capability_name) = 0; + // Adds a capability from a known list. + virtual bool AddCapability(WellKnownCapabilities capability) = 0; + // Adds a capability from a SID + virtual bool AddCapabilitySddl(const wchar_t* sddl_sid) = 0; + + // Adds an impersonation capability by name to this profile. + virtual bool AddImpersonationCapability(const wchar_t* capability_name) = 0; + // Adds an impersonation capability from a known list. + virtual bool AddImpersonationCapability(WellKnownCapabilities capability) = 0; + // Adds an impersonation capability from a SID + virtual bool AddImpersonationCapabilitySddl(const wchar_t* sddl_sid) = 0; + + // Enable Low Privilege AC. + virtual void SetEnableLowPrivilegeAppContainer(bool enable) = 0; + virtual bool GetEnableLowPrivilegeAppContainer() = 0; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_APP_CONTAINER_PROFILE_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/app_container_profile_base.cc b/security/sandbox/chromium/sandbox/win/src/app_container_profile_base.cc new file mode 100644 index 0000000000..e388a9978e --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/app_container_profile_base.cc @@ -0,0 +1,337 @@ +// Copyright 2017 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 <memory> + +#include <aclapi.h> +#include <userenv.h> + +#include "base/strings/stringprintf.h" +#if !defined(MOZ_SANDBOX) +#include "base/win/scoped_co_mem.h" +#endif +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/app_container_profile_base.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +namespace { + +typedef decltype(::CreateAppContainerProfile) CreateAppContainerProfileFunc; + +typedef decltype(::DeriveAppContainerSidFromAppContainerName) + DeriveAppContainerSidFromAppContainerNameFunc; + +typedef decltype(::DeleteAppContainerProfile) DeleteAppContainerProfileFunc; + +typedef decltype(::GetAppContainerFolderPath) GetAppContainerFolderPathFunc; + +typedef decltype( + ::GetAppContainerRegistryLocation) GetAppContainerRegistryLocationFunc; + +struct FreeSidDeleter { + inline void operator()(void* ptr) const { ::FreeSid(ptr); } +}; + +bool IsValidObjectType(SE_OBJECT_TYPE object_type) { + switch (object_type) { + case SE_FILE_OBJECT: + case SE_REGISTRY_KEY: + return true; + default: + break; + } + return false; +} + +bool GetGenericMappingForType(SE_OBJECT_TYPE object_type, + GENERIC_MAPPING* generic_mapping) { + if (!IsValidObjectType(object_type)) + return false; + if (object_type == SE_FILE_OBJECT) { + generic_mapping->GenericRead = FILE_GENERIC_READ; + generic_mapping->GenericWrite = FILE_GENERIC_WRITE; + generic_mapping->GenericExecute = FILE_GENERIC_EXECUTE; + generic_mapping->GenericAll = FILE_ALL_ACCESS; + } else { + generic_mapping->GenericRead = KEY_READ; + generic_mapping->GenericWrite = KEY_WRITE; + generic_mapping->GenericExecute = KEY_EXECUTE; + generic_mapping->GenericAll = KEY_ALL_ACCESS; + } + return true; +} + +class ScopedImpersonation { + public: + ScopedImpersonation(const base::win::ScopedHandle& token) { + BOOL result = ::ImpersonateLoggedOnUser(token.Get()); + DCHECK(result); + } + + ~ScopedImpersonation() { + BOOL result = ::RevertToSelf(); + DCHECK(result); + } +}; + +} // namespace + +// static +AppContainerProfileBase* AppContainerProfileBase::Create( + const wchar_t* package_name, + const wchar_t* display_name, + const wchar_t* description) { + static auto create_app_container_profile = + reinterpret_cast<CreateAppContainerProfileFunc*>(GetProcAddress( + GetModuleHandle(L"userenv"), "CreateAppContainerProfile")); + if (!create_app_container_profile) + return nullptr; + + PSID package_sid = nullptr; + HRESULT hr = create_app_container_profile( + package_name, display_name, description, nullptr, 0, &package_sid); + 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); + return new AppContainerProfileBase(Sid(package_sid)); +} + +// static +AppContainerProfileBase* AppContainerProfileBase::Open( + const wchar_t* package_name) { + static auto derive_app_container_sid = + reinterpret_cast<DeriveAppContainerSidFromAppContainerNameFunc*>( + GetProcAddress(GetModuleHandle(L"userenv"), + "DeriveAppContainerSidFromAppContainerName")); + if (!derive_app_container_sid) + return nullptr; + + PSID package_sid = nullptr; + HRESULT hr = derive_app_container_sid(package_name, &package_sid); + if (FAILED(hr)) + return nullptr; + + std::unique_ptr<void, FreeSidDeleter> sid_deleter(package_sid); + return new AppContainerProfileBase(Sid(package_sid)); +} + +// static +bool AppContainerProfileBase::Delete(const wchar_t* package_name) { + static auto delete_app_container_profile = + reinterpret_cast<DeleteAppContainerProfileFunc*>(GetProcAddress( + GetModuleHandle(L"userenv"), "DeleteAppContainerProfile")); + if (!delete_app_container_profile) + return false; + + return SUCCEEDED(delete_app_container_profile(package_name)); +} + +AppContainerProfileBase::AppContainerProfileBase(const Sid& package_sid) + : ref_count_(0), + package_sid_(package_sid), + enable_low_privilege_app_container_(false) {} + +AppContainerProfileBase::~AppContainerProfileBase() {} + +void AppContainerProfileBase::AddRef() { + ::InterlockedIncrement(&ref_count_); +} + +void AppContainerProfileBase::Release() { + LONG ref_count = ::InterlockedDecrement(&ref_count_); + if (ref_count == 0) { + delete this; + } +} + +bool AppContainerProfileBase::GetRegistryLocation( + REGSAM desired_access, + base::win::ScopedHandle* key) { + static GetAppContainerRegistryLocationFunc* + get_app_container_registry_location = + reinterpret_cast<GetAppContainerRegistryLocationFunc*>(GetProcAddress( + GetModuleHandle(L"userenv"), "GetAppContainerRegistryLocation")); + if (!get_app_container_registry_location) + return false; + + base::win::ScopedHandle token; + if (!BuildLowBoxToken(&token)) + return false; + + ScopedImpersonation impersonation(token); + HKEY key_handle; + if (FAILED(get_app_container_registry_location(desired_access, &key_handle))) + return false; + key->Set(key_handle); + return true; +} + +bool AppContainerProfileBase::GetFolderPath(base::FilePath* file_path) { +#if defined(MOZ_SANDBOX) + IMMEDIATE_CRASH(); +#else + static GetAppContainerFolderPathFunc* get_app_container_folder_path = + reinterpret_cast<GetAppContainerFolderPathFunc*>(GetProcAddress( + GetModuleHandle(L"userenv"), "GetAppContainerFolderPath")); + if (!get_app_container_folder_path) + return false; + std::wstring sddl_str; + if (!package_sid_.ToSddlString(&sddl_str)) + return false; + base::win::ScopedCoMem<wchar_t> path_str; + if (FAILED(get_app_container_folder_path(sddl_str.c_str(), &path_str))) + return false; + *file_path = base::FilePath(path_str.get()); + return true; +#endif +} + +bool AppContainerProfileBase::GetPipePath(const wchar_t* pipe_name, + base::FilePath* pipe_path) { +#if defined(MOZ_SANDBOX) + IMMEDIATE_CRASH(); +#else + std::wstring sddl_str; + if (!package_sid_.ToSddlString(&sddl_str)) + return false; + *pipe_path = base::FilePath(base::StringPrintf(L"\\\\.\\pipe\\%ls\\%ls", + sddl_str.c_str(), pipe_name)); + return true; +#endif +} + +bool AppContainerProfileBase::AccessCheck(const wchar_t* object_name, + SE_OBJECT_TYPE object_type, + DWORD desired_access, + DWORD* granted_access, + BOOL* access_status) { + GENERIC_MAPPING generic_mapping; + if (!GetGenericMappingForType(object_type, &generic_mapping)) + return false; + MapGenericMask(&desired_access, &generic_mapping); + PSECURITY_DESCRIPTOR sd = nullptr; + PACL dacl = nullptr; + if (GetNamedSecurityInfo( + object_name, object_type, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, + nullptr, nullptr, &dacl, nullptr, &sd) != ERROR_SUCCESS) { + return false; + } + + std::unique_ptr<void, LocalFreeDeleter> sd_ptr(sd); + + if (enable_low_privilege_app_container_) { + Sid any_package_sid(::WinBuiltinAnyPackageSid); + // We can't create a LPAC token directly, so modify the DACL to simulate it. + // Set mask for ALL APPLICATION PACKAGE Sid to 0. + for (WORD index = 0; index < dacl->AceCount; ++index) { + PVOID temp_ace; + if (!GetAce(dacl, index, &temp_ace)) + return false; + PACE_HEADER header = static_cast<PACE_HEADER>(temp_ace); + if ((header->AceType != ACCESS_ALLOWED_ACE_TYPE) && + (header->AceType != ACCESS_DENIED_ACE_TYPE)) { + continue; + } + // Allowed and deny aces have the same underlying structure. + PACCESS_ALLOWED_ACE ace = static_cast<PACCESS_ALLOWED_ACE>(temp_ace); + if (!::IsValidSid(&ace->SidStart)) { + continue; + } + if (::EqualSid(&ace->SidStart, any_package_sid.GetPSID())) { + ace->Mask = 0; + } + } + } + + PRIVILEGE_SET priv_set = {}; + DWORD priv_set_length = sizeof(PRIVILEGE_SET); + + base::win::ScopedHandle token; + if (!BuildLowBoxToken(&token)) + return false; + + return !!::AccessCheck(sd, token.Get(), desired_access, &generic_mapping, + &priv_set, &priv_set_length, granted_access, + access_status); +} + +bool AppContainerProfileBase::AddCapability(const wchar_t* capability_name) { + return AddCapability(Sid::FromNamedCapability(capability_name), false); +} + +bool AppContainerProfileBase::AddCapability(WellKnownCapabilities capability) { + return AddCapability(Sid::FromKnownCapability(capability), false); +} + +bool AppContainerProfileBase::AddCapabilitySddl(const wchar_t* sddl_sid) { + return AddCapability(Sid::FromSddlString(sddl_sid), false); +} + +bool AppContainerProfileBase::AddCapability(const Sid& capability_sid, + bool impersonation_only) { + if (!capability_sid.IsValid()) + return false; + if (!impersonation_only) + capabilities_.push_back(capability_sid); + impersonation_capabilities_.push_back(capability_sid); + return true; +} + +bool AppContainerProfileBase::AddImpersonationCapability( + const wchar_t* capability_name) { + return AddCapability(Sid::FromNamedCapability(capability_name), true); +} + +bool AppContainerProfileBase::AddImpersonationCapability( + WellKnownCapabilities capability) { + return AddCapability(Sid::FromKnownCapability(capability), true); +} + +bool AppContainerProfileBase::AddImpersonationCapabilitySddl( + const wchar_t* sddl_sid) { + return AddCapability(Sid::FromSddlString(sddl_sid), true); +} + +const std::vector<Sid>& AppContainerProfileBase::GetCapabilities() { + return capabilities_; +} + +const std::vector<Sid>& +AppContainerProfileBase::GetImpersonationCapabilities() { + return impersonation_capabilities_; +} + +Sid AppContainerProfileBase::GetPackageSid() const { + return package_sid_; +} + +void AppContainerProfileBase::SetEnableLowPrivilegeAppContainer(bool enable) { + enable_low_privilege_app_container_ = enable; +} + +bool AppContainerProfileBase::GetEnableLowPrivilegeAppContainer() { + return enable_low_privilege_app_container_; +} + +std::unique_ptr<SecurityCapabilities> +AppContainerProfileBase::GetSecurityCapabilities() { + return std::unique_ptr<SecurityCapabilities>( + new SecurityCapabilities(package_sid_, capabilities_)); +} + +bool AppContainerProfileBase::BuildLowBoxToken(base::win::ScopedHandle* token) { + return CreateLowBoxToken(nullptr, IMPERSONATION, + GetSecurityCapabilities().get(), nullptr, 0, + token) == ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/app_container_profile_base.h b/security/sandbox/chromium/sandbox/win/src/app_container_profile_base.h new file mode 100644 index 0000000000..35fb4efdf5 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/app_container_profile_base.h @@ -0,0 +1,94 @@ +// Copyright 2017 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. + +#ifndef SANDBOX_SRC_APP_CONTAINER_PROFILE_BASE_H_ +#define SANDBOX_SRC_APP_CONTAINER_PROFILE_BASE_H_ + +#include <windows.h> + +#include <accctrl.h> + +#include <memory> +#include <vector> + +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/app_container_profile.h" +#include "sandbox/win/src/security_capabilities.h" +#include "sandbox/win/src/sid.h" + +namespace sandbox { + +class AppContainerProfileBase final : public AppContainerProfile { + public: + void AddRef() override; + void Release() override; + bool GetRegistryLocation(REGSAM desired_access, + base::win::ScopedHandle* key) override; + bool GetFolderPath(base::FilePath* file_path) override; + bool GetPipePath(const wchar_t* pipe_name, + base::FilePath* pipe_path) override; + bool AccessCheck(const wchar_t* object_name, + SE_OBJECT_TYPE object_type, + DWORD desired_access, + DWORD* granted_access, + BOOL* access_status) override; + bool AddCapability(const wchar_t* capability_name) override; + bool AddCapability(WellKnownCapabilities capability) override; + bool AddCapabilitySddl(const wchar_t* sddl_sid) override; + bool AddImpersonationCapability(const wchar_t* capability_name) override; + bool AddImpersonationCapability(WellKnownCapabilities capability) override; + bool AddImpersonationCapabilitySddl(const wchar_t* sddl_sid) override; + void SetEnableLowPrivilegeAppContainer(bool enable) override; + bool GetEnableLowPrivilegeAppContainer() override; + + // Get the package SID for this AC. + Sid GetPackageSid() const; + + // Get an allocated SecurityCapabilities object for this App Container. + std::unique_ptr<SecurityCapabilities> GetSecurityCapabilities(); + + // Get a vector of capabilities. + const std::vector<Sid>& GetCapabilities(); + + // Get a vector of impersonation only capabilities. Used if the process needs + // a more privileged token to start. + const std::vector<Sid>& GetImpersonationCapabilities(); + + // Creates a new AppContainerProfile object. This will create a new profile + // if it doesn't already exist. The profile must be deleted manually using + // the Delete method if it's no longer required. + static AppContainerProfileBase* Create(const wchar_t* package_name, + const wchar_t* display_name, + const wchar_t* description); + + // Opens an AppContainerProfile object. No checks will be made on + // whether the package exists or not. + static AppContainerProfileBase* Open(const wchar_t* package_name); + + // Delete a profile based on name. Returns true if successful, or if the + // package doesn't already exist. + static bool Delete(const wchar_t* package_name); + + private: + AppContainerProfileBase(const Sid& package_sid); + ~AppContainerProfileBase(); + + bool BuildLowBoxToken(base::win::ScopedHandle* token); + bool AddCapability(const Sid& capability_sid, bool impersonation_only); + + // Standard object-lifetime reference counter. + volatile LONG ref_count_; + Sid package_sid_; + bool enable_low_privilege_app_container_; + std::vector<Sid> capabilities_; + std::vector<Sid> impersonation_capabilities_; + + DISALLOW_COPY_AND_ASSIGN(AppContainerProfileBase); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_APP_CONTAINER_PROFILE_BASE_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/app_container_test.cc b/security/sandbox/chromium/sandbox/win/src/app_container_test.cc new file mode 100644 index 0000000000..a6c0948a94 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/app_container_test.cc @@ -0,0 +1,342 @@ +// 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_->SetProcessMitigations(MITIGATION_HEAP_TERMINATE)); + 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)); + + MitigationFlags expected_mitigations = 0; + MitigationFlags expected_delayed = MITIGATION_HEAP_TERMINATE; + sandbox::ResultCode expected_result = SBOX_ERROR_BAD_PARAMS; + + if (base::win::GetVersion() >= base::win::Version::WIN10_RS5) { + expected_mitigations = MITIGATION_HEAP_TERMINATE; + expected_delayed = 0; + expected_result = SBOX_ALL_OK; + } + + EXPECT_EQ(expected_mitigations, policy_->GetProcessMitigations()); + EXPECT_EQ(expected_delayed, policy_->GetDelayedProcessMitigations()); + EXPECT_EQ(expected_result, + 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 diff --git a/security/sandbox/chromium/sandbox/win/src/broker_services.cc b/security/sandbox/chromium/sandbox/win/src/broker_services.cc new file mode 100644 index 0000000000..15ed321071 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/broker_services.cc @@ -0,0 +1,745 @@ +// 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 "sandbox/win/src/broker_services.h" + +#include <aclapi.h> + +#include <stddef.h> + +#include <utility> + +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/threading/platform_thread.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "base/win/startup_information.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/app_container_profile.h" +#include "sandbox/win/src/process_mitigations.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_policy_base.h" +#include "sandbox/win/src/sandbox_policy_diagnostic.h" +#include "sandbox/win/src/target_process.h" +#include "sandbox/win/src/win2k_threadpool.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +// Utility function to associate a completion port to a job object. +bool AssociateCompletionPort(HANDLE job, HANDLE port, void* key) { + JOBOBJECT_ASSOCIATE_COMPLETION_PORT job_acp = {key, port}; + return ::SetInformationJobObject(job, + JobObjectAssociateCompletionPortInformation, + &job_acp, sizeof(job_acp)) + ? true + : false; +} + +// Utility function to do the cleanup necessary when something goes wrong +// while in SpawnTarget and we must terminate the target process. +sandbox::ResultCode SpawnCleanup(sandbox::TargetProcess* target) { + target->Terminate(); + delete target; + return sandbox::SBOX_ERROR_GENERIC; +} + +// the different commands that you can send to the worker thread that +// executes TargetEventsThread(). +enum { + THREAD_CTRL_NONE, + THREAD_CTRL_NEW_JOB_TRACKER, + THREAD_CTRL_NEW_PROCESS_TRACKER, + THREAD_CTRL_PROCESS_SIGNALLED, + THREAD_CTRL_GET_POLICY_INFO, + THREAD_CTRL_QUIT, + THREAD_CTRL_LAST, +}; + +// Helper structure that allows the Broker to associate a job notification +// with a job object and with a policy. +struct JobTracker { + JobTracker(base::win::ScopedHandle job, + scoped_refptr<sandbox::PolicyBase> policy, + DWORD process_id) + : job(std::move(job)), policy(policy), process_id(process_id) {} + ~JobTracker() { FreeResources(); } + + // Releases the Job and notifies the associated Policy object to release its + // resources as well. + void FreeResources(); + + base::win::ScopedHandle job; + scoped_refptr<sandbox::PolicyBase> policy; + DWORD process_id; +}; + +void JobTracker::FreeResources() { + if (policy) { + bool res = ::TerminateJobObject(job.Get(), sandbox::SBOX_ALL_OK); + DCHECK(res); + // Closing the job causes the target process to be destroyed so this needs + // to happen before calling OnJobEmpty(). + HANDLE stale_job_handle = job.Get(); + job.Close(); + + // In OnJobEmpty() we don't actually use the job handle directly. + policy->OnJobEmpty(stale_job_handle); + policy = nullptr; + } +} + +// tracks processes that are not in jobs +struct ProcessTracker { + ProcessTracker(scoped_refptr<sandbox::PolicyBase> policy, + DWORD process_id, + base::win::ScopedHandle process) + : policy(policy), process_id(process_id), process(std::move(process)) {} + ~ProcessTracker() { FreeResources(); } + + void FreeResources(); + + scoped_refptr<sandbox::PolicyBase> policy; + DWORD process_id; + base::win::ScopedHandle process; + // Used to UnregisterWait. Not a real handle so cannot CloseHandle(). + HANDLE wait_handle; + // IOCP that is tracking this non-job process + HANDLE iocp; +}; + +void ProcessTracker::FreeResources() { + if (policy) { + policy->OnJobEmpty(nullptr); + policy = nullptr; + } +} + +// Helper redispatches process events to tracker thread. +void WINAPI ProcessEventCallback(PVOID param, BOOLEAN ignored) { + // This callback should do very little, and must be threadpool safe. + ProcessTracker* tracker = reinterpret_cast<ProcessTracker*>(param); + // If this fails we can do nothing... we will leak the policy. + ::PostQueuedCompletionStatus(tracker->iocp, 0, THREAD_CTRL_PROCESS_SIGNALLED, + reinterpret_cast<LPOVERLAPPED>(tracker)); +} + +// Helper class to send policy lists +class PolicyDiagnosticList final : public sandbox::PolicyList { + public: + PolicyDiagnosticList() {} + ~PolicyDiagnosticList() override {} + void push_back(std::unique_ptr<sandbox::PolicyInfo> info) { + internal_list_.push_back(std::move(info)); + } + std::vector<std::unique_ptr<sandbox::PolicyInfo>>::iterator begin() override { + return internal_list_.begin(); + } + std::vector<std::unique_ptr<sandbox::PolicyInfo>>::iterator end() override { + return internal_list_.end(); + } + size_t size() const override { return internal_list_.size(); } + + private: + std::vector<std::unique_ptr<sandbox::PolicyInfo>> internal_list_; +}; + +} // namespace + +namespace sandbox { + +BrokerServicesBase::BrokerServicesBase() {} + +// The broker uses a dedicated worker thread that services the job completion +// port to perform policy notifications and associated cleanup tasks. +ResultCode BrokerServicesBase::Init() { + if (job_port_.IsValid() || thread_pool_) + return SBOX_ERROR_UNEXPECTED_CALL; + + ::InitializeCriticalSection(&lock_); + + job_port_.Set(::CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 0)); + if (!job_port_.IsValid()) + return SBOX_ERROR_CANNOT_INIT_BROKERSERVICES; + + no_targets_.Set(::CreateEventW(nullptr, true, false, nullptr)); + + job_thread_.Set(::CreateThread(nullptr, 0, // Default security and stack. + TargetEventsThread, this, 0, nullptr)); + if (!job_thread_.IsValid()) + return SBOX_ERROR_CANNOT_INIT_BROKERSERVICES; + + return SBOX_ALL_OK; +} + +// The destructor should only be called when the Broker process is terminating. +// Since BrokerServicesBase is a singleton, this is called from the CRT +// termination handlers, if this code lives on a DLL it is called during +// DLL_PROCESS_DETACH in other words, holding the loader lock, so we cannot +// wait for threads here. +BrokerServicesBase::~BrokerServicesBase() { + // If there is no port Init() was never called successfully. + if (!job_port_.IsValid()) + return; + + // Closing the port causes, that no more Job notifications are delivered to + // the worker thread and also causes the thread to exit. This is what we + // want to do since we are going to close all outstanding Jobs and notifying + // the policy objects ourselves. + ::PostQueuedCompletionStatus(job_port_.Get(), 0, THREAD_CTRL_QUIT, nullptr); + + if (job_thread_.IsValid() && + WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_.Get(), 1000)) { + // Cannot clean broker services. + NOTREACHED(); + return; + } + thread_pool_.reset(); + ::DeleteCriticalSection(&lock_); +} + +scoped_refptr<TargetPolicy> BrokerServicesBase::CreatePolicy() { + // If you change the type of the object being created here you must also + // change the downcast to it in SpawnTarget(). + scoped_refptr<TargetPolicy> policy(new PolicyBase); + // PolicyBase starts with refcount 1. + policy->Release(); + return policy; +} + +// The worker thread stays in a loop waiting for asynchronous notifications +// from the job objects. Right now we only care about knowing when the last +// process on a job terminates, but in general this is the place to tell +// the policy about events. +DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) { + if (!param) + return 1; + + base::PlatformThread::SetName("BrokerEvent"); + + BrokerServicesBase* broker = reinterpret_cast<BrokerServicesBase*>(param); + HANDLE port = broker->job_port_.Get(); + HANDLE no_targets = broker->no_targets_.Get(); + + std::set<DWORD> child_process_ids; + std::list<std::unique_ptr<JobTracker>> jobs; + std::list<std::unique_ptr<ProcessTracker>> processes; + int target_counter = 0; + int untracked_target_counter = 0; + ::ResetEvent(no_targets); + + while (true) { + DWORD events = 0; + ULONG_PTR key = 0; + LPOVERLAPPED ovl = nullptr; + + if (!::GetQueuedCompletionStatus(port, &events, &key, &ovl, INFINITE)) { + // this call fails if the port has been closed before we have a + // chance to service the last packet which is 'exit' anyway so + // this is not an error. + return 1; + } + + if (key > THREAD_CTRL_LAST) { + // The notification comes from a job object. There are nine notifications + // that jobs can send and some of them depend on the job attributes set. + JobTracker* tracker = reinterpret_cast<JobTracker*>(key); + + // Processes may be added to a job after the process count has + // reached zero, leading us to manipulate a freed JobTracker + // object or job handle (as the key is no longer valid). We + // therefore check if the tracker has already been deleted. + if (std::find_if(jobs.begin(), jobs.end(), [&](auto&& p) -> bool { + return p.get() == tracker; + }) == jobs.end()) { + CHECK(false); + } + + switch (events) { + case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: { + // The job object has signaled that the last process associated + // with it has terminated. It is safe to free the tracker + // and release its reference to the associated policy object + // which will Close the job handle. + HANDLE job_handle = tracker->job.Get(); + + // Erase by comparing with the job handle. + jobs.erase(std::remove_if(jobs.begin(), jobs.end(), + [&](auto&& p) -> bool { + return p->job.Get() == job_handle; + }), + jobs.end()); + break; + } + + case JOB_OBJECT_MSG_NEW_PROCESS: { + // Child process created from sandboxed process. + DWORD process_id = + static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl)); + size_t count = child_process_ids.count(process_id); + if (count == 0) + untracked_target_counter++; + ++target_counter; + if (1 == target_counter) { + ::ResetEvent(no_targets); + } + break; + } + + case JOB_OBJECT_MSG_EXIT_PROCESS: + case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: { + { + AutoLock lock(&broker->lock_); + broker->active_targets_.erase( + static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl))); + } + size_t erase_result = child_process_ids.erase( + static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl))); + if (erase_result != 1U) { + // The process was untracked e.g. a child process of the target. + --untracked_target_counter; + DCHECK(untracked_target_counter >= 0); + } + --target_counter; + if (0 == target_counter) + ::SetEvent(no_targets); + + DCHECK(target_counter >= 0); + break; + } + + case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: { + // A child process attempted and failed to create a child process. + // Windows does not reveal the process id. + untracked_target_counter++; + target_counter++; + break; + } + + case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT: { + bool res = ::TerminateJobObject(tracker->job.Get(), + SBOX_FATAL_MEMORY_EXCEEDED); + DCHECK(res); + break; + } + + default: { + NOTREACHED(); + break; + } + } + } else if (THREAD_CTRL_NEW_JOB_TRACKER == key) { + std::unique_ptr<JobTracker> tracker; + tracker.reset(reinterpret_cast<JobTracker*>(ovl)); + DCHECK(tracker->job.IsValid()); + + child_process_ids.insert(tracker->process_id); + jobs.push_back(std::move(tracker)); + + } else if (THREAD_CTRL_NEW_PROCESS_TRACKER == key) { + std::unique_ptr<ProcessTracker> tracker; + tracker.reset(reinterpret_cast<ProcessTracker*>(ovl)); + + if (child_process_ids.empty()) { + ::SetEvent(broker->no_targets_.Get()); + } + + tracker->iocp = port; + if (!::RegisterWaitForSingleObject(&(tracker->wait_handle), + tracker->process.Get(), + ProcessEventCallback, tracker.get(), + INFINITE, WT_EXECUTEONLYONCE)) { + // Failed. Invalidate the wait_handle and store anyway. + tracker->wait_handle = INVALID_HANDLE_VALUE; + } + processes.push_back(std::move(tracker)); + + } else if (THREAD_CTRL_PROCESS_SIGNALLED == key) { + ProcessTracker* tracker = + static_cast<ProcessTracker*>(reinterpret_cast<void*>(ovl)); + + { + AutoLock lock(&broker->lock_); + broker->active_targets_.erase(tracker->process_id); + } + + ::UnregisterWait(tracker->wait_handle); + tracker->wait_handle = INVALID_HANDLE_VALUE; + // Copy process_id so that we can legally reference it even after we have + // found the ProcessTracker object to delete. + const DWORD process_id = tracker->process_id; + // PID is unique until the process handle is closed in dtor. + processes.erase(std::remove_if(processes.begin(), processes.end(), + [&](auto&& p) -> bool { + return p->process_id == process_id; + }), + processes.end()); + } else if (THREAD_CTRL_GET_POLICY_INFO == key) { + // Clone the policies for sandbox diagnostics. + std::unique_ptr<PolicyDiagnosticsReceiver> receiver; + receiver.reset(static_cast<PolicyDiagnosticsReceiver*>( + reinterpret_cast<void*>(ovl))); + // The PollicyInfo ctor copies essential information from the trackers. + auto policy_list = std::make_unique<PolicyDiagnosticList>(); + for (auto&& process_tracker : processes) { + if (process_tracker->policy) { + policy_list->push_back(std::make_unique<PolicyDiagnostic>( + process_tracker->policy.get())); + } + } + for (auto&& job_tracker : jobs) { + if (job_tracker->policy) { + policy_list->push_back( + std::make_unique<PolicyDiagnostic>(job_tracker->policy.get())); + } + } + // Receiver should return quickly. + receiver->ReceiveDiagnostics(std::move(policy_list)); + + } else if (THREAD_CTRL_QUIT == key) { + // The broker object is being destroyed so the thread needs to exit. + for (auto&& tracker : processes) { + ::UnregisterWait(tracker->wait_handle); + tracker->wait_handle = INVALID_HANDLE_VALUE; + } + // After this point, so further calls to ProcessEventCallback can + // occur. Other tracked objects are destroyed as this thread ends. + return 0; + } else { + // We have not implemented more commands. + NOTREACHED(); + } + } + + NOTREACHED(); + return 0; +} + +// SpawnTarget does all the interesting sandbox setup and creates the target +// process inside the sandbox. +ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, + const wchar_t* command_line, + base::EnvironmentMap& env_map, + scoped_refptr<TargetPolicy> policy, + ResultCode* last_warning, + DWORD* last_error, + PROCESS_INFORMATION* target_info) { + if (!exe_path) + return SBOX_ERROR_BAD_PARAMS; + + if (!policy) + return SBOX_ERROR_BAD_PARAMS; + + // Even though the resources touched by SpawnTarget can be accessed in + // multiple threads, the method itself cannot be called from more than one + // thread. This is to protect the global variables used while setting up the + // child process, and to make sure launcher thread mitigations are applied + // correctly. + static DWORD thread_id = ::GetCurrentThreadId(); + DCHECK(thread_id == ::GetCurrentThreadId()); + *last_warning = SBOX_ALL_OK; + + // Launcher thread only needs to be opted out of ACG once. Do this on the + // first child process being spawned. + static bool launcher_thread_opted_out = false; + + if (!launcher_thread_opted_out) { + // Soft fail this call. It will fail if ACG is not enabled for this process. + sandbox::ApplyMitigationsToCurrentThread( + sandbox::MITIGATION_DYNAMIC_CODE_OPT_OUT_THIS_THREAD); + launcher_thread_opted_out = true; + } + + // This downcast is safe as long as we control CreatePolicy() + scoped_refptr<PolicyBase> policy_base(static_cast<PolicyBase*>(policy.get())); + + // Construct the tokens and the job object that we are going to associate + // with the soon to be created target process. + base::win::ScopedHandle initial_token; + base::win::ScopedHandle lockdown_token; + base::win::ScopedHandle lowbox_token; + ResultCode result = SBOX_ALL_OK; + + result = + policy_base->MakeTokens(&initial_token, &lockdown_token, &lowbox_token); + if (SBOX_ALL_OK != result) + return result; + if (lowbox_token.IsValid() && + base::win::GetVersion() < base::win::Version::WIN8) { + // We don't allow lowbox_token below Windows 8. + return SBOX_ERROR_BAD_PARAMS; + } + + base::win::ScopedHandle job; + result = policy_base->MakeJobObject(&job); + if (SBOX_ALL_OK != result) + return result; + + // Initialize the startup information from the policy. + base::win::StartupInformation startup_info; + + // We don't want any child processes causing the IDC_APPSTARTING cursor. + startup_info.startup_info()->dwFlags |= STARTF_FORCEOFFFEEDBACK; + + // The liftime of |mitigations|, |inherit_handle_list| and + // |child_process_creation| have to be at least as long as + // |startup_info| because |UpdateProcThreadAttribute| requires that + // its |lpValue| parameter persist until |DeleteProcThreadAttributeList| is + // called; StartupInformation's destructor makes such a call. + DWORD64 mitigations[2]; + std::vector<HANDLE> inherited_handle_list; + DWORD child_process_creation = PROCESS_CREATION_CHILD_PROCESS_RESTRICTED; + + std::wstring desktop = policy_base->GetAlternateDesktop(); + if (!desktop.empty()) { + startup_info.startup_info()->lpDesktop = + const_cast<wchar_t*>(desktop.c_str()); + } + + bool inherit_handles = false; + + int attribute_count = 0; + + size_t mitigations_size; + ConvertProcessMitigationsToPolicy(policy_base->GetProcessMitigations(), + &mitigations[0], &mitigations_size); + if (mitigations[0] || mitigations[1]) + ++attribute_count; + + bool restrict_child_process_creation = false; + if (base::win::GetVersion() >= base::win::Version::WIN10_TH2 && + policy_base->GetJobLevel() <= JOB_LIMITED_USER) { + restrict_child_process_creation = true; + ++attribute_count; + } + + HANDLE stdout_handle = policy_base->GetStdoutHandle(); + HANDLE stderr_handle = policy_base->GetStderrHandle(); + + if (stdout_handle != INVALID_HANDLE_VALUE) + inherited_handle_list.push_back(stdout_handle); + + // Handles in the list must be unique. + if (stderr_handle != stdout_handle && stderr_handle != INVALID_HANDLE_VALUE) + inherited_handle_list.push_back(stderr_handle); + + const auto& policy_handle_list = policy_base->GetHandlesBeingShared(); + + for (HANDLE handle : policy_handle_list) + inherited_handle_list.push_back(handle); + + if (inherited_handle_list.size()) + ++attribute_count; + + scoped_refptr<AppContainerProfileBase> profile = + policy_base->GetAppContainerProfileBase(); + if (profile) { + if (base::win::GetVersion() < base::win::Version::WIN8) + return SBOX_ERROR_BAD_PARAMS; + ++attribute_count; + if (profile->GetEnableLowPrivilegeAppContainer()) { + // LPAC first supported in RS1. + if (base::win::GetVersion() < base::win::Version::WIN10_RS1) + return SBOX_ERROR_BAD_PARAMS; + ++attribute_count; + } + } + + if (!startup_info.InitializeProcThreadAttributeList(attribute_count)) + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + + if (mitigations[0] || mitigations[1]) { + if (!startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &mitigations[0], + mitigations_size)) { + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + } + } + + if (restrict_child_process_creation) { + if (!startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_CHILD_PROCESS_POLICY, &child_process_creation, + sizeof(child_process_creation))) { + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + } + } + + if (inherited_handle_list.size()) { + if (!startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &inherited_handle_list[0], + sizeof(HANDLE) * inherited_handle_list.size())) { + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + } + startup_info.startup_info()->dwFlags |= STARTF_USESTDHANDLES; + startup_info.startup_info()->hStdInput = INVALID_HANDLE_VALUE; + startup_info.startup_info()->hStdOutput = stdout_handle; + startup_info.startup_info()->hStdError = stderr_handle; + // Allowing inheritance of handles is only secure now that we + // have limited which handles will be inherited. + inherit_handles = true; + } + + // Declared here to ensure they stay in scope until after process creation. + std::unique_ptr<SecurityCapabilities> security_capabilities; + DWORD all_applications_package_policy = + PROCESS_CREATION_ALL_APPLICATION_PACKAGES_OPT_OUT; + + if (profile) { + security_capabilities = profile->GetSecurityCapabilities(); + if (!startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, + security_capabilities.get(), sizeof(SECURITY_CAPABILITIES))) { + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + } + if (profile->GetEnableLowPrivilegeAppContainer()) { + if (!startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_ALL_APPLICATION_PACKAGES_POLICY, + &all_applications_package_policy, + sizeof(all_applications_package_policy))) { + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + } + } + } + + // Construct the thread pool here in case it is expensive. + // The thread pool is shared by all the targets + if (!thread_pool_) + thread_pool_ = std::make_unique<Win2kThreadPool>(); + + // Create the TargetProcess object and spawn the target suspended. Note that + // Brokerservices does not own the target object. It is owned by the Policy. + base::win::ScopedProcessInformation process_info; + TargetProcess* target = new TargetProcess( + std::move(initial_token), std::move(lockdown_token), job.Get(), + thread_pool_.get(), + profile ? profile->GetImpersonationCapabilities() : std::vector<Sid>()); + + result = target->Create(exe_path, command_line, inherit_handles, startup_info, + &process_info, env_map, last_error); + + if (result != SBOX_ALL_OK) { + SpawnCleanup(target); + return result; + } + + if (lowbox_token.IsValid()) { + *last_warning = target->AssignLowBoxToken(lowbox_token); + // If this fails we continue, but report the error as a warning. + // This is due to certain configurations causing the setting of the + // token to fail post creation, and we'd rather continue if possible. + if (*last_warning != SBOX_ALL_OK) + *last_error = ::GetLastError(); + } + + // Now the policy is the owner of the target. + result = policy_base->AddTarget(target); + + if (result != SBOX_ALL_OK) { + *last_error = ::GetLastError(); + SpawnCleanup(target); + return result; + } + + if (job.IsValid()) { + JobTracker* tracker = + new JobTracker(std::move(job), policy_base, process_info.process_id()); + + // Post the tracker to the tracking thread, then associate the job with + // the tracker. The worker thread takes ownership of these objects. + CHECK(::PostQueuedCompletionStatus( + job_port_.Get(), 0, THREAD_CTRL_NEW_JOB_TRACKER, + reinterpret_cast<LPOVERLAPPED>(tracker))); + // There is no obvious recovery after failure here. Previous version with + // SpawnCleanup() caused deletion of TargetProcess twice. crbug.com/480639 + CHECK( + AssociateCompletionPort(tracker->job.Get(), job_port_.Get(), tracker)); + + AutoLock lock(&lock_); + active_targets_.insert(process_info.process_id()); + } else { + result = AddTargetPeerInternal(process_info.process_handle(), + process_info.process_id(), + policy_base, last_error); + if (result != SBOX_ALL_OK) { + // This may fail in the same way as Job associated processes. + // crbug.com/480639. + target->Terminate(); + return result; + } + } + + *target_info = process_info.Take(); + return result; +} + +ResultCode BrokerServicesBase::WaitForAllTargets() { + ::WaitForSingleObject(no_targets_.Get(), INFINITE); + return SBOX_ALL_OK; +} + +bool BrokerServicesBase::IsSafeDuplicationTarget(DWORD process_id) { + AutoLock lock(&lock_); + return active_targets_.find(process_id) != active_targets_.end(); +} + +ResultCode BrokerServicesBase::AddTargetPeerInternal( + HANDLE peer_process_handle, + DWORD peer_process_id, + scoped_refptr<PolicyBase> policy_base, + DWORD* last_error) { + // Duplicate the process handle to give the tracking machinery + // something valid to wait on in the tracking thread. + HANDLE tmp_process_handle = INVALID_HANDLE_VALUE; + if (!::DuplicateHandle(::GetCurrentProcess(), peer_process_handle, + ::GetCurrentProcess(), &tmp_process_handle, + SYNCHRONIZE, false, 0 /*no options*/)) { + *last_error = ::GetLastError(); + return SBOX_ERROR_CANNOT_DUPLICATE_PROCESS_HANDLE; + } + base::win::ScopedHandle dup_process_handle(tmp_process_handle); + ProcessTracker* tracker = new ProcessTracker( + policy_base, peer_process_id, std::move(dup_process_handle)); + // The tracker and policy will leak if this call fails. + ::PostQueuedCompletionStatus(job_port_.Get(), 0, + THREAD_CTRL_NEW_PROCESS_TRACKER, + reinterpret_cast<LPOVERLAPPED>(tracker)); + + AutoLock lock(&lock_); + active_targets_.insert(peer_process_id); + + return SBOX_ALL_OK; +} + +ResultCode BrokerServicesBase::AddTargetPeer(HANDLE peer_process) { + DWORD last_error; + return AddTargetPeerInternal(peer_process, ::GetProcessId(peer_process), + nullptr, &last_error); +} + +ResultCode BrokerServicesBase::GetPolicyDiagnostics( + std::unique_ptr<PolicyDiagnosticsReceiver> receiver) { + CHECK(job_thread_.IsValid()); + // Post to the job thread. + if (!::PostQueuedCompletionStatus( + job_port_.Get(), 0, THREAD_CTRL_GET_POLICY_INFO, + reinterpret_cast<LPOVERLAPPED>(receiver.get()))) { + receiver->OnError(SBOX_ERROR_GENERIC); + return SBOX_ERROR_GENERIC; + } + + // Ownership has passed to tracker thread. + receiver.release(); + return SBOX_ALL_OK; +} + +bool BrokerServicesBase::DeriveCapabilitySidFromName(const wchar_t* name, + PSID derived_sid, + DWORD sid_buffer_length) { + return ::CopySid(sid_buffer_length, derived_sid, + Sid::FromNamedCapability(name).GetPSID()); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/broker_services.h b/security/sandbox/chromium/sandbox/win/src/broker_services.h new file mode 100644 index 0000000000..64dc6d66e5 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/broker_services.h @@ -0,0 +1,105 @@ +// 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. + +#ifndef SANDBOX_WIN_SRC_BROKER_SERVICES_H_ +#define SANDBOX_WIN_SRC_BROKER_SERVICES_H_ + +#include <list> +#include <map> +#include <memory> +#include <set> +#include <utility> + +#include "base/compiler_specific.h" +#include "base/environment.h" +#include "base/macros.h" +#include "base/memory/scoped_refptr.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/job.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_policy_base.h" +#include "sandbox/win/src/sharedmem_ipc_server.h" +#include "sandbox/win/src/win2k_threadpool.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +// BrokerServicesBase --------------------------------------------------------- +// Broker implementation version 0 +// +// This is an implementation of the interface BrokerServices and +// of the associated TargetProcess interface. In this implementation +// TargetProcess is a friend of BrokerServices where the later manages a +// collection of the former. +class BrokerServicesBase final : public BrokerServices, + public SingletonBase<BrokerServicesBase> { + public: + BrokerServicesBase(); + + ~BrokerServicesBase(); + + // BrokerServices interface. + ResultCode Init() override; + scoped_refptr<TargetPolicy> CreatePolicy() override; + ResultCode SpawnTarget(const wchar_t* exe_path, + const wchar_t* command_line, + base::EnvironmentMap& env_map, + scoped_refptr<TargetPolicy> policy, + ResultCode* last_warning, + DWORD* last_error, + PROCESS_INFORMATION* target) override; + ResultCode WaitForAllTargets() override; + ResultCode AddTargetPeer(HANDLE peer_process) override; + + // Checks if the supplied process ID matches one of the broker's active + // target processes. We use this method for the specific purpose of + // checking if we can safely duplicate a handle to the supplied process + // in DuplicateHandleProxyAction. + bool IsSafeDuplicationTarget(DWORD process_id); + + ResultCode GetPolicyDiagnostics( + std::unique_ptr<PolicyDiagnosticsReceiver> receiver) override; + + bool DeriveCapabilitySidFromName(const wchar_t* name, PSID derived_sid, + DWORD sid_buffer_length) override; + + private: + // The routine that the worker thread executes. It is in charge of + // notifications and cleanup-related tasks. + static DWORD WINAPI TargetEventsThread(PVOID param); + + // The completion port used by the job objects to communicate events to + // the worker thread. + base::win::ScopedHandle job_port_; + + // Handle to a manual-reset event that is signaled when the total target + // process count reaches zero. + base::win::ScopedHandle no_targets_; + + // Handle to the worker thread that reacts to job notifications. + base::win::ScopedHandle job_thread_; + + // Provides a pool of threads that are used to wait on the IPC calls. + std::unique_ptr<ThreadProvider> thread_pool_; + + // The set representing the broker's active target processes including + // both sandboxed and unsandboxed peer processes. + std::set<DWORD> active_targets_; + + // Lock used to protect active_targets_ from being simultaneously accessed + // by multiple threads. + CRITICAL_SECTION lock_; + + ResultCode AddTargetPeerInternal(HANDLE peer_process_handle, + DWORD peer_process_id, + scoped_refptr<PolicyBase> policy_base, + DWORD* last_error); + + DISALLOW_COPY_AND_ASSIGN(BrokerServicesBase); +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_BROKER_SERVICES_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/crosscall_client.h b/security/sandbox/chromium/sandbox/win/src/crosscall_client.h new file mode 100644 index 0000000000..a346aeb753 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/crosscall_client.h @@ -0,0 +1,509 @@ +// 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. + +#ifndef SANDBOX_SRC_CROSSCALL_CLIENT_H_ +#define SANDBOX_SRC_CROSSCALL_CLIENT_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "base/compiler_specific.h" +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/sandbox.h" + +// This header defines the CrossCall(..) family of templated functions +// Their purpose is to simulate the syntax of regular call but to generate +// and IPC from the client-side. +// +// The basic pattern is to +// 1) use template argument deduction to compute the size of each +// parameter and the appropriate copy method +// 2) pack the parameters in the appropriate ActualCallParams< > object +// 3) call the IPC interface IPCProvider::DoCall( ) +// +// The general interface of CrossCall is: +// ResultCode CrossCall(IPCProvider& ipc_provider, +// uint32_t tag, +// const Par1& p1, const Par2& p2,...pn +// CrossCallReturn* answer) +// +// where: +// ipc_provider: is a specific implementation of the ipc transport see +// sharedmem_ipc_server.h for an example. +// tag : is the unique id for this IPC call. Is used to route the call to +// the appropriate service. +// p1, p2,.. pn : The input parameters of the IPC. Use only simple types +// and wide strings (can add support for others). +// answer : If the IPC was successful. The server-side answer is here. The +// interpretation of the answer is private to client and server. +// +// The return value is ALL_OK if the IPC was delivered to the server, other +// return codes indicate that the IPC transport failed to deliver it. +namespace sandbox { + +enum class IpcTag; + +// The copy helper uses templates to deduce the appropriate copy function to +// copy the input parameters in the buffer that is going to be send across the +// IPC. These template facility can be made more sophisticated as need arises. + +// The default copy helper. It catches the general case where no other +// specialized template matches better. We set the type to UINT32_TYPE, so this +// only works with objects whose size is 32 bits. +template <typename T> +class CopyHelper { + public: + CopyHelper(const T& t) : t_(t) {} + + // Returns the pointer to the start of the input. + const void* GetStart() const { return &t_; } + + // Update the stored value with the value in the buffer. This is not + // supported for this type. + bool Update(void* buffer) { + // Not supported; + return true; + } + + // Returns the size of the input in bytes. + uint32_t GetSize() const { return sizeof(T); } + + // Returns true if the current type is used as an In or InOut parameter. + bool IsInOut() { return false; } + + // Returns this object's type. + ArgType GetType() { + static_assert(sizeof(T) == sizeof(uint32_t), "specialization needed"); + return UINT32_TYPE; + } + + private: + const T& t_; +}; + +// This copy helper template specialization if for the void pointer +// case both 32 and 64 bit. +template <> +class CopyHelper<void*> { + public: + CopyHelper(void* t) : t_(t) {} + + // Returns the pointer to the start of the input. + const void* GetStart() const { return &t_; } + + // Update the stored value with the value in the buffer. This is not + // supported for this type. + bool Update(void* buffer) { + // Not supported; + return true; + } + + // Returns the size of the input in bytes. + uint32_t GetSize() const { return sizeof(t_); } + + // Returns true if the current type is used as an In or InOut parameter. + bool IsInOut() { return false; } + + // Returns this object's type. + ArgType GetType() { return VOIDPTR_TYPE; } + + private: + const void* t_; +}; + +// This copy helper template specialization catches the cases where the +// parameter is a pointer to a string. +template <> +class CopyHelper<const wchar_t*> { + public: + CopyHelper(const wchar_t* t) : t_(t) {} + + // Returns the pointer to the start of the string. + const void* GetStart() const { return t_; } + + // Update the stored value with the value in the buffer. This is not + // supported for this type. + bool Update(void* buffer) { + // Not supported; + return true; + } + + // Returns the size of the string in bytes. We define a nullptr string to + // be of zero length. + uint32_t GetSize() const { + __try { + return (!t_) ? 0 + : static_cast<uint32_t>(StringLength(t_) * sizeof(t_[0])); + } __except (EXCEPTION_EXECUTE_HANDLER) { + return UINT32_MAX; + } + } + + // Returns true if the current type is used as an In or InOut parameter. + bool IsInOut() { return false; } + + ArgType GetType() { return WCHAR_TYPE; } + + private: + // We provide our not very optimized version of wcslen(), since we don't + // want to risk having the linker use the version in the CRT since the CRT + // might not be present when we do an early IPC call. + static size_t CDECL StringLength(const wchar_t* wcs) { + const wchar_t* eos = wcs; + while (*eos++) + ; + return static_cast<size_t>(eos - wcs - 1); + } + + const wchar_t* t_; +}; + +// Specialization for non-const strings. We just reuse the implementation of the +// const string specialization. +template <> +class CopyHelper<wchar_t*> : public CopyHelper<const wchar_t*> { + public: + typedef CopyHelper<const wchar_t*> Base; + CopyHelper(wchar_t* t) : Base(t) {} + + const void* GetStart() const { return Base::GetStart(); } + + bool Update(void* buffer) { return Base::Update(buffer); } + + uint32_t GetSize() const { return Base::GetSize(); } + + bool IsInOut() { return Base::IsInOut(); } + + ArgType GetType() { return Base::GetType(); } +}; + +// Specialization for wchar_t arrays strings. We just reuse the implementation +// of the const string specialization. +template <size_t n> +class CopyHelper<const wchar_t[n]> : public CopyHelper<const wchar_t*> { + public: + typedef const wchar_t array[n]; + typedef CopyHelper<const wchar_t*> Base; + CopyHelper(array t) : Base(t) {} + + const void* GetStart() const { return Base::GetStart(); } + + bool Update(void* buffer) { return Base::Update(buffer); } + + uint32_t GetSize() const { return Base::GetSize(); } + + bool IsInOut() { return Base::IsInOut(); } + + ArgType GetType() { return Base::GetType(); } +}; + +// Generic encapsulation class containing a pointer to a buffer and the +// size of the buffer. It is used by the IPC to be able to pass in/out +// parameters. +class InOutCountedBuffer : public CountedBuffer { + public: + InOutCountedBuffer(void* buffer, uint32_t size) + : CountedBuffer(buffer, size) {} +}; + +// This copy helper template specialization catches the cases where the +// parameter is a an input buffer. +template <> +class CopyHelper<CountedBuffer> { + public: + CopyHelper(const CountedBuffer t) : t_(t) {} + + // Returns the pointer to the start of the string. + const void* GetStart() const { return t_.Buffer(); } + + // Update not required so just return true; + bool Update(void* buffer) { return true; } + + // Returns the size of the string in bytes. We define a nullptr string to + // be of zero length. + uint32_t GetSize() const { return t_.Size(); } + + // Returns true if the current type is used as an In or InOut parameter. + bool IsInOut() { return false; } + + ArgType GetType() { return INPTR_TYPE; } + + private: + const CountedBuffer t_; +}; + +// This copy helper template specialization catches the cases where the +// parameter is a an input/output buffer. +template <> +class CopyHelper<InOutCountedBuffer> { + public: + CopyHelper(const InOutCountedBuffer t) : t_(t) {} + + // Returns the pointer to the start of the string. + const void* GetStart() const { return t_.Buffer(); } + + // Updates the buffer with the value from the new buffer in parameter. + bool Update(void* buffer) { + // We are touching user memory, this has to be done from inside a try + // except. + __try { + memcpy_wrapper(t_.Buffer(), buffer, t_.Size()); + } __except (EXCEPTION_EXECUTE_HANDLER) { + return false; + } + return true; + } + + // Returns the size of the string in bytes. We define a nullptr string to + // be of zero length. + uint32_t GetSize() const { return t_.Size(); } + + // Returns true if the current type is used as an In or InOut parameter. + bool IsInOut() { return true; } + + ArgType GetType() { return INOUTPTR_TYPE; } + + private: + const InOutCountedBuffer t_; +}; + +// The following two macros make it less error prone the generation +// of CrossCall functions with ever more input parameters. + +#define XCALL_GEN_PARAMS_OBJ(num, params) \ + typedef ActualCallParams<num, kIPCChannelSize> ActualParams; \ + void* raw_mem = ipc_provider.GetBuffer(); \ + if (!raw_mem) \ + return SBOX_ERROR_NO_SPACE; \ + ActualParams* params = new (raw_mem) ActualParams(tag); + +#define XCALL_GEN_COPY_PARAM(num, params) \ + static_assert(kMaxIpcParams >= num, "too many parameters"); \ + CopyHelper<Par##num> ch##num(p##num); \ + if (!params->CopyParamIn(num - 1, ch##num.GetStart(), ch##num.GetSize(), \ + ch##num.IsInOut(), ch##num.GetType())) \ + return SBOX_ERROR_NO_SPACE; + +#define XCALL_GEN_UPDATE_PARAM(num, params) \ + if (!ch##num.Update(params->GetParamPtr(num - 1))) { \ + ipc_provider.FreeBuffer(raw_mem); \ + return SBOX_ERROR_BAD_PARAMS; \ + } + +#define XCALL_GEN_FREE_CHANNEL() ipc_provider.FreeBuffer(raw_mem); + +// CrossCall template with one input parameter +template <typename IPCProvider, typename Par1> +ResultCode CrossCall(IPCProvider& ipc_provider, + IpcTag tag, + const Par1& p1, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(1, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + + return result; +} + +// CrossCall template with two input parameters. +template <typename IPCProvider, typename Par1, typename Par2> +ResultCode CrossCall(IPCProvider& ipc_provider, + IpcTag tag, + const Par1& p1, + const Par2& p2, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(2, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} + +// CrossCall template with three input parameters. +template <typename IPCProvider, typename Par1, typename Par2, typename Par3> +ResultCode CrossCall(IPCProvider& ipc_provider, + IpcTag tag, + const Par1& p1, + const Par2& p2, + const Par3& p3, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(3, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + XCALL_GEN_COPY_PARAM(3, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_UPDATE_PARAM(3, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} + +// CrossCall template with four input parameters. +template <typename IPCProvider, + typename Par1, + typename Par2, + typename Par3, + typename Par4> +ResultCode CrossCall(IPCProvider& ipc_provider, + IpcTag tag, + const Par1& p1, + const Par2& p2, + const Par3& p3, + const Par4& p4, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(4, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + XCALL_GEN_COPY_PARAM(3, call_params); + XCALL_GEN_COPY_PARAM(4, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_UPDATE_PARAM(3, call_params); + XCALL_GEN_UPDATE_PARAM(4, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} + +// CrossCall template with five input parameters. +template <typename IPCProvider, + typename Par1, + typename Par2, + typename Par3, + typename Par4, + typename Par5> +ResultCode CrossCall(IPCProvider& ipc_provider, + IpcTag tag, + const Par1& p1, + const Par2& p2, + const Par3& p3, + const Par4& p4, + const Par5& p5, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(5, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + XCALL_GEN_COPY_PARAM(3, call_params); + XCALL_GEN_COPY_PARAM(4, call_params); + XCALL_GEN_COPY_PARAM(5, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_UPDATE_PARAM(3, call_params); + XCALL_GEN_UPDATE_PARAM(4, call_params); + XCALL_GEN_UPDATE_PARAM(5, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} + +// CrossCall template with six input parameters. +template <typename IPCProvider, + typename Par1, + typename Par2, + typename Par3, + typename Par4, + typename Par5, + typename Par6> +ResultCode CrossCall(IPCProvider& ipc_provider, + IpcTag tag, + const Par1& p1, + const Par2& p2, + const Par3& p3, + const Par4& p4, + const Par5& p5, + const Par6& p6, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(6, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + XCALL_GEN_COPY_PARAM(3, call_params); + XCALL_GEN_COPY_PARAM(4, call_params); + XCALL_GEN_COPY_PARAM(5, call_params); + XCALL_GEN_COPY_PARAM(6, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_UPDATE_PARAM(3, call_params); + XCALL_GEN_UPDATE_PARAM(4, call_params); + XCALL_GEN_UPDATE_PARAM(5, call_params); + XCALL_GEN_UPDATE_PARAM(6, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} + +// CrossCall template with seven input parameters. +template <typename IPCProvider, + typename Par1, + typename Par2, + typename Par3, + typename Par4, + typename Par5, + typename Par6, + typename Par7> +ResultCode CrossCall(IPCProvider& ipc_provider, + IpcTag tag, + const Par1& p1, + const Par2& p2, + const Par3& p3, + const Par4& p4, + const Par5& p5, + const Par6& p6, + const Par7& p7, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(7, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + XCALL_GEN_COPY_PARAM(3, call_params); + XCALL_GEN_COPY_PARAM(4, call_params); + XCALL_GEN_COPY_PARAM(5, call_params); + XCALL_GEN_COPY_PARAM(6, call_params); + XCALL_GEN_COPY_PARAM(7, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_UPDATE_PARAM(3, call_params); + XCALL_GEN_UPDATE_PARAM(4, call_params); + XCALL_GEN_UPDATE_PARAM(5, call_params); + XCALL_GEN_UPDATE_PARAM(6, call_params); + XCALL_GEN_UPDATE_PARAM(7, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} +} // namespace sandbox + +#endif // SANDBOX_SRC_CROSSCALL_CLIENT_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/crosscall_params.h b/security/sandbox/chromium/sandbox/win/src/crosscall_params.h new file mode 100644 index 0000000000..6971b55cb7 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/crosscall_params.h @@ -0,0 +1,315 @@ +// 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. + +#ifndef SANDBOX_SRC_CROSSCALL_PARAMS_H__ +#define SANDBOX_SRC_CROSSCALL_PARAMS_H__ + +#if !defined(SANDBOX_FUZZ_TARGET) +#include <windows.h> + +#include <lmaccess.h> +#else +#include "sandbox/win/fuzzer/fuzzer_types.h" +#endif + +#include <stddef.h> +#include <stdint.h> + +#include <memory> + +#include "base/macros.h" +#include "sandbox/win/src/internal_types.h" +#if !defined(SANDBOX_FUZZ_TARGET) +#include "sandbox/win/src/sandbox_nt_types.h" +#endif +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_types.h" + +// This header is part of CrossCall: the sandbox inter-process communication. +// This header defines the basic types used both in the client IPC and in the +// server IPC code. CrossCallParams and ActualCallParams model the input +// parameters of an IPC call and CrossCallReturn models the output params and +// the return value. +// +// An IPC call is defined by its 'tag' which is a (uint32_t) unique identifier +// that is used to route the IPC call to the proper server. Every tag implies +// a complete call signature including the order and type of each parameter. +// +// Like most IPC systems. CrossCall is designed to take as inputs 'simple' +// types such as integers and strings. Classes, generic arrays or pointers to +// them are not supported. +// +// Another limitation of CrossCall is that the return value and output +// parameters can only be uint32_t integers. Returning complex structures or +// strings is not supported. + +namespace sandbox { + +// this is the assumed channel size. This can be overridden in a given +// IPC implementation. +const uint32_t kIPCChannelSize = 1024; + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt; + +namespace { + +// Increases |value| until there is no need for padding given an int64_t +// alignment. Returns the increased value. +inline uint32_t Align(uint32_t value) { + uint32_t alignment = sizeof(int64_t); + return ((value + alignment - 1) / alignment) * alignment; +} + +inline void* memcpy_wrapper(void* dest, const void* src, size_t count) { + if (g_nt.memcpy) + return g_nt.memcpy(dest, src, count); + return memcpy(dest, src, count); +} + +} // namespace + +// max number of extended return parameters. See CrossCallReturn +const size_t kExtendedReturnCount = 8; + +// Union of multiple types to be used as extended results +// in the CrossCallReturn. +union MultiType { + uint32_t unsigned_int; + void* pointer; + HANDLE handle; + ULONG_PTR ulong_ptr; +}; + +// Maximum number of IPC parameters currently supported. +// To increase this value, we have to: +// - Add another Callback typedef to Dispatcher. +// - Add another case to the switch on SharedMemIPCServer::InvokeCallback. +// - Add another case to the switch in GetActualAndMaxBufferSize +// - Add another case to the switch in GetMinDeclaredActualCallParamsSize +const int kMaxIpcParams = 9; + +// Contains the information about a parameter in the ipc buffer. +struct ParamInfo { + ArgType type_; + uint32_t offset_; + uint32_t size_; +}; + +// Models the return value and the return parameters of an IPC call +// currently limited to one status code and eight generic return values +// which cannot be pointers to other data. For x64 ports this structure +// might have to use other integer types. +struct CrossCallReturn { + // the IPC tag. It should match the original IPC tag. + uint32_t tag; + // The result of the IPC operation itself. + ResultCode call_outcome; + // the result of the IPC call as executed in the server. The interpretation + // of this value depends on the specific service. + union { + NTSTATUS nt_status; + DWORD win32_result; + }; + // Number of extended return values. + uint32_t extended_count; + // for calls that should return a windows handle. It is found here. + HANDLE handle; + // The array of extended values. + MultiType extended[kExtendedReturnCount]; +}; + +// CrossCallParams base class that models the input params all packed in a +// single compact memory blob. The representation can vary but in general a +// given child of this class is meant to represent all input parameters +// necessary to make a IPC call. +// +// This class cannot have virtual members because its assumed the IPC +// parameters start from the 'this' pointer to the end, which is defined by +// one of the subclasses +// +// Objects of this class cannot be constructed directly. Only derived +// classes have the proper knowledge to construct it. +class CrossCallParams { + public: + // Returns the tag (ipc unique id) associated with this IPC. + IpcTag GetTag() const { return tag_; } + + // Returns the beggining of the buffer where the IPC params can be stored. + // prior to an IPC call + const void* GetBuffer() const { return this; } + + // Returns how many parameter this IPC call should have. + uint32_t GetParamsCount() const { return params_count_; } + + // Returns a pointer to the CrossCallReturn structure. + CrossCallReturn* GetCallReturn() { return &call_return; } + + // Returns true if this call contains InOut parameters. + bool IsInOut() const { return (1 == is_in_out_); } + + // Tells the CrossCall object if it contains InOut parameters. + void SetIsInOut(bool value) { + if (value) + is_in_out_ = 1; + else + is_in_out_ = 0; + } + + protected: + // constructs the IPC call params. Called only from the derived classes + CrossCallParams(IpcTag tag, uint32_t params_count) + : tag_(tag), is_in_out_(0), params_count_(params_count) {} + + private: + IpcTag tag_; + uint32_t is_in_out_; + CrossCallReturn call_return; + const uint32_t params_count_; + DISALLOW_COPY_AND_ASSIGN(CrossCallParams); +}; + +// ActualCallParams models an specific IPC call parameters with respect to the +// storage allocation that the packed parameters should need. +// NUMBER_PARAMS: the number of parameters, valid from 1 to N +// BLOCK_SIZE: the total storage that the NUMBER_PARAMS parameters can take, +// typically the block size is defined by the channel size of the underlying +// ipc mechanism. +// In practice this class is used to levergage C++ capacity to properly +// calculate sizes and displacements given the possibility of the packed params +// blob to be complex. +// +// As is, this class assumes that the layout of the blob is as follows. Assume +// that NUMBER_PARAMS = 2 and a 32-bit build: +// +// [ tag 4 bytes] +// [ IsOnOut 4 bytes] +// [ call return 52 bytes] +// [ params count 4 bytes] +// [ parameter 0 type 4 bytes] +// [ parameter 0 offset 4 bytes] ---delta to ---\ +// [ parameter 0 size 4 bytes] | +// [ parameter 1 type 4 bytes] | +// [ parameter 1 offset 4 bytes] ---------------|--\ +// [ parameter 1 size 4 bytes] | | +// [ parameter 2 type 4 bytes] | | +// [ parameter 2 offset 4 bytes] ----------------------\ +// [ parameter 2 size 4 bytes] | | | +// |---------------------------| | | | +// | value 0 (x bytes) | <--------------/ | | +// | value 1 (y bytes) | <-----------------/ | +// | | | +// | end of buffer | <---------------------/ +// |---------------------------| +// +// Note that the actual number of params is NUMBER_PARAMS + 1 +// so that the size of each actual param can be computed from the difference +// between one parameter and the next down. The offset of the last param +// points to the end of the buffer and the type and size are undefined. +// +template <size_t NUMBER_PARAMS, size_t BLOCK_SIZE> +class ActualCallParams : public CrossCallParams { + public: + // constructor. Pass the ipc unique tag as input + explicit ActualCallParams(IpcTag tag) : CrossCallParams(tag, NUMBER_PARAMS) { + param_info_[0].offset_ = + static_cast<uint32_t>(parameters_ - reinterpret_cast<char*>(this)); + } + + // Testing-only constructor. Allows setting the |number_params| to a + // wrong value. + ActualCallParams(IpcTag tag, uint32_t number_params) + : CrossCallParams(tag, number_params) { + param_info_[0].offset_ = + static_cast<uint32_t>(parameters_ - reinterpret_cast<char*>(this)); + } + + static constexpr size_t MaxParamsSize() { + return sizeof( + ActualCallParams<NUMBER_PARAMS, kIPCChannelSize>::parameters_); + } + + // Testing-only method. Allows setting the apparent size to a wrong value. + // returns the previous size. + uint32_t OverrideSize(uint32_t new_size) { + uint32_t previous_size = param_info_[NUMBER_PARAMS].offset_; + param_info_[NUMBER_PARAMS].offset_ = new_size; + return previous_size; + } + + // Copies each paramter into the internal buffer. For each you must supply: + // index: 0 for the first param, 1 for the next an so on + bool CopyParamIn(uint32_t index, + const void* parameter_address, + uint32_t size, + bool is_in_out, + ArgType type) { + if (index >= NUMBER_PARAMS) { + return false; + } + + if (UINT32_MAX == size) { + // Memory error while getting the size. + return false; + } + + if (size && !parameter_address) { + return false; + } + + if ((size > sizeof(*this)) || + (param_info_[index].offset_ > (sizeof(*this) - size))) { + // It does not fit, abort copy. + return false; + } + + char* dest = reinterpret_cast<char*>(this) + param_info_[index].offset_; + + // We might be touching user memory, this has to be done from inside a try + // except. + __try { + memcpy_wrapper(dest, parameter_address, size); + } __except (EXCEPTION_EXECUTE_HANDLER) { + return false; + } + + // Set the flag to tell the broker to update the buffer once the call is + // made. + if (is_in_out) + SetIsInOut(true); + + param_info_[index + 1].offset_ = Align(param_info_[index].offset_ + size); + param_info_[index].size_ = size; + param_info_[index].type_ = type; + return true; + } + + // Returns a pointer to a parameter in the memory section. + void* GetParamPtr(size_t index) { + return reinterpret_cast<char*>(this) + param_info_[index].offset_; + } + + // Returns the total size of the buffer. Only valid once all the paramters + // have been copied in with CopyParamIn. + uint32_t GetSize() const { return param_info_[NUMBER_PARAMS].offset_; } + + protected: + ActualCallParams() : CrossCallParams(IpcTag::UNUSED, NUMBER_PARAMS) {} + + private: + ParamInfo param_info_[NUMBER_PARAMS + 1]; + char parameters_[BLOCK_SIZE - sizeof(CrossCallParams) - + sizeof(ParamInfo) * (NUMBER_PARAMS + 1)]; + DISALLOW_COPY_AND_ASSIGN(ActualCallParams); + + friend uint32_t GetMinDeclaredActualCallParamsSize(uint32_t param_count); +}; + +static_assert(sizeof(ActualCallParams<1, 1024>) == 1024, "bad size buffer"); +static_assert(sizeof(ActualCallParams<2, 1024>) == 1024, "bad size buffer"); +static_assert(sizeof(ActualCallParams<3, 1024>) == 1024, "bad size buffer"); + +} // namespace sandbox + +#endif // SANDBOX_SRC_CROSSCALL_PARAMS_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/crosscall_server.cc b/security/sandbox/chromium/sandbox/win/src/crosscall_server.cc new file mode 100644 index 0000000000..27bfdfbd65 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/crosscall_server.cc @@ -0,0 +1,345 @@ +// 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 "sandbox/win/src/crosscall_server.h" + +#include <stddef.h> +#include <stdint.h> + +#include <atomic> +#include <string> +#include <vector> + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/crosscall_params.h" + +// See comment in atomicops.h. This is needed any time windows.h is included +// after atomicops.h. +#undef MemoryBarrier + +// This code performs the ipc message validation. Potential security flaws +// on the ipc are likelier to be found in this code than in the rest of +// the ipc code. + +namespace { + +// The buffer for a message must match the max channel size. +const size_t kMaxBufferSize = sandbox::kIPCChannelSize; + +} // namespace + +namespace sandbox { + +// The template types are used to calculate the maximum expected size. +typedef ActualCallParams<0, kMaxBufferSize> ActualCP0; +typedef ActualCallParams<1, kMaxBufferSize> ActualCP1; +typedef ActualCallParams<2, kMaxBufferSize> ActualCP2; +typedef ActualCallParams<3, kMaxBufferSize> ActualCP3; +typedef ActualCallParams<4, kMaxBufferSize> ActualCP4; +typedef ActualCallParams<5, kMaxBufferSize> ActualCP5; +typedef ActualCallParams<6, kMaxBufferSize> ActualCP6; +typedef ActualCallParams<7, kMaxBufferSize> ActualCP7; +typedef ActualCallParams<8, kMaxBufferSize> ActualCP8; +typedef ActualCallParams<9, kMaxBufferSize> ActualCP9; + +// Returns the actual size for the parameters in an IPC buffer. Returns +// zero if the |param_count| is zero or too big. +uint32_t GetActualBufferSize(uint32_t param_count, void* buffer_base) { + // Retrieve the actual size and the maximum size of the params buffer. + switch (param_count) { + case 0: + return 0; + case 1: + return reinterpret_cast<ActualCP1*>(buffer_base)->GetSize(); + case 2: + return reinterpret_cast<ActualCP2*>(buffer_base)->GetSize(); + case 3: + return reinterpret_cast<ActualCP3*>(buffer_base)->GetSize(); + case 4: + return reinterpret_cast<ActualCP4*>(buffer_base)->GetSize(); + case 5: + return reinterpret_cast<ActualCP5*>(buffer_base)->GetSize(); + case 6: + return reinterpret_cast<ActualCP6*>(buffer_base)->GetSize(); + case 7: + return reinterpret_cast<ActualCP7*>(buffer_base)->GetSize(); + case 8: + return reinterpret_cast<ActualCP8*>(buffer_base)->GetSize(); + case 9: + return reinterpret_cast<ActualCP9*>(buffer_base)->GetSize(); + default: + return 0; + } +} + +// Returns the minimum size for the parameters in an IPC buffer. Returns +// zero if the |param_count| is less than zero or too big. +uint32_t GetMinDeclaredActualCallParamsSize(uint32_t param_count) { + switch (param_count) { + case 0: + return offsetof(ActualCP0, parameters_); + case 1: + return offsetof(ActualCP1, parameters_); + case 2: + return offsetof(ActualCP2, parameters_); + case 3: + return offsetof(ActualCP3, parameters_); + case 4: + return offsetof(ActualCP4, parameters_); + case 5: + return offsetof(ActualCP5, parameters_); + case 6: + return offsetof(ActualCP6, parameters_); + case 7: + return offsetof(ActualCP7, parameters_); + case 8: + return offsetof(ActualCP8, parameters_); + case 9: + return offsetof(ActualCP9, parameters_); + default: + return 0; + } +} + +// Verifies that the declared sizes of an IPC buffer are within range. +bool IsSizeWithinRange(uint32_t buffer_size, + uint32_t min_declared_size, + uint32_t declared_size) { + if ((buffer_size < min_declared_size) || + (sizeof(CrossCallParamsEx) > min_declared_size)) { + // Minimal computed size bigger than existing buffer or param_count + // integer overflow. + return false; + } + + if ((declared_size > buffer_size) || (declared_size < min_declared_size)) { + // Declared size is bigger than buffer or smaller than computed size + // or param_count is equal to 0 or bigger than 9. + return false; + } + + return true; +} + +CrossCallParamsEx::CrossCallParamsEx() : CrossCallParams(IpcTag::UNUSED, 0) {} + +// We override the delete operator because the object's backing memory +// is hand allocated in CreateFromBuffer. We don't override the new operator +// because the constructors are private so there is no way to mismatch +// new & delete. +void CrossCallParamsEx::operator delete(void* raw_memory) throw() { + if (!raw_memory) { + // C++ standard allows 'delete 0' behavior. + return; + } + delete[] reinterpret_cast<char*>(raw_memory); +} + +// This function uses a SEH try block so cannot use C++ objects that +// have destructors or else you get Compiler Error C2712. So no DCHECKs +// inside this function. +CrossCallParamsEx* CrossCallParamsEx::CreateFromBuffer(void* buffer_base, + uint32_t buffer_size, + uint32_t* output_size) { + // IMPORTANT: Everything inside buffer_base and derived from it such + // as param_count and declared_size is untrusted. + if (!buffer_base) + return nullptr; + if (buffer_size < sizeof(CrossCallParams)) + return nullptr; + if (buffer_size > kMaxBufferSize) + return nullptr; + + char* backing_mem = nullptr; + uint32_t param_count = 0; + uint32_t declared_size; + uint32_t min_declared_size; + CrossCallParamsEx* copied_params = nullptr; + + // Touching the untrusted buffer is done under a SEH try block. This + // will catch memory access violations so we don't crash. + __try { + CrossCallParams* call_params = + reinterpret_cast<CrossCallParams*>(buffer_base); + + // Check against the minimum size given the number of stated params + // if too small we bail out. + param_count = call_params->GetParamsCount(); + min_declared_size = GetMinDeclaredActualCallParamsSize(param_count); + + // Initial check for the buffer being big enough to determine the actual + // buffer size. + if (buffer_size < min_declared_size) + return nullptr; + + // Retrieve the declared size which if it fails returns 0. + declared_size = GetActualBufferSize(param_count, buffer_base); + + if (!IsSizeWithinRange(buffer_size, min_declared_size, declared_size)) + return nullptr; + + // Now we copy the actual amount of the message. + *output_size = declared_size; + backing_mem = new char[declared_size]; + copied_params = reinterpret_cast<CrossCallParamsEx*>(backing_mem); + memcpy(backing_mem, call_params, declared_size); + + // Avoid compiler optimizations across this point. Any value stored in + // memory should be stored for real, and values previously read from memory + // should be actually read. + std::atomic_thread_fence(std::memory_order_seq_cst); + + min_declared_size = GetMinDeclaredActualCallParamsSize(param_count); + + // Check that the copied buffer is still valid. + if (copied_params->GetParamsCount() != param_count || + GetActualBufferSize(param_count, backing_mem) != declared_size || + !IsSizeWithinRange(buffer_size, min_declared_size, declared_size)) { + delete[] backing_mem; + return nullptr; + } + + } __except (EXCEPTION_EXECUTE_HANDLER) { + // In case of a windows exception we know it occurred while touching the + // untrusted buffer so we bail out as is. + delete[] backing_mem; + return nullptr; + } + + // Here and below we're making use of uintptr_t to have well-defined integer + // overflow when doing pointer arithmetic. + auto backing_mem_ptr = reinterpret_cast<uintptr_t>(backing_mem); + auto last_byte = reinterpret_cast<uintptr_t>(&backing_mem[declared_size]); + auto first_byte = + reinterpret_cast<uintptr_t>(&backing_mem[min_declared_size]); + + // Verify here that all and each parameters make sense. This is done in the + // local copy. + for (uint32_t ix = 0; ix != param_count; ++ix) { + uint32_t size = 0; + ArgType type; + auto address = reinterpret_cast<uintptr_t>( + copied_params->GetRawParameter(ix, &size, &type)); + if ((!address) || // No null params. + (INVALID_TYPE >= type) || (LAST_TYPE <= type) || // Unknown type. + (address < backing_mem_ptr) || // Start cannot point before buffer. + (address < first_byte) || // Start cannot point too low. + (address > last_byte) || // Start cannot point past buffer. + ((address + size) < address) || // Invalid size. + ((address + size) > last_byte)) { // End cannot point past buffer. + // Malformed. + delete[] backing_mem; + return nullptr; + } + } + // The parameter buffer looks good. + return copied_params; +} + +// Accessors to the parameters in the raw buffer. +void* CrossCallParamsEx::GetRawParameter(uint32_t index, + uint32_t* size, + ArgType* type) { + if (index >= GetParamsCount()) + return nullptr; + // The size is always computed from the parameter minus the next + // parameter, this works because the message has an extra parameter slot + *size = param_info_[index].size_; + *type = param_info_[index].type_; + + return param_info_[index].offset_ + reinterpret_cast<char*>(this); +} + +// Covers common case for 32 bit integers. +bool CrossCallParamsEx::GetParameter32(uint32_t index, uint32_t* param) { + uint32_t size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + if (!start || (4 != size) || (UINT32_TYPE != type)) + return false; + // Copy the 4 bytes. + *(reinterpret_cast<uint32_t*>(param)) = *(reinterpret_cast<uint32_t*>(start)); + return true; +} + +bool CrossCallParamsEx::GetParameterVoidPtr(uint32_t index, void** param) { + uint32_t size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + if (!start || (sizeof(void*) != size) || (VOIDPTR_TYPE != type)) + return false; + *param = *(reinterpret_cast<void**>(start)); + return true; +} + +// Covers the common case of reading a string. Note that the string is not +// scanned for invalid characters. +bool CrossCallParamsEx::GetParameterStr(uint32_t index, std::wstring* string) { + DCHECK(string->empty()); + uint32_t size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + if (WCHAR_TYPE != type) + return false; + + // Check if this is an empty string. + if (size == 0) { + *string = std::wstring(); + return true; + } + + if (!start || ((size % sizeof(wchar_t)) != 0)) + return false; + + string->assign(reinterpret_cast<const wchar_t*>(start), + size / sizeof(wchar_t)); + return true; +} + +bool CrossCallParamsEx::GetParameterPtr(uint32_t index, + uint32_t expected_size, + void** pointer) { + uint32_t size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + + if ((size != expected_size) || (INOUTPTR_TYPE != type && INPTR_TYPE != type)) + return false; + + if (!start) + return false; + + *pointer = start; + return true; +} + +void SetCallError(ResultCode error, CrossCallReturn* call_return) { + call_return->call_outcome = error; + call_return->extended_count = 0; +} + +void SetCallSuccess(CrossCallReturn* call_return) { + call_return->call_outcome = SBOX_ALL_OK; +} + +Dispatcher* Dispatcher::OnMessageReady(IPCParams* ipc, + CallbackGeneric* callback) { + DCHECK(callback); + std::vector<IPCCall>::iterator it = ipc_calls_.begin(); + for (; it != ipc_calls_.end(); ++it) { + if (it->params.Matches(ipc)) { + *callback = it->callback; + return this; + } + } + return nullptr; +} + +Dispatcher::Dispatcher() {} + +Dispatcher::~Dispatcher() {} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/crosscall_server.h b/security/sandbox/chromium/sandbox/win/src/crosscall_server.h new file mode 100644 index 0000000000..aed7f99aae --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/crosscall_server.h @@ -0,0 +1,261 @@ +// 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. + +#ifndef SANDBOX_SRC_CROSSCALL_SERVER_H_ +#define SANDBOX_SRC_CROSSCALL_SERVER_H_ + +#include <stdint.h> + +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/ipc_tags.h" + +// This is the IPC server interface for CrossCall: The IPC for the Sandbox +// On the server, CrossCall needs two things: +// 1) threads: Or better said, someone to provide them, that is what the +// ThreadProvider interface is defined for. These thread(s) are +// the ones that will actually execute the IPC data retrieval. +// +// 2) a dispatcher: This interface represents the way to route and process +// an IPC call given the IPC tag. +// +// The other class included here CrossCallParamsEx is the server side version +// of the CrossCallParams class of /sandbox/crosscall_params.h The difference +// is that the sever version is paranoid about the correctness of the IPC +// message and will do all sorts of verifications. +// +// A general diagram of the interaction is as follows: +// +// ------------ +// | | +// ThreadProvider <--(1)Register--| IPC | +// | | Implemen | +// | | -tation | +// (2) | | OnMessage +// IPC fired --callback ------>| |--(3)---> Dispatcher +// | | +// ------------ +// +// The IPC implementation sits as a middleman between the handling of the +// specifics of scheduling a thread to service the IPC and the multiple +// entities that can potentially serve each particular IPC. +namespace sandbox { + +class InterceptionManager; + +// This function signature is required as the callback when an IPC call fires. +// context: a user-defined pointer that was set using ThreadProvider +// reason: 0 if the callback was fired because of a timeout. +// 1 if the callback was fired because of an event. +typedef void(__stdcall* CrossCallIPCCallback)(void* context, + unsigned char reason); + +// ThreadProvider models a thread factory. The idea is to decouple thread +// creation and lifetime from the inner guts of the IPC. The contract is +// simple: +// - the IPC implementation calls RegisterWait with a waitable object that +// becomes signaled when an IPC arrives and needs to be serviced. +// - when the waitable object becomes signaled, the thread provider conjures +// a thread that calls the callback (CrossCallIPCCallback) function +// - the callback function tries its best not to block and return quickly +// and should not assume that the next callback will use the same thread +// - when the callback returns the ThreadProvider owns again the thread +// and can destroy it or keep it around. +class ThreadProvider { + public: + // Registers a waitable object with the thread provider. + // client: A number to associate with all the RegisterWait calls, typically + // this is the address of the caller object. This parameter cannot + // be zero. + // waitable_object : a kernel object that can be waited on + // callback: a function pointer which is the function that will be called + // when the waitable object fires + // context: a user-provider pointer that is passed back to the callback + // when its called + virtual bool RegisterWait(const void* client, + HANDLE waitable_object, + CrossCallIPCCallback callback, + void* context) = 0; + + // Removes all the registrations done with the same cookie parameter. + // This frees internal thread pool resources. + virtual bool UnRegisterWaits(void* cookie) = 0; + virtual ~ThreadProvider() {} +}; + +// Models the server-side of the original input parameters. +// Provides IPC buffer validation and it is capable of reading the parameters +// out of the IPC buffer. +class CrossCallParamsEx : public CrossCallParams { + public: + // Factory constructor. Pass an IPCbuffer (and buffer size) that contains a + // pending IPCcall. This constructor will: + // 1) validate the IPC buffer. returns nullptr is the IPCbuffer is malformed. + // 2) make a copy of the IPCbuffer (parameter capture) + static CrossCallParamsEx* CreateFromBuffer(void* buffer_base, + uint32_t buffer_size, + uint32_t* output_size); + + // Provides IPCinput parameter raw access: + // index : the parameter to read; 0 is the first parameter + // returns nullptr if the parameter is non-existent. If it exists it also + // returns the size in *size + void* GetRawParameter(uint32_t index, uint32_t* size, ArgType* type); + + // Gets a parameter that is four bytes in size. + // Returns false if the parameter does not exist or is not 32 bits wide. + bool GetParameter32(uint32_t index, uint32_t* param); + + // Gets a parameter that is void pointer in size. + // Returns false if the parameter does not exist or is not void pointer sized. + bool GetParameterVoidPtr(uint32_t index, void** param); + + // Gets a parameter that is a string. Returns false if the parameter does not + // exist. + bool GetParameterStr(uint32_t index, std::wstring* string); + + // Gets a parameter that is an in/out buffer. Returns false is the parameter + // does not exist or if the size of the actual parameter is not equal to the + // expected size. + bool GetParameterPtr(uint32_t index, uint32_t expected_size, void** pointer); + + // Frees the memory associated with the IPC parameters. + static void operator delete(void* raw_memory) throw(); + + private: + // Only the factory method CreateFromBuffer can construct these objects. + CrossCallParamsEx(); + + ParamInfo param_info_[1]; + DISALLOW_COPY_AND_ASSIGN(CrossCallParamsEx); +}; + +// Simple helper function that sets the members of CrossCallReturn +// to the proper state to signal a basic error. +void SetCallError(ResultCode error, CrossCallReturn* call_return); + +// Sets the internal status of call_return to signify the that IPC call +// completed successfully. +void SetCallSuccess(CrossCallReturn* call_return); + +// Represents the client process that initiated the IPC which boils down to the +// process handle and the job object handle that contains the client process. +struct ClientInfo { + HANDLE process; + DWORD process_id; +}; + +// All IPC-related information to be passed to the IPC handler. +struct IPCInfo { + IpcTag ipc_tag; + const ClientInfo* client_info; + CrossCallReturn return_info; +}; + +// This structure identifies IPC signatures. +struct IPCParams { + IpcTag ipc_tag; + ArgType args[kMaxIpcParams]; + + bool Matches(IPCParams* other) const { + return !memcmp(this, other, sizeof(*other)); + } +}; + +// Models an entity that can process an IPC message or it can route to another +// one that could handle it. When an IPC arrives the IPC implementation will: +// 1) call OnMessageReady() with the tag of the pending IPC. If the dispatcher +// returns nullptr it means that it cannot handle this IPC but if it returns +// non-null, it must be the pointer to a dispatcher that can handle it. +// 2) When the IPC finally obtains a valid Dispatcher the IPC +// implementation creates a CrossCallParamsEx from the raw IPC buffer. +// 3) It calls the returned callback, with the IPC info and arguments. +class Dispatcher { + public: + // Called from the IPC implementation to handle a specific IPC message. + typedef bool (Dispatcher::*CallbackGeneric)(); + typedef bool (Dispatcher::*Callback0)(IPCInfo* ipc); + typedef bool (Dispatcher::*Callback1)(IPCInfo* ipc, void* p1); + typedef bool (Dispatcher::*Callback2)(IPCInfo* ipc, void* p1, void* p2); + typedef bool (Dispatcher::*Callback3)(IPCInfo* ipc, + void* p1, + void* p2, + void* p3); + typedef bool (Dispatcher::*Callback4)(IPCInfo* ipc, + void* p1, + void* p2, + void* p3, + void* p4); + typedef bool (Dispatcher::*Callback5)(IPCInfo* ipc, + void* p1, + void* p2, + void* p3, + void* p4, + void* p5); + typedef bool (Dispatcher::*Callback6)(IPCInfo* ipc, + void* p1, + void* p2, + void* p3, + void* p4, + void* p5, + void* p6); + typedef bool (Dispatcher::*Callback7)(IPCInfo* ipc, + void* p1, + void* p2, + void* p3, + void* p4, + void* p5, + void* p6, + void* p7); + typedef bool (Dispatcher::*Callback8)(IPCInfo* ipc, + void* p1, + void* p2, + void* p3, + void* p4, + void* p5, + void* p6, + void* p7, + void* p8); + typedef bool (Dispatcher::*Callback9)(IPCInfo* ipc, + void* p1, + void* p2, + void* p3, + void* p4, + void* p5, + void* p6, + void* p7, + void* p8, + void* p9); + + // Called from the IPC implementation when an IPC message is ready override + // on a derived class to handle a set of IPC messages. Return nullptr if your + // subclass does not handle the message or return the pointer to the subclass + // that can handle it. + virtual Dispatcher* OnMessageReady(IPCParams* ipc, CallbackGeneric* callback); + + // Called when a target proces is created, to setup the interceptions related + // with the given service (IPC). + virtual bool SetupService(InterceptionManager* manager, IpcTag service) = 0; + + Dispatcher(); + virtual ~Dispatcher(); + + protected: + // Structure that defines an IPC Call with all the parameters and the handler. + struct IPCCall { + IPCParams params; + CallbackGeneric callback; + }; + + // List of IPC Calls supported by the class. + std::vector<IPCCall> ipc_calls_; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_CROSSCALL_SERVER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/eat_resolver.cc b/security/sandbox/chromium/sandbox/win/src/eat_resolver.cc new file mode 100644 index 0000000000..f9551ed4b2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/eat_resolver.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2006-2010 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/win/src/eat_resolver.h" + +#include <stddef.h> + +#include "base/win/pe_image.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +NTSTATUS EatResolverThunk::Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = + Init(target_module, interceptor_module, target_name, interceptor_name, + interceptor_entry_point, thunk_storage, storage_bytes); + if (!NT_SUCCESS(ret)) + return ret; + + if (!eat_entry_) + return STATUS_INVALID_PARAMETER; + +#if defined(_WIN64) + // We have two thunks, in order: the return path and the forward path. + if (!SetInternalThunk(thunk_storage, storage_bytes, nullptr, target_)) + return STATUS_BUFFER_TOO_SMALL; + + size_t thunk_bytes = GetInternalThunkSize(); + storage_bytes -= thunk_bytes; + thunk_storage = reinterpret_cast<char*>(thunk_storage) + thunk_bytes; +#endif + + if (!SetInternalThunk(thunk_storage, storage_bytes, target_, interceptor_)) + return STATUS_BUFFER_TOO_SMALL; + + AutoProtectMemory memory; + ret = memory.ChangeProtection(eat_entry_, sizeof(DWORD), PAGE_READWRITE); + if (!NT_SUCCESS(ret)) + return ret; + + // Perform the patch. + *eat_entry_ = static_cast<DWORD>(reinterpret_cast<uintptr_t>(thunk_storage)) - + static_cast<DWORD>(reinterpret_cast<uintptr_t>(target_module)); + + if (storage_used) + *storage_used = GetThunkSize(); + + return ret; +} + +NTSTATUS EatResolverThunk::ResolveTarget(const void* module, + const char* function_name, + void** address) { + DCHECK_NT(address); + if (!module) + return STATUS_INVALID_PARAMETER; + + base::win::PEImage pe(module); + if (!pe.VerifyMagic()) + return STATUS_INVALID_IMAGE_FORMAT; + + eat_entry_ = pe.GetExportEntry(function_name); + + if (!eat_entry_) + return STATUS_PROCEDURE_NOT_FOUND; + + *address = pe.RVAToAddr(*eat_entry_); + + return STATUS_SUCCESS; +} + +size_t EatResolverThunk::GetThunkSize() const { +#if defined(_WIN64) + return GetInternalThunkSize() * 2; +#else + return GetInternalThunkSize(); +#endif +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/eat_resolver.h b/security/sandbox/chromium/sandbox/win/src/eat_resolver.h new file mode 100644 index 0000000000..cbc5516f8c --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/eat_resolver.h @@ -0,0 +1,49 @@ +// Copyright (c) 2010 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. + +#ifndef SANDBOX_SRC_EAT_RESOLVER_H__ +#define SANDBOX_SRC_EAT_RESOLVER_H__ + +#include <stddef.h> + +#include "base/macros.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/resolver.h" + +namespace sandbox { + +// This is the concrete resolver used to perform exports table interceptions. +class EatResolverThunk : public ResolverThunk { + public: + EatResolverThunk() : eat_entry_(nullptr) {} + ~EatResolverThunk() override {} + + // Implementation of Resolver::Setup. + NTSTATUS Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) override; + + // Implementation of Resolver::ResolveTarget. + NTSTATUS ResolveTarget(const void* module, + const char* function_name, + void** address) override; + + // Implementation of Resolver::GetThunkSize. + size_t GetThunkSize() const override; + + private: + // The entry to patch. + DWORD* eat_entry_; + + DISALLOW_COPY_AND_ASSIGN(EatResolverThunk); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_EAT_RESOLVER_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/file_policy_test.cc b/security/sandbox/chromium/sandbox/win/src/file_policy_test.cc new file mode 100644 index 0000000000..4989ab308f --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/file_policy_test.cc @@ -0,0 +1,705 @@ +// Copyright (c) 2011 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 <algorithm> +#include <cctype> + +#include <windows.h> +#include <winioctl.h> + +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/filesystem_policy.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.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" + +#define BINDNTDLL(name) \ + name##Function name = reinterpret_cast<name##Function>( \ + ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), #name)) + +namespace sandbox { + +const ULONG kSharing = FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE; + +// Creates a file using different desired access. Returns if the call succeeded +// or not. The first argument in argv is the filename. The second argument +// determines the type of access and the dispositino of the file. +SBOX_TESTS_COMMAND int File_Create(int argc, wchar_t** argv) { + if (argc != 2) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + std::wstring operation(argv[0]); + + if (operation == L"Read") { + base::win::ScopedHandle file1(CreateFile( + argv[1], GENERIC_READ, kSharing, nullptr, OPEN_EXISTING, 0, nullptr)); + base::win::ScopedHandle file2(CreateFile( + argv[1], FILE_EXECUTE, kSharing, nullptr, OPEN_EXISTING, 0, nullptr)); + + if (file1.IsValid() == file2.IsValid()) + return file1.IsValid() ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED; + return file1.IsValid() ? SBOX_TEST_FIRST_ERROR : SBOX_TEST_SECOND_ERROR; + + } else if (operation == L"Write") { + base::win::ScopedHandle file1(CreateFile( + argv[1], GENERIC_ALL, kSharing, nullptr, OPEN_EXISTING, 0, nullptr)); + base::win::ScopedHandle file2( + CreateFile(argv[1], GENERIC_READ | FILE_WRITE_DATA, kSharing, nullptr, + OPEN_EXISTING, 0, nullptr)); + + if (file1.IsValid() == file2.IsValid()) + return file1.IsValid() ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED; + return file1.IsValid() ? SBOX_TEST_FIRST_ERROR : SBOX_TEST_SECOND_ERROR; + + } else if (operation == L"ReadCreate") { + base::win::ScopedHandle file2(CreateFile(argv[1], GENERIC_READ, kSharing, + nullptr, CREATE_NEW, 0, nullptr)); + base::win::ScopedHandle file1(CreateFile( + argv[1], GENERIC_READ, kSharing, nullptr, CREATE_ALWAYS, 0, nullptr)); + + if (file1.IsValid() == file2.IsValid()) + return file1.IsValid() ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED; + return file1.IsValid() ? SBOX_TEST_FIRST_ERROR : SBOX_TEST_SECOND_ERROR; + } + + return SBOX_TEST_INVALID_PARAMETER; +} + +SBOX_TESTS_COMMAND int File_Win32Create(int argc, wchar_t** argv) { + if (argc != 1) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + + std::wstring full_path = MakePathToSys(argv[0], false); + if (full_path.empty()) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + + HANDLE file = + ::CreateFileW(full_path.c_str(), GENERIC_READ, kSharing, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + + if (INVALID_HANDLE_VALUE != file) { + ::CloseHandle(file); + return SBOX_TEST_SUCCEEDED; + } else { + if (ERROR_ACCESS_DENIED == ::GetLastError()) { + return SBOX_TEST_DENIED; + } else { + return SBOX_TEST_FAILED; + } + } + return SBOX_TEST_SUCCEEDED; +} + +// Creates the file in parameter using the NtCreateFile api and returns if the +// call succeeded or not. +SBOX_TESTS_COMMAND int File_CreateSys32(int argc, wchar_t** argv) { + BINDNTDLL(NtCreateFile); + BINDNTDLL(RtlInitUnicodeString); + if (!NtCreateFile || !RtlInitUnicodeString) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + std::wstring file(argv[0]); + if (0 != _wcsnicmp(file.c_str(), kNTDevicePrefix, kNTDevicePrefixLen)) + file = MakePathToSys(argv[0], true); + + UNICODE_STRING object_name; + RtlInitUnicodeString(&object_name, file.c_str()); + + OBJECT_ATTRIBUTES obj_attributes = {}; + InitializeObjectAttributes(&obj_attributes, &object_name, + OBJ_CASE_INSENSITIVE, nullptr, nullptr); + + HANDLE handle; + IO_STATUS_BLOCK io_block = {}; + NTSTATUS status = + NtCreateFile(&handle, FILE_READ_DATA, &obj_attributes, &io_block, nullptr, + 0, kSharing, FILE_OPEN, 0, nullptr, 0); + if (NT_SUCCESS(status)) { + ::CloseHandle(handle); + return SBOX_TEST_SUCCEEDED; + } else if (STATUS_ACCESS_DENIED == status) { + return SBOX_TEST_DENIED; + } else if (STATUS_OBJECT_NAME_NOT_FOUND == status) { + return SBOX_TEST_NOT_FOUND; + } + return SBOX_TEST_FAILED; +} + +// Opens the file in parameter using the NtOpenFile api and returns if the +// call succeeded or not. +SBOX_TESTS_COMMAND int File_OpenSys32(int argc, wchar_t** argv) { + BINDNTDLL(NtOpenFile); + BINDNTDLL(RtlInitUnicodeString); + if (!NtOpenFile || !RtlInitUnicodeString) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + std::wstring file = MakePathToSys(argv[0], true); + UNICODE_STRING object_name; + RtlInitUnicodeString(&object_name, file.c_str()); + + OBJECT_ATTRIBUTES obj_attributes = {}; + InitializeObjectAttributes(&obj_attributes, &object_name, + OBJ_CASE_INSENSITIVE, nullptr, nullptr); + + HANDLE handle; + IO_STATUS_BLOCK io_block = {}; + NTSTATUS status = NtOpenFile(&handle, FILE_READ_DATA, &obj_attributes, + &io_block, kSharing, 0); + if (NT_SUCCESS(status)) { + ::CloseHandle(handle); + return SBOX_TEST_SUCCEEDED; + } else if (STATUS_ACCESS_DENIED == status) { + return SBOX_TEST_DENIED; + } else if (STATUS_OBJECT_NAME_NOT_FOUND == status) { + return SBOX_TEST_NOT_FOUND; + } + return SBOX_TEST_FAILED; +} + +SBOX_TESTS_COMMAND int File_GetDiskSpace(int argc, wchar_t** argv) { + std::wstring sys_path = MakePathToSys(L"", false); + if (sys_path.empty()) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + ULARGE_INTEGER free_user = {}; + ULARGE_INTEGER total = {}; + ULARGE_INTEGER free_total = {}; + if (::GetDiskFreeSpaceExW(sys_path.c_str(), &free_user, &total, + &free_total)) { + if ((total.QuadPart != 0) && (free_total.QuadPart != 0)) { + return SBOX_TEST_SUCCEEDED; + } + } else { + if (ERROR_ACCESS_DENIED == ::GetLastError()) { + return SBOX_TEST_DENIED; + } else { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + } + return SBOX_TEST_SUCCEEDED; +} + +// Move a file using the MoveFileEx api and returns if the call succeeded or +// not. +SBOX_TESTS_COMMAND int File_Rename(int argc, wchar_t** argv) { + if (argc != 2) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (::MoveFileEx(argv[0], argv[1], 0)) + return SBOX_TEST_SUCCEEDED; + + if (::GetLastError() != ERROR_ACCESS_DENIED) + return SBOX_TEST_FAILED; + + return SBOX_TEST_DENIED; +} + +// Query the attributes of file in parameter using the NtQueryAttributesFile api +// and NtQueryFullAttributesFile and returns if the call succeeded or not. The +// second argument in argv is "d" or "f" telling if we expect the attributes to +// specify a file or a directory. The expected attribute has to match the real +// attributes for the call to be successful. +SBOX_TESTS_COMMAND int File_QueryAttributes(int argc, wchar_t** argv) { + BINDNTDLL(NtQueryAttributesFile); + BINDNTDLL(NtQueryFullAttributesFile); + BINDNTDLL(RtlInitUnicodeString); + if (!NtQueryAttributesFile || !NtQueryFullAttributesFile || + !RtlInitUnicodeString) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (argc != 2) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + bool expect_directory = (L'd' == argv[1][0]); + + UNICODE_STRING object_name; + std::wstring file = MakePathToSys(argv[0], true); + RtlInitUnicodeString(&object_name, file.c_str()); + + OBJECT_ATTRIBUTES obj_attributes = {}; + InitializeObjectAttributes(&obj_attributes, &object_name, + OBJ_CASE_INSENSITIVE, nullptr, nullptr); + + FILE_BASIC_INFORMATION info = {}; + FILE_NETWORK_OPEN_INFORMATION full_info = {}; + NTSTATUS status1 = NtQueryAttributesFile(&obj_attributes, &info); + NTSTATUS status2 = NtQueryFullAttributesFile(&obj_attributes, &full_info); + + if (status1 != status2) + return SBOX_TEST_FAILED; + + if (NT_SUCCESS(status1)) { + if (info.FileAttributes != full_info.FileAttributes) + return SBOX_TEST_FAILED; + + bool is_directory1 = (info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + if (expect_directory == is_directory1) + return SBOX_TEST_SUCCEEDED; + } else if (STATUS_ACCESS_DENIED == status1) { + return SBOX_TEST_DENIED; + } else if (STATUS_OBJECT_NAME_NOT_FOUND == status1) { + return SBOX_TEST_NOT_FOUND; + } + + return SBOX_TEST_FAILED; +} + +// Tries to create a backup of calc.exe in system32 folder. This should fail +// with ERROR_ACCESS_DENIED if everything is working as expected. +SBOX_TESTS_COMMAND int File_CopyFile(int argc, wchar_t** argv) { + std::wstring calc_path = MakePathToSys(L"calc.exe", false); + std::wstring calc_backup_path = MakePathToSys(L"calc.exe.bak", false); + + if (::CopyFile(calc_path.c_str(), calc_backup_path.c_str(), FALSE)) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (::GetLastError() != ERROR_ACCESS_DENIED) + return SBOX_TEST_FAILED; + + return SBOX_TEST_SUCCEEDED; +} + +TEST(FilePolicyTest, DenyNtCreateCalc) { + TestRunner runner; + EXPECT_TRUE( + runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_DIR_ANY, L"calc.exe")); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_CreateSys32 calc.exe")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_CreateSys32 calc.exe")); +} + +TEST(FilePolicyTest, AllowNtCreateCalc) { + TestRunner runner; + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"calc.exe")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_CreateSys32 calc.exe")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_CreateSys32 calc.exe")); +} + +TEST(FilePolicyTest, AllowNtCreateWithNativePath) { + std::wstring calc = MakePathToSys(L"calc.exe", false); + std::wstring nt_path; + ASSERT_TRUE(GetNtPathFromWin32Path(calc, &nt_path)); + TestRunner runner; + runner.AddFsRule(TargetPolicy::FILES_ALLOW_READONLY, nt_path.c_str()); + + wchar_t buff[MAX_PATH]; + ::wsprintfW(buff, L"File_CreateSys32 %s", nt_path.c_str()); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(buff)); + + for (wchar_t& c : nt_path) + c = std::tolower(c); + ::wsprintfW(buff, L"File_CreateSys32 %s", nt_path.c_str()); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(buff)); +} + +TEST(FilePolicyTest, AllowReadOnly) { + TestRunner runner; + + // Create a temp file because we need write access to it. + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name), 0u); + + EXPECT_TRUE( + runner.AddFsRule(TargetPolicy::FILES_ALLOW_READONLY, temp_file_name)); + + wchar_t command_read[MAX_PATH + 20] = {}; + wsprintf(command_read, L"File_Create Read \"%ls\"", temp_file_name); + wchar_t command_read_create[MAX_PATH + 20] = {}; + wsprintf(command_read_create, L"File_Create ReadCreate \"%ls\"", + temp_file_name); + wchar_t command_write[MAX_PATH + 20] = {}; + wsprintf(command_write, L"File_Create Write \"%ls\"", temp_file_name); + + // Verify that we cannot create the file after revert. + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command_read_create)); + + // Verify that we don't have write access after revert. + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command_write)); + + // Verify that we have read access after revert. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command_read)); + + // Verify that we really have write access to the file. + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command_write)); + + DeleteFile(temp_file_name); +} + +// Tests support of "\\\\.\\DeviceName" kind of paths. +TEST(FilePolicyTest, AllowImplicitDeviceName) { + TestRunner runner; + + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name), 0u); + + std::wstring path(temp_file_name); + EXPECT_TRUE(ConvertToLongPath(&path)); + EXPECT_TRUE(GetNtPathFromWin32Path(path, &path)); + path = path.substr(sandbox::kNTDevicePrefixLen); + + wchar_t command[MAX_PATH + 20] = {}; + wsprintf(command, L"File_Create Read \"\\\\.\\%ls\"", path.c_str()); + path = std::wstring(kNTPrefix) + path; + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); + EXPECT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, path.c_str())); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command)); + + DeleteFile(temp_file_name); +} + +TEST(FilePolicyTest, AllowWildcard) { + TestRunner runner; + + // Create a temp file because we need write access to it. + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name), 0u); + + wcscat_s(temp_directory, MAX_PATH, L"*"); + EXPECT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_directory)); + + wchar_t command_write[MAX_PATH + 20] = {}; + wsprintf(command_write, L"File_Create Write \"%ls\"", temp_file_name); + + // Verify that we have write access after revert. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command_write)); + + DeleteFile(temp_file_name); +} + +TEST(FilePolicyTest, AllowNtCreatePatternRule) { + TestRunner runner; + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"App*.dll")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_OpenSys32 apphelp.dll")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_OpenSys32 appwiz.cpl")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_OpenSys32 apphelp.dll")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_OpenSys32 appwiz.cpl")); +} + +TEST(FilePolicyTest, CheckNotFound) { + TestRunner runner; + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"n*.dll")); + + EXPECT_EQ(SBOX_TEST_NOT_FOUND, + runner.RunTest(L"File_OpenSys32 notfound.dll")); +} + +TEST(FilePolicyTest, CheckNoLeak) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_CreateSys32 notfound.exe")); +} + +TEST(FilePolicyTest, TestQueryAttributesFile) { + TestRunner runner; + EXPECT_TRUE( + runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"apphelp.dll")); + EXPECT_TRUE( + runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"notfound.exe")); + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"drivers")); + EXPECT_TRUE( + runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_QUERY, L"ipconfig.exe")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_QueryAttributes drivers d")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_QueryAttributes apphelp.dll f")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_QueryAttributes ipconfig.exe f")); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"File_QueryAttributes ftp.exe f")); + + EXPECT_EQ(SBOX_TEST_NOT_FOUND, + runner.RunTest(L"File_QueryAttributes notfound.exe f")); +} + +// Makes sure that we don't leak information when there is not policy to allow +// a path. +TEST(FilePolicyTest, TestQueryAttributesFileNoPolicy) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"File_QueryAttributes ftp.exe f")); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"File_QueryAttributes notfound.exe f")); +} + +TEST(FilePolicyTest, TestRename) { + TestRunner runner; + + // Give access to the temp directory. + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name1[MAX_PATH]; + wchar_t temp_file_name2[MAX_PATH]; + wchar_t temp_file_name3[MAX_PATH]; + wchar_t temp_file_name4[MAX_PATH]; + wchar_t temp_file_name5[MAX_PATH]; + wchar_t temp_file_name6[MAX_PATH]; + wchar_t temp_file_name7[MAX_PATH]; + wchar_t temp_file_name8[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name1), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name2), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name3), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name4), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name5), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name6), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name7), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name8), 0u); + + // Add rules to make file1->file2 succeed. + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_file_name1)); + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_file_name2)); + + // Add rules to make file3->file4 fail. + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_file_name3)); + ASSERT_TRUE( + runner.AddFsRule(TargetPolicy::FILES_ALLOW_READONLY, temp_file_name4)); + + // Add rules to make file5->file6 fail. + ASSERT_TRUE( + runner.AddFsRule(TargetPolicy::FILES_ALLOW_READONLY, temp_file_name5)); + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_file_name6)); + + // Add rules to make file7->no_pol_file fail. + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_file_name7)); + + // Delete the files where the files are going to be renamed to. + ::DeleteFile(temp_file_name2); + ::DeleteFile(temp_file_name4); + ::DeleteFile(temp_file_name6); + ::DeleteFile(temp_file_name8); + + wchar_t command[MAX_PATH * 2 + 20] = {}; + wsprintf(command, L"File_Rename \"%ls\" \"%ls\"", temp_file_name1, + temp_file_name2); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command)); + + wsprintf(command, L"File_Rename \"%ls\" \"%ls\"", temp_file_name3, + temp_file_name4); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); + + wsprintf(command, L"File_Rename \"%ls\" \"%ls\"", temp_file_name5, + temp_file_name6); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); + + wsprintf(command, L"File_Rename \"%ls\" \"%ls\"", temp_file_name7, + temp_file_name8); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); + + // Delete all the files in case they are still there. + ::DeleteFile(temp_file_name1); + ::DeleteFile(temp_file_name2); + ::DeleteFile(temp_file_name3); + ::DeleteFile(temp_file_name4); + ::DeleteFile(temp_file_name5); + ::DeleteFile(temp_file_name6); + ::DeleteFile(temp_file_name7); + ::DeleteFile(temp_file_name8); +} + +TEST(FilePolicyTest, OpenSys32FilesDenyBecauseOfDir) { + TestRunner runner; + EXPECT_TRUE( + runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_DIR_ANY, L"notepad.exe")); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_Win32Create notepad.exe")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_Win32Create notepad.exe")); +} + +TEST(FilePolicyTest, OpenSys32FilesAllowNotepad) { + TestRunner runner; + EXPECT_TRUE( + runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"notepad.exe")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_Win32Create notepad.exe")); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_Win32Create calc.exe")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_Win32Create notepad.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_Win32Create calc.exe")); +} + +TEST(FilePolicyTest, FileGetDiskSpace) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_GetDiskSpace")); + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_GetDiskSpace")); + + // Add an 'allow' rule in the windows\system32 such that GetDiskFreeSpaceEx + // succeeds (it does an NtOpenFile) but windows\system32\notepad.exe is + // denied since there is no wild card in the rule. + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_DIR_ANY, L"")); + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_GetDiskSpace")); + + runner.SetTestState(AFTER_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_GetDiskSpace")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_Win32Create notepad.exe")); +} + +TEST(FilePolicyTest, TestReparsePoint) { + TestRunner runner; + + // Create a temp file because we need write access to it. + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name), 0u); + + // Delete the file and create a directory instead. + ASSERT_TRUE(::DeleteFile(temp_file_name)); + ASSERT_TRUE(::CreateDirectory(temp_file_name, nullptr)); + + // Create a temporary file in the subfolder. + std::wstring subfolder = temp_file_name; + std::wstring temp_file_title = subfolder.substr(subfolder.rfind(L"\\") + 1); + std::wstring temp_file = subfolder + L"\\file_" + temp_file_title; + + HANDLE file = ::CreateFile(temp_file.c_str(), FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + CREATE_ALWAYS, 0, nullptr); + ASSERT_TRUE(INVALID_HANDLE_VALUE != file); + ASSERT_TRUE(::CloseHandle(file)); + + // Create a temporary file in the temp directory. + std::wstring temp_dir = temp_directory; + std::wstring temp_file_in_temp = temp_dir + L"file_" + temp_file_title; + file = ::CreateFile(temp_file_in_temp.c_str(), FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + CREATE_ALWAYS, 0, nullptr); + ASSERT_TRUE(INVALID_HANDLE_VALUE != file); + ASSERT_TRUE(::CloseHandle(file)); + + // Give write access to the temp directory. + std::wstring temp_dir_wildcard = temp_dir + L"*"; + EXPECT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, + temp_dir_wildcard.c_str())); + + // Prepare the command to execute. + std::wstring command_write; + command_write += L"File_Create Write \""; + command_write += temp_file; + command_write += L"\""; + + // Verify that we have write access to the original file + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command_write.c_str())); + + // Replace the subfolder by a reparse point to %temp%. + ::DeleteFile(temp_file.c_str()); + HANDLE dir = ::CreateFile(subfolder.c_str(), FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + EXPECT_TRUE(INVALID_HANDLE_VALUE != dir); + + std::wstring temp_dir_nt; + temp_dir_nt += L"\\??\\"; + temp_dir_nt += temp_dir; + EXPECT_TRUE(SetReparsePoint(dir, temp_dir_nt.c_str())); + EXPECT_TRUE(::CloseHandle(dir)); + + // Try to open the file again. + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command_write.c_str())); + + // Remove the reparse point. + dir = ::CreateFile(subfolder.c_str(), FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, + nullptr); + EXPECT_TRUE(INVALID_HANDLE_VALUE != dir); + EXPECT_TRUE(DeleteReparsePoint(dir)); + EXPECT_TRUE(::CloseHandle(dir)); + + // Cleanup. + EXPECT_TRUE(::DeleteFile(temp_file_in_temp.c_str())); + EXPECT_TRUE(::RemoveDirectory(subfolder.c_str())); +} + +TEST(FilePolicyTest, CheckExistingNTPrefixEscape) { + std::wstring name = L"\\??\\NAME"; + + std::wstring result = FixNTPrefixForMatch(name); + + EXPECT_STREQ(result.c_str(), L"\\/?/?\\NAME"); +} + +TEST(FilePolicyTest, CheckEscapedNTPrefixNoEscape) { + std::wstring name = L"\\/?/?\\NAME"; + + std::wstring result = FixNTPrefixForMatch(name); + + EXPECT_STREQ(result.c_str(), name.c_str()); +} + +TEST(FilePolicyTest, CheckMissingNTPrefixEscape) { + std::wstring name = L"C:\\NAME"; + + std::wstring result = FixNTPrefixForMatch(name); + + EXPECT_STREQ(result.c_str(), L"\\/?/?\\C:\\NAME"); +} + +TEST(FilePolicyTest, TestCopyFile) { + // Check if the test is running Win8 or newer since + // MITIGATION_STRICT_HANDLE_CHECKS is not supported on older systems. + if (base::win::GetVersion() < base::win::Version::WIN8) + return; + + TestRunner runner; + runner.SetTimeout(2000); + + // Allow read access to calc.exe, this should be on all Windows versions. + ASSERT_TRUE( + runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_READONLY, L"calc.exe")); + + sandbox::TargetPolicy* policy = runner.GetPolicy(); + + // Set proper mitigation. + EXPECT_EQ( + policy->SetDelayedProcessMitigations(MITIGATION_STRICT_HANDLE_CHECKS), + SBOX_ALL_OK); + + ASSERT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_CopyFile")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/filesystem_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/filesystem_dispatcher.cc new file mode 100644 index 0000000000..c0f1de07fb --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/filesystem_dispatcher.cc @@ -0,0 +1,302 @@ +// Copyright (c) 2006-2010 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/win/src/filesystem_dispatcher.h" + +#include <stdint.h> + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/filesystem_interception.h" +#include "sandbox/win/src/filesystem_policy.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +FilesystemDispatcher::FilesystemDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall create_params = { + {IpcTag::NTCREATEFILE, + {WCHAR_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE, + UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>(&FilesystemDispatcher::NtCreateFile)}; + + static const IPCCall open_file = { + {IpcTag::NTOPENFILE, + {WCHAR_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>(&FilesystemDispatcher::NtOpenFile)}; + + static const IPCCall attribs = { + {IpcTag::NTQUERYATTRIBUTESFILE, {WCHAR_TYPE, UINT32_TYPE, INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &FilesystemDispatcher::NtQueryAttributesFile)}; + + static const IPCCall full_attribs = { + {IpcTag::NTQUERYFULLATTRIBUTESFILE, + {WCHAR_TYPE, UINT32_TYPE, INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &FilesystemDispatcher::NtQueryFullAttributesFile)}; + + static const IPCCall set_info = { + {IpcTag::NTSETINFO_RENAME, + {VOIDPTR_TYPE, INOUTPTR_TYPE, INOUTPTR_TYPE, UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &FilesystemDispatcher::NtSetInformationFile)}; + + ipc_calls_.push_back(create_params); + ipc_calls_.push_back(open_file); + ipc_calls_.push_back(attribs); + ipc_calls_.push_back(full_attribs); + ipc_calls_.push_back(set_info); +} + +bool FilesystemDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + switch (service) { + case IpcTag::NTCREATEFILE: + return INTERCEPT_NT(manager, NtCreateFile, CREATE_FILE_ID, 48); + + case IpcTag::NTOPENFILE: + return INTERCEPT_NT(manager, NtOpenFile, OPEN_FILE_ID, 28); + + case IpcTag::NTQUERYATTRIBUTESFILE: + return INTERCEPT_NT(manager, NtQueryAttributesFile, QUERY_ATTRIB_FILE_ID, + 12); + + case IpcTag::NTQUERYFULLATTRIBUTESFILE: + return INTERCEPT_NT(manager, NtQueryFullAttributesFile, + QUERY_FULL_ATTRIB_FILE_ID, 12); + + case IpcTag::NTSETINFO_RENAME: + return INTERCEPT_NT(manager, NtSetInformationFile, SET_INFO_FILE_ID, 24); + + default: + return false; + } +} + +bool FilesystemDispatcher::NtCreateFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + uint32_t desired_access, + uint32_t file_attributes, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options) { + if (!PreProcessName(name)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + const wchar_t* filename = name->c_str(); + + uint32_t broker = BROKER_TRUE; + CountedParameterSet<OpenFile> params; + params[OpenFile::NAME] = ParamPickerMake(filename); + params[OpenFile::ACCESS] = ParamPickerMake(desired_access); + params[OpenFile::DISPOSITION] = ParamPickerMake(create_disposition); + params[OpenFile::OPTIONS] = ParamPickerMake(create_options); + params[OpenFile::BROKER] = ParamPickerMake(broker); + + // To evaluate the policy we need to call back to the policy object. We + // are just middlemen in the operation since is the FileSystemPolicy which + // knows what to do. + EvalResult result = + policy_base_->EvalPolicy(IpcTag::NTCREATEFILE, params.GetBase()); + HANDLE handle; + ULONG_PTR io_information = 0; + NTSTATUS nt_status; + if (!FileSystemPolicy::CreateFileAction( + result, *ipc->client_info, *name, attributes, desired_access, + file_attributes, share_access, create_disposition, create_options, + &handle, &nt_status, &io_information)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + // Return operation status on the IPC. + ipc->return_info.extended[0].ulong_ptr = io_information; + ipc->return_info.nt_status = nt_status; + ipc->return_info.handle = handle; + return true; +} + +bool FilesystemDispatcher::NtOpenFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + uint32_t desired_access, + uint32_t share_access, + uint32_t open_options) { + if (!PreProcessName(name)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + const wchar_t* filename = name->c_str(); + + uint32_t broker = BROKER_TRUE; + uint32_t create_disposition = FILE_OPEN; + CountedParameterSet<OpenFile> params; + params[OpenFile::NAME] = ParamPickerMake(filename); + params[OpenFile::ACCESS] = ParamPickerMake(desired_access); + params[OpenFile::DISPOSITION] = ParamPickerMake(create_disposition); + params[OpenFile::OPTIONS] = ParamPickerMake(open_options); + params[OpenFile::BROKER] = ParamPickerMake(broker); + + // To evaluate the policy we need to call back to the policy object. We + // are just middlemen in the operation since is the FileSystemPolicy which + // knows what to do. + EvalResult result = + policy_base_->EvalPolicy(IpcTag::NTOPENFILE, params.GetBase()); + HANDLE handle; + ULONG_PTR io_information = 0; + NTSTATUS nt_status; + if (!FileSystemPolicy::OpenFileAction( + result, *ipc->client_info, *name, attributes, desired_access, + share_access, open_options, &handle, &nt_status, &io_information)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + // Return operation status on the IPC. + ipc->return_info.extended[0].ulong_ptr = io_information; + ipc->return_info.nt_status = nt_status; + ipc->return_info.handle = handle; + return true; +} + +bool FilesystemDispatcher::NtQueryAttributesFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + CountedBuffer* info) { + if (sizeof(FILE_BASIC_INFORMATION) != info->Size()) + return false; + + if (!PreProcessName(name)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + uint32_t broker = BROKER_TRUE; + const wchar_t* filename = name->c_str(); + CountedParameterSet<FileName> params; + params[FileName::NAME] = ParamPickerMake(filename); + params[FileName::BROKER] = ParamPickerMake(broker); + + // To evaluate the policy we need to call back to the policy object. We + // are just middlemen in the operation since is the FileSystemPolicy which + // knows what to do. + EvalResult result = + policy_base_->EvalPolicy(IpcTag::NTQUERYATTRIBUTESFILE, params.GetBase()); + + FILE_BASIC_INFORMATION* information = + reinterpret_cast<FILE_BASIC_INFORMATION*>(info->Buffer()); + NTSTATUS nt_status; + if (!FileSystemPolicy::QueryAttributesFileAction(result, *ipc->client_info, + *name, attributes, + information, &nt_status)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + // Return operation status on the IPC. + ipc->return_info.nt_status = nt_status; + return true; +} + +bool FilesystemDispatcher::NtQueryFullAttributesFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + CountedBuffer* info) { + if (sizeof(FILE_NETWORK_OPEN_INFORMATION) != info->Size()) + return false; + + if (!PreProcessName(name)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + uint32_t broker = BROKER_TRUE; + const wchar_t* filename = name->c_str(); + CountedParameterSet<FileName> params; + params[FileName::NAME] = ParamPickerMake(filename); + params[FileName::BROKER] = ParamPickerMake(broker); + + // To evaluate the policy we need to call back to the policy object. We + // are just middlemen in the operation since is the FileSystemPolicy which + // knows what to do. + EvalResult result = policy_base_->EvalPolicy( + IpcTag::NTQUERYFULLATTRIBUTESFILE, params.GetBase()); + + FILE_NETWORK_OPEN_INFORMATION* information = + reinterpret_cast<FILE_NETWORK_OPEN_INFORMATION*>(info->Buffer()); + NTSTATUS nt_status; + if (!FileSystemPolicy::QueryFullAttributesFileAction( + result, *ipc->client_info, *name, attributes, information, + &nt_status)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + // Return operation status on the IPC. + ipc->return_info.nt_status = nt_status; + return true; +} + +bool FilesystemDispatcher::NtSetInformationFile(IPCInfo* ipc, + HANDLE handle, + CountedBuffer* status, + CountedBuffer* info, + uint32_t length, + uint32_t info_class) { + if (sizeof(IO_STATUS_BLOCK) != status->Size()) + return false; + if (length != info->Size()) + return false; + + FILE_RENAME_INFORMATION* rename_info = + reinterpret_cast<FILE_RENAME_INFORMATION*>(info->Buffer()); + + if (!IsSupportedRenameCall(rename_info, length, info_class)) + return false; + + std::wstring name; + name.assign(rename_info->FileName, + rename_info->FileNameLength / sizeof(rename_info->FileName[0])); + if (!PreProcessName(&name)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + uint32_t broker = BROKER_TRUE; + const wchar_t* filename = name.c_str(); + CountedParameterSet<FileName> params; + params[FileName::NAME] = ParamPickerMake(filename); + params[FileName::BROKER] = ParamPickerMake(broker); + + // To evaluate the policy we need to call back to the policy object. We + // are just middlemen in the operation since is the FileSystemPolicy which + // knows what to do. + EvalResult result = + policy_base_->EvalPolicy(IpcTag::NTSETINFO_RENAME, params.GetBase()); + + IO_STATUS_BLOCK* io_status = + reinterpret_cast<IO_STATUS_BLOCK*>(status->Buffer()); + NTSTATUS nt_status; + if (!FileSystemPolicy::SetInformationFileAction( + result, *ipc->client_info, handle, rename_info, length, info_class, + io_status, &nt_status)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + // Return operation status on the IPC. + ipc->return_info.nt_status = nt_status; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/filesystem_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/filesystem_dispatcher.h new file mode 100644 index 0000000000..5551656e62 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/filesystem_dispatcher.h @@ -0,0 +1,76 @@ +// Copyright (c) 2010 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. + +#ifndef SANDBOX_SRC_FILESYSTEM_DISPATCHER_H__ +#define SANDBOX_SRC_FILESYSTEM_DISPATCHER_H__ + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles file system-related IPC calls. +class FilesystemDispatcher : public Dispatcher { + public: + explicit FilesystemDispatcher(PolicyBase* policy_base); + ~FilesystemDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Processes IPC requests coming from calls to NtCreateFile in the target. + bool NtCreateFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + uint32_t desired_access, + uint32_t file_attributes, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options); + + // Processes IPC requests coming from calls to NtOpenFile in the target. + bool NtOpenFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + uint32_t desired_access, + uint32_t share_access, + uint32_t create_options); + + // Processes IPC requests coming from calls to NtQueryAttributesFile in the + // target. + bool NtQueryAttributesFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + CountedBuffer* info); + + // Processes IPC requests coming from calls to NtQueryFullAttributesFile in + // the target. + bool NtQueryFullAttributesFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + CountedBuffer* info); + + // Processes IPC requests coming from calls to NtSetInformationFile with the + // rename information class. + bool NtSetInformationFile(IPCInfo* ipc, + HANDLE handle, + CountedBuffer* status, + CountedBuffer* info, + uint32_t length, + uint32_t info_class); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(FilesystemDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_FILESYSTEM_DISPATCHER_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/filesystem_interception.cc b/security/sandbox/chromium/sandbox/win/src/filesystem_interception.cc new file mode 100644 index 0000000000..200dd1f63d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/filesystem_interception.cc @@ -0,0 +1,412 @@ +// Copyright (c) 2006-2008 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/win/src/filesystem_interception.h" + +#include <stdint.h> + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/filesystem_policy.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" +#include "mozilla/sandboxing/sandboxLogging.h" + +// This status occurs when trying to access a network share on the machine from +// which it is shared. +#define STATUS_NETWORK_OPEN_RESTRICTION ((NTSTATUS)0xC0000201L) + +namespace sandbox { + +NTSTATUS WINAPI TargetNtCreateFile(NtCreateFileFunction orig_CreateFile, + PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + PLARGE_INTEGER allocation_size, + ULONG file_attributes, + ULONG sharing, + ULONG disposition, + ULONG options, + PVOID ea_buffer, + ULONG ea_length) { + // Check if the process can open it first. + NTSTATUS status = orig_CreateFile( + file, desired_access, object_attributes, io_status, allocation_size, + file_attributes, sharing, disposition, options, ea_buffer, ea_length); + if (STATUS_ACCESS_DENIED != status && + STATUS_NETWORK_OPEN_RESTRICTION != status) + return status; + + mozilla::sandboxing::LogBlocked("NtCreateFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(file, sizeof(HANDLE), WRITE)) + break; + if (!ValidParameter(io_status, sizeof(IO_STATUS_BLOCK), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes = 0; + NTSTATUS ret = + AllocAndCopyName(object_attributes, &name, &attributes, nullptr); + if (!NT_SUCCESS(ret) || !name) + break; + + uint32_t desired_access_uint32 = desired_access; + uint32_t options_uint32 = options; + uint32_t disposition_uint32 = disposition; + uint32_t broker = BROKER_FALSE; + CountedParameterSet<OpenFile> params; + const wchar_t* name_ptr = name.get(); + params[OpenFile::NAME] = ParamPickerMake(name_ptr); + params[OpenFile::ACCESS] = ParamPickerMake(desired_access_uint32); + params[OpenFile::DISPOSITION] = ParamPickerMake(disposition_uint32); + params[OpenFile::OPTIONS] = ParamPickerMake(options_uint32); + params[OpenFile::BROKER] = ParamPickerMake(broker); + + if (!QueryBroker(IpcTag::NTCREATEFILE, params.GetBase())) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + // The following call must match in the parameters with + // FilesystemDispatcher::ProcessNtCreateFile. + ResultCode code = + CrossCall(ipc, IpcTag::NTCREATEFILE, name.get(), attributes, + desired_access_uint32, file_attributes, sharing, disposition, + options_uint32, &answer); + if (SBOX_ALL_OK != code) + break; + + status = answer.nt_status; + + if (!NT_SUCCESS(answer.nt_status)) + break; + + __try { + *file = answer.handle; + io_status->Status = answer.nt_status; + io_status->Information = answer.extended[0].ulong_ptr; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + mozilla::sandboxing::LogAllowed("NtCreateFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +NTSTATUS WINAPI TargetNtOpenFile(NtOpenFileFunction orig_OpenFile, + PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + ULONG sharing, + ULONG options) { + // Check if the process can open it first. + NTSTATUS status = orig_OpenFile(file, desired_access, object_attributes, + io_status, sharing, options); + if (STATUS_ACCESS_DENIED != status && + STATUS_NETWORK_OPEN_RESTRICTION != status) + return status; + + mozilla::sandboxing::LogBlocked("NtOpenFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(file, sizeof(HANDLE), WRITE)) + break; + if (!ValidParameter(io_status, sizeof(IO_STATUS_BLOCK), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes; + NTSTATUS ret = + AllocAndCopyName(object_attributes, &name, &attributes, nullptr); + if (!NT_SUCCESS(ret) || !name) + break; + + uint32_t desired_access_uint32 = desired_access; + uint32_t options_uint32 = options; + uint32_t disposition_uint32 = FILE_OPEN; + uint32_t broker = BROKER_FALSE; + const wchar_t* name_ptr = name.get(); + CountedParameterSet<OpenFile> params; + params[OpenFile::NAME] = ParamPickerMake(name_ptr); + params[OpenFile::ACCESS] = ParamPickerMake(desired_access_uint32); + params[OpenFile::DISPOSITION] = ParamPickerMake(disposition_uint32); + params[OpenFile::OPTIONS] = ParamPickerMake(options_uint32); + params[OpenFile::BROKER] = ParamPickerMake(broker); + + if (!QueryBroker(IpcTag::NTOPENFILE, params.GetBase())) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = + CrossCall(ipc, IpcTag::NTOPENFILE, name.get(), attributes, + desired_access_uint32, sharing, options_uint32, &answer); + if (SBOX_ALL_OK != code) + break; + + status = answer.nt_status; + + if (!NT_SUCCESS(answer.nt_status)) + break; + + __try { + *file = answer.handle; + io_status->Status = answer.nt_status; + io_status->Information = answer.extended[0].ulong_ptr; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + mozilla::sandboxing::LogAllowed("NtOpenFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +NTSTATUS WINAPI +TargetNtQueryAttributesFile(NtQueryAttributesFileFunction orig_QueryAttributes, + POBJECT_ATTRIBUTES object_attributes, + PFILE_BASIC_INFORMATION file_attributes) { + // Check if the process can query it first. + NTSTATUS status = orig_QueryAttributes(object_attributes, file_attributes); + if (STATUS_ACCESS_DENIED != status && + STATUS_NETWORK_OPEN_RESTRICTION != status) + return status; + + mozilla::sandboxing::LogBlocked("NtQueryAttributesFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(file_attributes, sizeof(FILE_BASIC_INFORMATION), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes = 0; + NTSTATUS ret = + AllocAndCopyName(object_attributes, &name, &attributes, nullptr); + if (!NT_SUCCESS(ret) || !name) + break; + + InOutCountedBuffer file_info(file_attributes, + sizeof(FILE_BASIC_INFORMATION)); + + uint32_t broker = BROKER_FALSE; + CountedParameterSet<FileName> params; + const wchar_t* name_ptr = name.get(); + params[FileName::NAME] = ParamPickerMake(name_ptr); + params[FileName::BROKER] = ParamPickerMake(broker); + + if (!QueryBroker(IpcTag::NTQUERYATTRIBUTESFILE, params.GetBase())) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::NTQUERYATTRIBUTESFILE, name.get(), + attributes, file_info, &answer); + + if (SBOX_ALL_OK != code) + break; + + status = answer.nt_status; + + mozilla::sandboxing::LogAllowed("NtQueryAttributesFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +NTSTATUS WINAPI TargetNtQueryFullAttributesFile( + NtQueryFullAttributesFileFunction orig_QueryFullAttributes, + POBJECT_ATTRIBUTES object_attributes, + PFILE_NETWORK_OPEN_INFORMATION file_attributes) { + // Check if the process can query it first. + NTSTATUS status = + orig_QueryFullAttributes(object_attributes, file_attributes); + if (STATUS_ACCESS_DENIED != status && + STATUS_NETWORK_OPEN_RESTRICTION != status) + return status; + + mozilla::sandboxing::LogBlocked("NtQueryFullAttributesFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(file_attributes, sizeof(FILE_NETWORK_OPEN_INFORMATION), + WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes = 0; + NTSTATUS ret = + AllocAndCopyName(object_attributes, &name, &attributes, nullptr); + if (!NT_SUCCESS(ret) || !name) + break; + + InOutCountedBuffer file_info(file_attributes, + sizeof(FILE_NETWORK_OPEN_INFORMATION)); + + uint32_t broker = BROKER_FALSE; + CountedParameterSet<FileName> params; + const wchar_t* name_ptr = name.get(); + params[FileName::NAME] = ParamPickerMake(name_ptr); + params[FileName::BROKER] = ParamPickerMake(broker); + + if (!QueryBroker(IpcTag::NTQUERYFULLATTRIBUTESFILE, params.GetBase())) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::NTQUERYFULLATTRIBUTESFILE, + name.get(), attributes, file_info, &answer); + + if (SBOX_ALL_OK != code) + break; + + status = answer.nt_status; + + mozilla::sandboxing::LogAllowed("NtQueryFullAttributesFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +NTSTATUS WINAPI +TargetNtSetInformationFile(NtSetInformationFileFunction orig_SetInformationFile, + HANDLE file, + PIO_STATUS_BLOCK io_status, + PVOID file_info, + ULONG length, + FILE_INFORMATION_CLASS file_info_class) { + // Check if the process can open it first. + NTSTATUS status = orig_SetInformationFile(file, io_status, file_info, length, + file_info_class); + if (STATUS_ACCESS_DENIED != status) + return status; + + mozilla::sandboxing::LogBlocked("NtSetInformationFile"); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + if (!ValidParameter(io_status, sizeof(IO_STATUS_BLOCK), WRITE)) + break; + + if (!ValidParameter(file_info, length, READ)) + break; + + FILE_RENAME_INFORMATION* file_rename_info = + reinterpret_cast<FILE_RENAME_INFORMATION*>(file_info); + OBJECT_ATTRIBUTES object_attributes; + UNICODE_STRING object_name; + InitializeObjectAttributes(&object_attributes, &object_name, 0, nullptr, + nullptr); + + __try { + if (!IsSupportedRenameCall(file_rename_info, length, file_info_class)) + break; + + object_attributes.RootDirectory = file_rename_info->RootDirectory; + object_name.Buffer = file_rename_info->FileName; + object_name.Length = object_name.MaximumLength = + static_cast<USHORT>(file_rename_info->FileNameLength); + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + NTSTATUS ret = + AllocAndCopyName(&object_attributes, &name, nullptr, nullptr); + if (!NT_SUCCESS(ret) || !name) + break; + + uint32_t broker = BROKER_FALSE; + CountedParameterSet<FileName> params; + const wchar_t* name_ptr = name.get(); + params[FileName::NAME] = ParamPickerMake(name_ptr); + params[FileName::BROKER] = ParamPickerMake(broker); + + if (!QueryBroker(IpcTag::NTSETINFO_RENAME, params.GetBase())) + break; + + InOutCountedBuffer io_status_buffer(io_status, sizeof(IO_STATUS_BLOCK)); + // This is actually not an InOut buffer, only In, but using InOut facility + // really helps to simplify the code. + InOutCountedBuffer file_info_buffer(file_info, length); + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = + CrossCall(ipc, IpcTag::NTSETINFO_RENAME, file, io_status_buffer, + file_info_buffer, length, file_info_class, &answer); + + if (SBOX_ALL_OK != code) + break; + + status = answer.nt_status; + mozilla::sandboxing::LogAllowed("NtSetInformationFile"); + } while (false); + + return status; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/filesystem_interception.h b/security/sandbox/chromium/sandbox/win/src/filesystem_interception.h new file mode 100644 index 0000000000..d9d92f7fa4 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/filesystem_interception.h @@ -0,0 +1,67 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_WIN_SRC_FILESYSTEM_INTERCEPTION_H_ +#define SANDBOX_WIN_SRC_FILESYSTEM_INTERCEPTION_H_ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +// Interception of NtCreateFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateFile(NtCreateFileFunction orig_CreateFile, + PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + PLARGE_INTEGER allocation_size, + ULONG file_attributes, + ULONG sharing, + ULONG disposition, + ULONG options, + PVOID ea_buffer, + ULONG ea_length); + +// Interception of NtOpenFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenFile(NtOpenFileFunction orig_OpenFile, + PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + ULONG sharing, + ULONG options); + +// Interception of NtQueryAtttributesFile on the child process. +// It should never be called directly. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtQueryAttributesFile(NtQueryAttributesFileFunction orig_QueryAttributes, + POBJECT_ATTRIBUTES object_attributes, + PFILE_BASIC_INFORMATION file_attributes); + +// Interception of NtQueryFullAtttributesFile on the child process. +// It should never be called directly. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtQueryFullAttributesFile( + NtQueryFullAttributesFileFunction orig_QueryAttributes, + POBJECT_ATTRIBUTES object_attributes, + PFILE_NETWORK_OPEN_INFORMATION file_attributes); + +// Interception of NtSetInformationFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtSetInformationFile(NtSetInformationFileFunction orig_SetInformationFile, + HANDLE file, + PIO_STATUS_BLOCK io_status, + PVOID file_information, + ULONG length, + FILE_INFORMATION_CLASS file_information_class); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_FILESYSTEM_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/filesystem_policy.cc b/security/sandbox/chromium/sandbox/win/src/filesystem_policy.cc new file mode 100644 index 0000000000..3018cad0cb --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/filesystem_policy.cc @@ -0,0 +1,443 @@ +// Copyright (c) 2011 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/win/src/filesystem_policy.h" + +#include <stdint.h> + +#include <algorithm> +#include <string> + +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +NTSTATUS NtCreateFileInTarget(HANDLE* target_file_handle, + ACCESS_MASK desired_access, + OBJECT_ATTRIBUTES* obj_attributes, + IO_STATUS_BLOCK* io_status_block, + ULONG file_attributes, + ULONG share_access, + ULONG create_disposition, + ULONG create_options, + PVOID ea_buffer, + ULONG ea_length, + HANDLE target_process) { + NtCreateFileFunction NtCreateFile = nullptr; + ResolveNTFunctionPtr("NtCreateFile", &NtCreateFile); + + HANDLE local_handle = INVALID_HANDLE_VALUE; + NTSTATUS status = + NtCreateFile(&local_handle, desired_access, obj_attributes, + io_status_block, nullptr, file_attributes, share_access, + create_disposition, create_options, ea_buffer, ea_length); + if (!NT_SUCCESS(status)) { + return status; + } + + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, target_process, + target_file_handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return STATUS_SUCCESS; +} + +// Get an initialized anonymous level Security QOS. +SECURITY_QUALITY_OF_SERVICE GetAnonymousQOS() { + SECURITY_QUALITY_OF_SERVICE security_qos = {0}; + security_qos.Length = sizeof(security_qos); + security_qos.ImpersonationLevel = SecurityAnonymous; + // Set dynamic tracking so that a pipe doesn't capture the broker's token + security_qos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; + security_qos.EffectiveOnly = true; + + return security_qos; +} + +} // namespace. + +namespace sandbox { + +bool FileSystemPolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + std::wstring mod_name(name); + if (mod_name.empty()) { + return false; + } + + // Don't pre-process the path name and check for reparse points if it is the + // special case of allowing read access to all paths. + if (!(semantics == TargetPolicy::FILES_ALLOW_READONLY + && mod_name.compare(L"*") == 0) + && !PreProcessName(&mod_name)) { + // The path to be added might contain a reparse point. + NOTREACHED(); + return false; + } + + // TODO(cpu) bug 32224: This prefix add is a hack because we don't have the + // infrastructure to normalize names. In any case we need to escape the + // question marks. + if (_wcsnicmp(mod_name.c_str(), kNTDevicePrefix, kNTDevicePrefixLen)) { + mod_name = FixNTPrefixForMatch(mod_name); + name = mod_name.c_str(); + } + + EvalResult result = ASK_BROKER; + + // List of supported calls for the filesystem. + const unsigned kCallNtCreateFile = 0x1; + const unsigned kCallNtOpenFile = 0x2; + const unsigned kCallNtQueryAttributesFile = 0x4; + const unsigned kCallNtQueryFullAttributesFile = 0x8; + const unsigned kCallNtSetInfoRename = 0x10; + + DWORD rule_to_add = kCallNtOpenFile | kCallNtCreateFile | + kCallNtQueryAttributesFile | + kCallNtQueryFullAttributesFile | kCallNtSetInfoRename; + + PolicyRule create(result); + PolicyRule open(result); + PolicyRule query(result); + PolicyRule query_full(result); + PolicyRule rename(result); + + switch (semantics) { + case TargetPolicy::FILES_ALLOW_DIR_ANY: { + open.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND); + create.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND); + break; + } + case TargetPolicy::FILES_ALLOW_READONLY: { + // We consider all flags that are not known to be readonly as potentially + // used for write. + DWORD allowed_flags = FILE_READ_DATA | FILE_READ_ATTRIBUTES | + FILE_READ_EA | SYNCHRONIZE | FILE_EXECUTE | + GENERIC_READ | GENERIC_EXECUTE | READ_CONTROL; + DWORD restricted_flags = ~allowed_flags; + open.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND); + open.AddNumberMatch(IF, OpenFile::DISPOSITION, FILE_OPEN, EQUAL); + create.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND); + create.AddNumberMatch(IF, OpenFile::DISPOSITION, FILE_OPEN, EQUAL); + + // Read only access don't work for rename. + rule_to_add &= ~kCallNtSetInfoRename; + break; + } + case TargetPolicy::FILES_ALLOW_QUERY: { + // Here we don't want to add policy for the open or the create. + rule_to_add &= + ~(kCallNtOpenFile | kCallNtCreateFile | kCallNtSetInfoRename); + break; + } + case TargetPolicy::FILES_ALLOW_ANY: { + break; + } + default: { + NOTREACHED(); + return false; + } + } + + if ((rule_to_add & kCallNtCreateFile) && + (!create.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IpcTag::NTCREATEFILE, &create))) { + return false; + } + + if ((rule_to_add & kCallNtOpenFile) && + (!open.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IpcTag::NTOPENFILE, &open))) { + return false; + } + + if ((rule_to_add & kCallNtQueryAttributesFile) && + (!query.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IpcTag::NTQUERYATTRIBUTESFILE, &query))) { + return false; + } + + if ((rule_to_add & kCallNtQueryFullAttributesFile) && + (!query_full.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IpcTag::NTQUERYFULLATTRIBUTESFILE, &query_full))) { + return false; + } + + if ((rule_to_add & kCallNtSetInfoRename) && + (!rename.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IpcTag::NTSETINFO_RENAME, &rename))) { + return false; + } + + return true; +} + +// Right now we insert two rules, to be evaluated before any user supplied rule: +// - go to the broker if the path doesn't look like the paths that we push on +// the policy (namely \??\something). +// - go to the broker if it looks like this is a short-name path. +// +// It is possible to add a rule to go to the broker in any case; it would look +// something like: +// rule = new PolicyRule(ASK_BROKER); +// rule->AddNumberMatch(IF_NOT, FileName::BROKER, true, AND); +// policy->AddRule(service, rule); +bool FileSystemPolicy::SetInitialRules(LowLevelPolicy* policy) { + PolicyRule format(ASK_BROKER); + PolicyRule short_name(ASK_BROKER); + + bool rv = format.AddNumberMatch(IF_NOT, FileName::BROKER, BROKER_TRUE, AND); + rv &= format.AddStringMatch(IF_NOT, FileName::NAME, L"\\/?/?\\*", + CASE_SENSITIVE); + + rv &= short_name.AddNumberMatch(IF_NOT, FileName::BROKER, BROKER_TRUE, AND); + rv &= short_name.AddStringMatch(IF, FileName::NAME, L"*~*", CASE_SENSITIVE); + + if (!rv || !policy->AddRule(IpcTag::NTCREATEFILE, &format)) + return false; + + if (!policy->AddRule(IpcTag::NTCREATEFILE, &short_name)) + return false; + + if (!policy->AddRule(IpcTag::NTOPENFILE, &format)) + return false; + + if (!policy->AddRule(IpcTag::NTOPENFILE, &short_name)) + return false; + + if (!policy->AddRule(IpcTag::NTQUERYATTRIBUTESFILE, &format)) + return false; + + if (!policy->AddRule(IpcTag::NTQUERYATTRIBUTESFILE, &short_name)) + return false; + + if (!policy->AddRule(IpcTag::NTQUERYFULLATTRIBUTESFILE, &format)) + return false; + + if (!policy->AddRule(IpcTag::NTQUERYFULLATTRIBUTESFILE, &short_name)) + return false; + + if (!policy->AddRule(IpcTag::NTSETINFO_RENAME, &format)) + return false; + + if (!policy->AddRule(IpcTag::NTSETINFO_RENAME, &short_name)) + return false; + + return true; +} + +bool FileSystemPolicy::CreateFileAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + uint32_t desired_access, + uint32_t file_attributes, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG_PTR* io_information) { + *handle = nullptr; + // The only action supported is ASK_BROKER which means create the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + IO_STATUS_BLOCK io_block = {}; + UNICODE_STRING uni_name = {}; + OBJECT_ATTRIBUTES obj_attributes = {}; + SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS(); + + InitObjectAttribs(file, attributes, nullptr, &obj_attributes, &uni_name, + IsPipe(file) ? &security_qos : nullptr); + *nt_status = + NtCreateFileInTarget(handle, desired_access, &obj_attributes, &io_block, + file_attributes, share_access, create_disposition, + create_options, nullptr, 0, client_info.process); + + *io_information = io_block.Information; + return true; +} + +bool FileSystemPolicy::OpenFileAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + uint32_t desired_access, + uint32_t share_access, + uint32_t open_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG_PTR* io_information) { + *handle = nullptr; + // The only action supported is ASK_BROKER which means open the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + // An NtOpen is equivalent to an NtCreate with FileAttributes = 0 and + // CreateDisposition = FILE_OPEN. + IO_STATUS_BLOCK io_block = {}; + UNICODE_STRING uni_name = {}; + OBJECT_ATTRIBUTES obj_attributes = {}; + SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS(); + + InitObjectAttribs(file, attributes, nullptr, &obj_attributes, &uni_name, + IsPipe(file) ? &security_qos : nullptr); + *nt_status = NtCreateFileInTarget( + handle, desired_access, &obj_attributes, &io_block, 0, share_access, + FILE_OPEN, open_options, nullptr, 0, client_info.process); + + *io_information = io_block.Information; + return true; +} + +bool FileSystemPolicy::QueryAttributesFileAction( + EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + FILE_BASIC_INFORMATION* file_info, + NTSTATUS* nt_status) { + // The only action supported is ASK_BROKER which means query the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + NtQueryAttributesFileFunction NtQueryAttributesFile = nullptr; + ResolveNTFunctionPtr("NtQueryAttributesFile", &NtQueryAttributesFile); + + UNICODE_STRING uni_name = {0}; + OBJECT_ATTRIBUTES obj_attributes = {0}; + SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS(); + + InitObjectAttribs(file, attributes, nullptr, &obj_attributes, &uni_name, + IsPipe(file) ? &security_qos : nullptr); + *nt_status = NtQueryAttributesFile(&obj_attributes, file_info); + + return true; +} + +bool FileSystemPolicy::QueryFullAttributesFileAction( + EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + FILE_NETWORK_OPEN_INFORMATION* file_info, + NTSTATUS* nt_status) { + // The only action supported is ASK_BROKER which means query the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + NtQueryFullAttributesFileFunction NtQueryFullAttributesFile = nullptr; + ResolveNTFunctionPtr("NtQueryFullAttributesFile", &NtQueryFullAttributesFile); + + UNICODE_STRING uni_name = {0}; + OBJECT_ATTRIBUTES obj_attributes = {0}; + SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS(); + + InitObjectAttribs(file, attributes, nullptr, &obj_attributes, &uni_name, + IsPipe(file) ? &security_qos : nullptr); + *nt_status = NtQueryFullAttributesFile(&obj_attributes, file_info); + + return true; +} + +bool FileSystemPolicy::SetInformationFileAction(EvalResult eval_result, + const ClientInfo& client_info, + HANDLE target_file_handle, + void* file_info, + uint32_t length, + uint32_t info_class, + IO_STATUS_BLOCK* io_block, + NTSTATUS* nt_status) { + // The only action supported is ASK_BROKER which means open the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + NtSetInformationFileFunction NtSetInformationFile = nullptr; + ResolveNTFunctionPtr("NtSetInformationFile", &NtSetInformationFile); + + HANDLE local_handle = nullptr; + if (!::DuplicateHandle(client_info.process, target_file_handle, + ::GetCurrentProcess(), &local_handle, 0, false, + DUPLICATE_SAME_ACCESS)) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + base::win::ScopedHandle handle(local_handle); + + FILE_INFORMATION_CLASS file_info_class = + static_cast<FILE_INFORMATION_CLASS>(info_class); + *nt_status = NtSetInformationFile(local_handle, io_block, file_info, length, + file_info_class); + + return true; +} + +bool PreProcessName(std::wstring* path) { + // We now allow symbolic links to be opened via the broker, so we can no + // longer rely on the same object check where we checked the path of the + // opened file against the original. We don't specify a root when creating + // OBJECT_ATTRIBUTES from file names for brokering so they must be fully + // qualified and we can just check for the parent directory double dot between + // two backslashes. NtCreateFile doesn't seem to allow it anyway, but this is + // just an extra precaution. It also doesn't seem to allow the forward slash, + // but this is also used for checking policy rules, so we just replace forward + // slashes with backslashes. + std::replace(path->begin(), path->end(), L'/', L'\\'); + if (path->find(L"\\..\\") != std::wstring::npos) { + return false; + } + + ConvertToLongPath(path); + return true; +} + +std::wstring FixNTPrefixForMatch(const std::wstring& name) { + std::wstring mod_name = name; + + // NT prefix escaped for rule matcher + const wchar_t kNTPrefixEscaped[] = L"\\/?/?\\"; + const int kNTPrefixEscapedLen = base::size(kNTPrefixEscaped) - 1; + + if (0 != mod_name.compare(0, kNTPrefixLen, kNTPrefix)) { + if (0 != mod_name.compare(0, kNTPrefixEscapedLen, kNTPrefixEscaped)) { + // TODO(nsylvain): Find a better way to do name resolution. Right now we + // take the name and we expand it. + mod_name.insert(0, kNTPrefixEscaped); + } + } else { + // Start of name matches NT prefix, replace with escaped format + // Fixes bug: 334882 + mod_name.replace(0, kNTPrefixLen, kNTPrefixEscaped); + } + + return mod_name; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/filesystem_policy.h b/security/sandbox/chromium/sandbox/win/src/filesystem_policy.h new file mode 100644 index 0000000000..81dd1d0d1d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/filesystem_policy.h @@ -0,0 +1,112 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_SRC_FILESYSTEM_POLICY_H__ +#define SANDBOX_SRC_FILESYSTEM_POLICY_H__ + +#include <stdint.h> + +#include <string> + +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +enum IsBroker { BROKER_FALSE, BROKER_TRUE }; + +// This class centralizes most of the knowledge related to file system policy +class FileSystemPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for File IO, in particular open or create actions. + // 'name' is the file or directory name. + // 'semantics' is the desired semantics for the open or create. + // 'policy' is the policy generator to which the rules are going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Add basic file system rules. + static bool SetInitialRules(LowLevelPolicy* policy); + + // Performs the desired policy action on a create request with an + // API that is compatible with the IPC-received parameters. + // 'client_info' : the target process that is making the request. + // 'eval_result' : The desired policy action to accomplish. + // 'file' : The target file or directory. + static bool CreateFileAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + uint32_t desired_access, + uint32_t file_attributes, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG_PTR* io_information); + + // Performs the desired policy action on an open request with an + // API that is compatible with the IPC-received parameters. + // 'client_info' : the target process that is making the request. + // 'eval_result' : The desired policy action to accomplish. + // 'file' : The target file or directory. + static bool OpenFileAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + uint32_t desired_access, + uint32_t share_access, + uint32_t open_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG_PTR* io_information); + + // Performs the desired policy action on a query request with an + // API that is compatible with the IPC-received parameters. + static bool QueryAttributesFileAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + FILE_BASIC_INFORMATION* file_info, + NTSTATUS* nt_status); + + // Performs the desired policy action on a query request with an + // API that is compatible with the IPC-received parameters. + static bool QueryFullAttributesFileAction( + EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + FILE_NETWORK_OPEN_INFORMATION* file_info, + NTSTATUS* nt_status); + + // Performs the desired policy action on a set_info request with an + // API that is compatible with the IPC-received parameters. + static bool SetInformationFileAction(EvalResult eval_result, + const ClientInfo& client_info, + HANDLE target_file_handle, + void* file_info, + uint32_t length, + uint32_t info_class, + IO_STATUS_BLOCK* io_block, + NTSTATUS* nt_status); +}; + +// Expands the path and check if it's a reparse point. Returns false if the path +// cannot be trusted. +bool PreProcessName(std::wstring* path); + +// Corrects global paths to have a correctly escaped NT prefix at the +// beginning. If the name has no NT prefix (either normal or escaped) +// add the escaped form to the string +std::wstring FixNTPrefixForMatch(const std::wstring& name); + +} // namespace sandbox + +#endif // SANDBOX_SRC_FILESYSTEM_POLICY_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/handle_closer.cc b/security/sandbox/chromium/sandbox/win/src/handle_closer.cc new file mode 100644 index 0000000000..6751151dcb --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_closer.cc @@ -0,0 +1,185 @@ +// Copyright (c) 2011 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/win/src/handle_closer.h" + +#include <stddef.h> + +#include <memory> + +#include "base/logging.h" +#include "base/memory/free_deleter.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/internal_types.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/process_thread_interception.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +template <typename T> +T RoundUpToWordSize(T v) { + if (size_t mod = v % sizeof(size_t)) + v += sizeof(size_t) - mod; + return v; +} + +template <typename T> +T* RoundUpToWordSize(T* v) { + return reinterpret_cast<T*>(RoundUpToWordSize(reinterpret_cast<size_t>(v))); +} + +} // namespace + +namespace sandbox { + +// Memory buffer mapped from the parent, with the list of handles. +SANDBOX_INTERCEPT HandleCloserInfo* g_handles_to_close; + +HandleCloser::HandleCloser() {} + +HandleCloser::~HandleCloser() {} + +ResultCode HandleCloser::AddHandle(const wchar_t* handle_type, + const wchar_t* handle_name) { + if (!handle_type) + return SBOX_ERROR_BAD_PARAMS; + + std::wstring resolved_name; + if (handle_name) { + resolved_name = handle_name; + if (handle_type == std::wstring(L"Key")) + if (!ResolveRegistryName(resolved_name, &resolved_name)) + return SBOX_ERROR_BAD_PARAMS; + } + + HandleMap::iterator names = handles_to_close_.find(handle_type); + if (names == handles_to_close_.end()) { // We have no entries for this type. + std::pair<HandleMap::iterator, bool> result = handles_to_close_.insert( + HandleMap::value_type(handle_type, HandleMap::mapped_type())); + names = result.first; + if (handle_name) + names->second.insert(resolved_name); + } else if (!handle_name) { // Now we need to close all handles of this type. + names->second.clear(); + } else if (!names->second.empty()) { // Add another name for this type. + names->second.insert(resolved_name); + } // If we're already closing all handles of type then we're done. + + return SBOX_ALL_OK; +} + +size_t HandleCloser::GetBufferSize() { + size_t bytes_total = offsetof(HandleCloserInfo, handle_entries); + + for (HandleMap::iterator i = handles_to_close_.begin(); + i != handles_to_close_.end(); ++i) { + size_t bytes_entry = offsetof(HandleListEntry, handle_type) + + (i->first.size() + 1) * sizeof(wchar_t); + for (HandleMap::mapped_type::iterator j = i->second.begin(); + j != i->second.end(); ++j) { + bytes_entry += ((*j).size() + 1) * sizeof(wchar_t); + } + + // Round up to the nearest multiple of word size. + bytes_entry = RoundUpToWordSize(bytes_entry); + bytes_total += bytes_entry; + } + + return bytes_total; +} + +bool HandleCloser::InitializeTargetHandles(TargetProcess* target) { + // Do nothing on an empty list (global pointer already initialized to + // nullptr). + if (handles_to_close_.empty()) + return true; + + size_t bytes_needed = GetBufferSize(); + std::unique_ptr<size_t[]> local_buffer( + new size_t[bytes_needed / sizeof(size_t)]); + + if (!SetupHandleList(local_buffer.get(), bytes_needed)) + return false; + + void* remote_data; + if (!CopyToChildMemory(target->Process(), local_buffer.get(), bytes_needed, + &remote_data)) + return false; + + g_handles_to_close = reinterpret_cast<HandleCloserInfo*>(remote_data); + + ResultCode rc = target->TransferVariable( + "g_handles_to_close", &g_handles_to_close, sizeof(g_handles_to_close)); + + return (SBOX_ALL_OK == rc); +} + +bool HandleCloser::SetupHandleList(void* buffer, size_t buffer_bytes) { + ::ZeroMemory(buffer, buffer_bytes); + HandleCloserInfo* handle_info = reinterpret_cast<HandleCloserInfo*>(buffer); + handle_info->record_bytes = buffer_bytes; + handle_info->num_handle_types = handles_to_close_.size(); + + wchar_t* output = reinterpret_cast<wchar_t*>(&handle_info->handle_entries[0]); + wchar_t* end = reinterpret_cast<wchar_t*>(reinterpret_cast<char*>(buffer) + + buffer_bytes); + for (HandleMap::iterator i = handles_to_close_.begin(); + i != handles_to_close_.end(); ++i) { + if (output >= end) + return false; + HandleListEntry* list_entry = reinterpret_cast<HandleListEntry*>(output); + output = &list_entry->handle_type[0]; + + // Copy the typename and set the offset and count. + i->first.copy(output, i->first.size()); + *(output += i->first.size()) = L'\0'; + output++; + list_entry->offset_to_names = + reinterpret_cast<char*>(output) - reinterpret_cast<char*>(list_entry); + list_entry->name_count = i->second.size(); + + // Copy the handle names. + for (HandleMap::mapped_type::iterator j = i->second.begin(); + j != i->second.end(); ++j) { + output = std::copy((*j).begin(), (*j).end(), output) + 1; + } + + // Round up to the nearest multiple of sizeof(size_t). + output = RoundUpToWordSize(output); + list_entry->record_bytes = + reinterpret_cast<char*>(output) - reinterpret_cast<char*>(list_entry); + } + + DCHECK_EQ(reinterpret_cast<size_t>(output), reinterpret_cast<size_t>(end)); + return output <= end; +} + +bool GetHandleName(HANDLE handle, std::wstring* handle_name) { + static NtQueryObject QueryObject = nullptr; + if (!QueryObject) + ResolveNTFunctionPtr("NtQueryObject", &QueryObject); + + ULONG size = MAX_PATH; + std::unique_ptr<UNICODE_STRING, base::FreeDeleter> name; + NTSTATUS result; + + do { + name.reset(static_cast<UNICODE_STRING*>(malloc(size))); + DCHECK(name.get()); + result = + QueryObject(handle, ObjectNameInformation, name.get(), size, &size); + } while (result == STATUS_INFO_LENGTH_MISMATCH || + result == STATUS_BUFFER_OVERFLOW); + + if (NT_SUCCESS(result) && name->Buffer && name->Length) + handle_name->assign(name->Buffer, name->Length / sizeof(wchar_t)); + else + handle_name->clear(); + + return NT_SUCCESS(result); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/handle_closer.h b/security/sandbox/chromium/sandbox/win/src/handle_closer.h new file mode 100644 index 0000000000..4f023b27b6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_closer.h @@ -0,0 +1,76 @@ +// 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. + +#ifndef SANDBOX_SRC_HANDLE_CLOSER_H_ +#define SANDBOX_SRC_HANDLE_CLOSER_H_ + +#include <stddef.h> + +#include <map> +#include <set> + +#include <string> + +#include "base/macros.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/target_process.h" + +namespace sandbox { + +// This is a map of handle-types to names that we need to close in the +// target process. A null set means we need to close all handles of the +// given type. +typedef std::map<const std::wstring, std::set<std::wstring>> HandleMap; + +// Type and set of corresponding handle names to close. +struct HandleListEntry { + size_t record_bytes; // Rounded to sizeof(size_t) bytes. + size_t offset_to_names; // Nul terminated strings of name_count names. + size_t name_count; + wchar_t handle_type[1]; +}; + +// Global parameters and a pointer to the list of entries. +struct HandleCloserInfo { + size_t record_bytes; // Rounded to sizeof(size_t) bytes. + size_t num_handle_types; + struct HandleListEntry handle_entries[1]; +}; + +SANDBOX_INTERCEPT HandleCloserInfo* g_handle_closer_info; + +// Adds handles to close after lockdown. +class HandleCloser { + public: + HandleCloser(); + ~HandleCloser(); + + // Adds a handle that will be closed in the target process after lockdown. + // A nullptr value for handle_name indicates all handles of the specified + // type. An empty string for handle_name indicates the handle is unnamed. + ResultCode AddHandle(const wchar_t* handle_type, const wchar_t* handle_name); + + // Serializes and copies the closer table into the target process. + bool InitializeTargetHandles(TargetProcess* target); + + private: + // Calculates the memory needed to copy the serialized handles list (rounded + // to the nearest machine-word size). + size_t GetBufferSize(); + + // Serializes the handle list into the target process. + bool SetupHandleList(void* buffer, size_t buffer_bytes); + + HandleMap handles_to_close_; + + DISALLOW_COPY_AND_ASSIGN(HandleCloser); +}; + +// Returns the object manager's name associated with a handle +bool GetHandleName(HANDLE handle, std::wstring* handle_name); + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_CLOSER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/handle_closer_agent.cc b/security/sandbox/chromium/sandbox/win/src/handle_closer_agent.cc new file mode 100644 index 0000000000..55fe2d4689 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_closer_agent.cc @@ -0,0 +1,239 @@ +// 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 "sandbox/win/src/handle_closer_agent.h" + +#include <limits.h> +#include <stddef.h> + +#include "base/logging.h" +#include "base/win/static_constants.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +// Returns type infomation for an NT object. This routine is expected to be +// called for invalid handles so it catches STATUS_INVALID_HANDLE exceptions +// that can be generated when handle tracing is enabled. +NTSTATUS QueryObjectTypeInformation(HANDLE handle, void* buffer, ULONG* size) { + static NtQueryObject QueryObject = nullptr; + if (!QueryObject) + ResolveNTFunctionPtr("NtQueryObject", &QueryObject); + + NTSTATUS status = STATUS_UNSUCCESSFUL; + __try { + status = QueryObject(handle, ObjectTypeInformation, buffer, *size, size); + } __except (GetExceptionCode() == STATUS_INVALID_HANDLE + ? EXCEPTION_EXECUTE_HANDLER + : EXCEPTION_CONTINUE_SEARCH) { + status = STATUS_INVALID_HANDLE; + } + return status; +} + +} // namespace + +namespace sandbox { + +// Memory buffer mapped from the parent, with the list of handles. +SANDBOX_INTERCEPT HandleCloserInfo* g_handles_to_close = nullptr; + +bool HandleCloserAgent::NeedsHandlesClosed() { + return !!g_handles_to_close; +} + +HandleCloserAgent::HandleCloserAgent() + : dummy_handle_(::CreateEvent(nullptr, false, false, nullptr)) {} + +HandleCloserAgent::~HandleCloserAgent() {} + +// Attempts to stuff |closed_handle| with a duplicated handle for a dummy Event +// with no access. This should allow the handle to be closed, to avoid +// generating EXCEPTION_INVALID_HANDLE on shutdown, but nothing else. For now +// the only supported |type| is Event or File. +bool HandleCloserAgent::AttemptToStuffHandleSlot(HANDLE closed_handle, + const std::wstring& type) { + // Only attempt to stuff Files and Events at the moment. + if (type != L"Event" && type != L"File") { + return true; + } + + if (!dummy_handle_.IsValid()) + return false; + + // This should never happen, as g_dummy is created before closing to_stuff. + DCHECK(dummy_handle_.Get() != closed_handle); + + std::vector<HANDLE> to_close; + + const DWORD original_proc_num = GetCurrentProcessorNumber(); + DWORD proc_num = original_proc_num; + DWORD_PTR original_affinity_mask = + SetThreadAffinityMask(GetCurrentThread(), DWORD_PTR{1} << proc_num); + bool found_handle = false; + BOOL result = FALSE; + + // There is per-processor based free list of handles entries. The free handle + // from current processor's freelist is preferred for reusing, so cycling + // through all possible processors to find closed_handle. + // Start searching from current processor which covers usual cases. + + do { + DWORD_PTR current_mask = DWORD_PTR{1} << proc_num; + + if (original_affinity_mask & current_mask) { + if (proc_num != original_proc_num) { + SetThreadAffinityMask(GetCurrentThread(), current_mask); + } + + HANDLE dup_dummy = nullptr; + size_t count = 16; + + do { + result = + ::DuplicateHandle(::GetCurrentProcess(), dummy_handle_.Get(), + ::GetCurrentProcess(), &dup_dummy, 0, false, 0); + if (!result) { + break; + } + if (dup_dummy != closed_handle) { + to_close.push_back(dup_dummy); + } else { + found_handle = true; + } + } while (count-- && reinterpret_cast<uintptr_t>(dup_dummy) < + reinterpret_cast<uintptr_t>(closed_handle)); + } + + proc_num++; + if (proc_num == sizeof(DWORD_PTR) * 8) { + proc_num = 0; + } + if (proc_num == original_proc_num) { + break; + } + } while (result && !found_handle); + + SetThreadAffinityMask(GetCurrentThread(), original_affinity_mask); + + for (HANDLE h : to_close) + ::CloseHandle(h); + + return found_handle; +} + +// Reads g_handles_to_close and creates the lookup map. +void HandleCloserAgent::InitializeHandlesToClose(bool* is_csrss_connected) { + CHECK(g_handles_to_close); + + // Default to connected state + *is_csrss_connected = true; + + // Grab the header. + HandleListEntry* entry = g_handles_to_close->handle_entries; + for (size_t i = 0; i < g_handles_to_close->num_handle_types; ++i) { + // Set the type name. + wchar_t* input = entry->handle_type; + if (!wcscmp(input, L"ALPC Port")) { + *is_csrss_connected = false; + } + HandleMap::mapped_type& handle_names = handles_to_close_[input]; + input = reinterpret_cast<wchar_t*>(reinterpret_cast<char*>(entry) + + entry->offset_to_names); + // Grab all the handle names. + for (size_t j = 0; j < entry->name_count; ++j) { + std::pair<HandleMap::mapped_type::iterator, bool> name = + handle_names.insert(input); + CHECK(name.second); + input += name.first->size() + 1; + } + + // Move on to the next entry. + entry = reinterpret_cast<HandleListEntry*>(reinterpret_cast<char*>(entry) + + entry->record_bytes); + + DCHECK(reinterpret_cast<wchar_t*>(entry) >= input); + DCHECK(reinterpret_cast<wchar_t*>(entry) - input < + static_cast<ptrdiff_t>(sizeof(size_t) / sizeof(wchar_t))); + } + + // Clean up the memory we copied over. + ::VirtualFree(g_handles_to_close, 0, MEM_RELEASE); + g_handles_to_close = nullptr; +} + +bool HandleCloserAgent::CloseHandles() { + DWORD handle_count = UINT_MAX; + const int kInvalidHandleThreshold = 100; + const size_t kHandleOffset = 4; // Handles are always a multiple of 4. + + if (!::GetProcessHandleCount(::GetCurrentProcess(), &handle_count)) + return false; + + // Skip closing these handles when Application Verifier is in use in order to + // avoid invalid-handle exceptions. + if (GetModuleHandleA(base::win::kApplicationVerifierDllName)) + return true; + + // Set up buffers for the type info and the name. + std::vector<BYTE> type_info_buffer(sizeof(OBJECT_TYPE_INFORMATION) + + 32 * sizeof(wchar_t)); + OBJECT_TYPE_INFORMATION* type_info = + reinterpret_cast<OBJECT_TYPE_INFORMATION*>(&(type_info_buffer[0])); + std::wstring handle_name; + HANDLE handle = nullptr; + int invalid_count = 0; + + // Keep incrementing until we hit the number of handles reported by + // GetProcessHandleCount(). If we hit a very long sequence of invalid + // handles we assume that we've run past the end of the table. + while (handle_count && invalid_count < kInvalidHandleThreshold) { + reinterpret_cast<size_t&>(handle) += kHandleOffset; + NTSTATUS rc; + + // Get the type name, reusing the buffer. + ULONG size = static_cast<ULONG>(type_info_buffer.size()); + rc = QueryObjectTypeInformation(handle, type_info, &size); + while (rc == STATUS_INFO_LENGTH_MISMATCH || rc == STATUS_BUFFER_OVERFLOW) { + type_info_buffer.resize(size + sizeof(wchar_t)); + type_info = + reinterpret_cast<OBJECT_TYPE_INFORMATION*>(&(type_info_buffer[0])); + rc = QueryObjectTypeInformation(handle, type_info, &size); + // Leave padding for the nul terminator. + if (NT_SUCCESS(rc) && size == type_info_buffer.size()) + rc = STATUS_INFO_LENGTH_MISMATCH; + } + if (!NT_SUCCESS(rc) || !type_info->Name.Buffer) { + ++invalid_count; + continue; + } + + --handle_count; + type_info->Name.Buffer[type_info->Name.Length / sizeof(wchar_t)] = L'\0'; + + // Check if we're looking for this type of handle. + HandleMap::iterator result = handles_to_close_.find(type_info->Name.Buffer); + if (result != handles_to_close_.end()) { + HandleMap::mapped_type& names = result->second; + // Empty set means close all handles of this type; otherwise check name. + if (!names.empty()) { + // Move on to the next handle if this name doesn't match. + if (!GetHandleName(handle, &handle_name) || !names.count(handle_name)) + continue; + } + + if (!::SetHandleInformation(handle, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0)) + return false; + if (!::CloseHandle(handle)) + return false; + // Attempt to stuff this handle with a new dummy Event. + AttemptToStuffHandleSlot(handle, result->first); + } + } + + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/handle_closer_agent.h b/security/sandbox/chromium/sandbox/win/src/handle_closer_agent.h new file mode 100644 index 0000000000..91f8e74c7e --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_closer_agent.h @@ -0,0 +1,46 @@ +// Copyright (c) 2011 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. + +#ifndef SANDBOX_SRC_HANDLE_CLOSER_AGENT_H_ +#define SANDBOX_SRC_HANDLE_CLOSER_AGENT_H_ + +#include <string> + +#include "base/macros.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/handle_closer.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +// Target process code to close the handle list copied over from the broker. +class HandleCloserAgent { + public: + HandleCloserAgent(); + ~HandleCloserAgent(); + + // Reads the serialized list from the broker and creates the lookup map. + // Updates is_csrss_connected based on type of handles closed. + void InitializeHandlesToClose(bool* is_csrss_connected); + + // Closes any handles matching those in the lookup map. + bool CloseHandles(); + + // True if we have handles waiting to be closed. + static bool NeedsHandlesClosed(); + + private: + // Attempt to stuff a closed handle with a dummy Event. + bool AttemptToStuffHandleSlot(HANDLE closed_handle, const std::wstring& type); + + HandleMap handles_to_close_; + base::win::ScopedHandle dummy_handle_; + + DISALLOW_COPY_AND_ASSIGN(HandleCloserAgent); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_CLOSER_AGENT_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/handle_closer_test.cc b/security/sandbox/chromium/sandbox/win/src/handle_closer_test.cc new file mode 100644 index 0000000000..7406f77592 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_closer_test.cc @@ -0,0 +1,297 @@ +// Copyright (c) 2011 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 <limits.h> +#include <stddef.h> + +#include "base/strings/stringprintf.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/handle_closer_agent.h" +#include "sandbox/win/src/nt_internals.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" + +namespace { + +const wchar_t* kFileExtensions[] = {L".1", L".2", L".3", L".4"}; + +// Returns a handle to a unique marker file that can be retrieved between runs. +HANDLE GetMarkerFile(const wchar_t* extension) { + wchar_t path_buffer[MAX_PATH + 1]; + CHECK(::GetTempPath(MAX_PATH, path_buffer)); + std::wstring marker_path = path_buffer; + marker_path += L"\\sbox_marker_"; + + // Generate a unique value from the exe's size and timestamp. + CHECK(::GetModuleFileName(nullptr, path_buffer, MAX_PATH)); + base::win::ScopedHandle module( + ::CreateFile(path_buffer, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); + CHECK(module.IsValid()); + FILETIME timestamp; + CHECK(::GetFileTime(module.Get(), ×tamp, nullptr, nullptr)); + marker_path += + base::StringPrintf(L"%08x%08x%08x", ::GetFileSize(module.Get(), nullptr), + timestamp.dwLowDateTime, timestamp.dwHighDateTime); + marker_path += extension; + + // Make the file delete-on-close so cleanup is automatic. + return CreateFile(marker_path.c_str(), FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, nullptr); +} + +// Returns type infomation for an NT object. This routine is expected to be +// called for invalid handles so it catches STATUS_INVALID_HANDLE exceptions +// that can be generated when handle tracing is enabled. +NTSTATUS QueryObjectTypeInformation(HANDLE handle, void* buffer, ULONG* size) { + static NtQueryObject QueryObject = nullptr; + if (!QueryObject) + ResolveNTFunctionPtr("NtQueryObject", &QueryObject); + + NTSTATUS status = STATUS_UNSUCCESSFUL; + __try { + status = QueryObject(handle, ObjectTypeInformation, buffer, *size, size); + } __except (GetExceptionCode() == STATUS_INVALID_HANDLE + ? EXCEPTION_EXECUTE_HANDLER + : EXCEPTION_CONTINUE_SEARCH) { + status = STATUS_INVALID_HANDLE; + } + return status; +} + +// Used by the thread pool tests. +HANDLE finish_event; +const int kWaitCount = 20; + +} // namespace + +namespace sandbox { + +// Checks for the presence of a list of files (in object path form). +// Format: CheckForFileHandle (Y|N) \path\to\file1 [\path\to\file2 ...] +// - Y or N depending if the file should exist or not. +SBOX_TESTS_COMMAND int CheckForFileHandles(int argc, wchar_t** argv) { + if (argc < 2) + return SBOX_TEST_FAILED_TO_RUN_TEST; + bool should_find = argv[0][0] == L'Y'; + if (argv[0][1] != L'\0' || (!should_find && argv[0][0] != L'N')) + return SBOX_TEST_FAILED_TO_RUN_TEST; + + static int state = BEFORE_INIT; + switch (state++) { + case BEFORE_INIT: + // Create a unique marker file that is open while the test is running. + // The handles leak, but it will be closed by the test or on exit. + for (const wchar_t* kExtension : kFileExtensions) + CHECK_NE(GetMarkerFile(kExtension), INVALID_HANDLE_VALUE); + return SBOX_TEST_SUCCEEDED; + + case AFTER_REVERT: { + // Brute force the handle table to find what we're looking for. + DWORD handle_count = UINT_MAX; + const int kInvalidHandleThreshold = 100; + const size_t kHandleOffset = 4; // Handles are always a multiple of 4. + HANDLE handle = nullptr; + int invalid_count = 0; + std::wstring handle_name; + + if (!::GetProcessHandleCount(::GetCurrentProcess(), &handle_count)) + return SBOX_TEST_FAILED_TO_RUN_TEST; + + while (handle_count && invalid_count < kInvalidHandleThreshold) { + reinterpret_cast<size_t&>(handle) += kHandleOffset; + if (GetHandleName(handle, &handle_name)) { + for (int i = 1; i < argc; ++i) { + if (handle_name == argv[i]) + return should_find ? SBOX_TEST_SUCCEEDED : SBOX_TEST_FAILED; + } + --handle_count; + } else { + ++invalid_count; + } + } + + return should_find ? SBOX_TEST_FAILED : SBOX_TEST_SUCCEEDED; + } + + default: // Do nothing. + break; + } + + return SBOX_TEST_SUCCEEDED; +} + +// Checks that supplied handle is an Event and it's not waitable. +// Format: CheckForEventHandles +SBOX_TESTS_COMMAND int CheckForEventHandles(int argc, wchar_t** argv) { + static int state = BEFORE_INIT; + static std::vector<HANDLE> to_check; + + switch (state++) { + case BEFORE_INIT: + // Create a unique marker file that is open while the test is running. + for (const wchar_t* kExtension : kFileExtensions) { + HANDLE handle = GetMarkerFile(kExtension); + CHECK_NE(handle, INVALID_HANDLE_VALUE); + to_check.push_back(handle); + } + return SBOX_TEST_SUCCEEDED; + + case AFTER_REVERT: + for (HANDLE handle : to_check) { + // Set up buffers for the type info and the name. + std::vector<BYTE> type_info_buffer(sizeof(OBJECT_TYPE_INFORMATION) + + 32 * sizeof(wchar_t)); + OBJECT_TYPE_INFORMATION* type_info = + reinterpret_cast<OBJECT_TYPE_INFORMATION*>(&(type_info_buffer[0])); + NTSTATUS rc; + + // Get the type name, reusing the buffer. + ULONG size = static_cast<ULONG>(type_info_buffer.size()); + rc = QueryObjectTypeInformation(handle, type_info, &size); + while (rc == STATUS_INFO_LENGTH_MISMATCH || + rc == STATUS_BUFFER_OVERFLOW) { + type_info_buffer.resize(size + sizeof(wchar_t)); + type_info = reinterpret_cast<OBJECT_TYPE_INFORMATION*>( + &(type_info_buffer[0])); + rc = QueryObjectTypeInformation(handle, type_info, &size); + // Leave padding for the nul terminator. + if (NT_SUCCESS(rc) && size == type_info_buffer.size()) + rc = STATUS_INFO_LENGTH_MISMATCH; + } + + CHECK(NT_SUCCESS(rc)); + CHECK(type_info->Name.Buffer); + + type_info->Name.Buffer[type_info->Name.Length / sizeof(wchar_t)] = + L'\0'; + + // Should be an Event now. + CHECK_EQ(wcslen(type_info->Name.Buffer), 5U); + CHECK_EQ(wcscmp(L"Event", type_info->Name.Buffer), 0); + + // Should not be able to wait. + CHECK_EQ(WaitForSingleObject(handle, INFINITE), WAIT_FAILED); + + // Should be able to close. + CHECK(::CloseHandle(handle)); + } + return SBOX_TEST_SUCCEEDED; + + default: // Do nothing. + break; + } + + return SBOX_TEST_SUCCEEDED; +} + +TEST(HandleCloserTest, CheckForMarkerFiles) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + + std::wstring command = std::wstring(L"CheckForFileHandles Y"); + for (const wchar_t* kExtension : kFileExtensions) { + std::wstring handle_name; + base::win::ScopedHandle marker(GetMarkerFile(kExtension)); + CHECK(marker.IsValid()); + CHECK(sandbox::GetHandleName(marker.Get(), &handle_name)); + command += (L" "); + command += handle_name; + } + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command.c_str())) + << "Failed: " << command; +} + +TEST(HandleCloserTest, CloseMarkerFiles) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + sandbox::TargetPolicy* policy = runner.GetPolicy(); + + std::wstring command = std::wstring(L"CheckForFileHandles N"); + for (const wchar_t* kExtension : kFileExtensions) { + std::wstring handle_name; + base::win::ScopedHandle marker(GetMarkerFile(kExtension)); + CHECK(marker.IsValid()); + CHECK(sandbox::GetHandleName(marker.Get(), &handle_name)); + CHECK_EQ(policy->AddKernelObjectToClose(L"File", handle_name.c_str()), + SBOX_ALL_OK); + command += (L" "); + command += handle_name; + } + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command.c_str())) + << "Failed: " << command; +} + +TEST(HandleCloserTest, CheckStuffedHandle) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + sandbox::TargetPolicy* policy = runner.GetPolicy(); + + for (const wchar_t* kExtension : kFileExtensions) { + std::wstring handle_name; + base::win::ScopedHandle marker(GetMarkerFile(kExtension)); + CHECK(marker.IsValid()); + CHECK(sandbox::GetHandleName(marker.Get(), &handle_name)); + CHECK_EQ(policy->AddKernelObjectToClose(L"File", handle_name.c_str()), + SBOX_ALL_OK); + } + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckForEventHandles")); +} + +void WINAPI ThreadPoolTask(void* event, BOOLEAN timeout) { + static volatile LONG waiters_remaining = kWaitCount; + CHECK(!timeout); + CHECK(::CloseHandle(event)); + if (::InterlockedDecrement(&waiters_remaining) == 0) + CHECK(::SetEvent(finish_event)); +} + +// Run a thread pool inside a sandbox without a CSRSS connection. +SBOX_TESTS_COMMAND int RunThreadPool(int argc, wchar_t** argv) { + HANDLE wait_list[20]; + finish_event = ::CreateEvent(nullptr, true, false, nullptr); + CHECK(finish_event); + + // Set up a bunch of waiters. + HANDLE pool = nullptr; + for (int i = 0; i < kWaitCount; ++i) { + HANDLE event = ::CreateEvent(nullptr, true, false, nullptr); + CHECK(event); + CHECK(::RegisterWaitForSingleObject(&pool, event, ThreadPoolTask, event, + INFINITE, WT_EXECUTEONLYONCE)); + wait_list[i] = event; + } + + // Signal all the waiters. + for (int i = 0; i < kWaitCount; ++i) + CHECK(::SetEvent(wait_list[i])); + + CHECK_EQ(::WaitForSingleObject(finish_event, INFINITE), WAIT_OBJECT_0); + CHECK(::CloseHandle(finish_event)); + + return SBOX_TEST_SUCCEEDED; +} + +TEST(HandleCloserTest, RunThreadPool) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(AFTER_REVERT); + + // Sandbox policy will determine which platforms to disconnect CSRSS and when + // to close the CSRSS handle. + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"RunThreadPool")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/handle_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/handle_dispatcher.cc new file mode 100644 index 0000000000..611e33d2a6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_dispatcher.cc @@ -0,0 +1,93 @@ +// 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 "sandbox/win/src/handle_dispatcher.h" + +#include <stdint.h> + +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/handle_interception.h" +#include "sandbox/win/src/handle_policy.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sandbox_utils.h" + +namespace sandbox { + +HandleDispatcher::HandleDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall duplicate_handle_proxy = { + {IpcTag::DUPLICATEHANDLEPROXY, + {VOIDPTR_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &HandleDispatcher::DuplicateHandleProxy)}; + + ipc_calls_.push_back(duplicate_handle_proxy); +} + +bool HandleDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + // We perform no interceptions for handles right now. + switch (service) { + case IpcTag::DUPLICATEHANDLEPROXY: + return true; + + default: + return false; + } +} + +bool HandleDispatcher::DuplicateHandleProxy(IPCInfo* ipc, + HANDLE source_handle, + uint32_t target_process_id, + uint32_t desired_access, + uint32_t options) { + static NtQueryObject QueryObject = NULL; + if (!QueryObject) + ResolveNTFunctionPtr("NtQueryObject", &QueryObject); + + // Get a copy of the handle for use in the broker process. + HANDLE handle_temp; + if (!::DuplicateHandle(ipc->client_info->process, source_handle, + ::GetCurrentProcess(), &handle_temp, + 0, FALSE, DUPLICATE_SAME_ACCESS | options)) { + ipc->return_info.win32_result = ::GetLastError(); + return false; + } + options &= ~DUPLICATE_CLOSE_SOURCE; + base::win::ScopedHandle handle(handle_temp); + + // Get the object type (32 characters is safe; current max is 14). + BYTE buffer[sizeof(OBJECT_TYPE_INFORMATION) + 32 * sizeof(wchar_t)]; + OBJECT_TYPE_INFORMATION* type_info = + reinterpret_cast<OBJECT_TYPE_INFORMATION*>(buffer); + ULONG size = sizeof(buffer) - sizeof(wchar_t); + NTSTATUS error = + QueryObject(handle.Get(), ObjectTypeInformation, type_info, size, &size); + if (!NT_SUCCESS(error)) { + ipc->return_info.nt_status = error; + return false; + } + type_info->Name.Buffer[type_info->Name.Length / sizeof(wchar_t)] = L'\0'; + + CountedParameterSet<HandleTarget> params; + params[HandleTarget::NAME] = ParamPickerMake(type_info->Name.Buffer); + params[HandleTarget::TARGET] = ParamPickerMake(target_process_id); + + EvalResult eval = policy_base_->EvalPolicy(IpcTag::DUPLICATEHANDLEPROXY, + params.GetBase()); + ipc->return_info.win32_result = + HandlePolicy::DuplicateHandleProxyAction(eval, handle.Get(), + target_process_id, + &ipc->return_info.handle, + desired_access, options); + return true; +} + +} // namespace sandbox + diff --git a/security/sandbox/chromium/sandbox/win/src/handle_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/handle_dispatcher.h new file mode 100644 index 0000000000..6f9adbc10b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_dispatcher.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef SANDBOX_SRC_HANDLE_DISPATCHER_H_ +#define SANDBOX_SRC_HANDLE_DISPATCHER_H_ + +#include <stdint.h> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles handle-related IPC calls. +class HandleDispatcher : public Dispatcher { + public: + explicit HandleDispatcher(PolicyBase* policy_base); + ~HandleDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Processes IPC requests coming from calls to + // TargetServices::DuplicateHandle() in the target. + bool DuplicateHandleProxy(IPCInfo* ipc, + HANDLE source_handle, + uint32_t target_process_id, + uint32_t desired_access, + uint32_t options); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(HandleDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_DISPATCHER_H_ + diff --git a/security/sandbox/chromium/sandbox/win/src/handle_inheritance_test.cc b/security/sandbox/chromium/sandbox/win/src/handle_inheritance_test.cc new file mode 100644 index 0000000000..c91903c809 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_inheritance_test.cc @@ -0,0 +1,49 @@ +// 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 <stdio.h> + +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +SBOX_TESTS_COMMAND int HandleInheritanceTests_PrintToStdout(int argc, + wchar_t** argv) { + printf("Example output to stdout\n"); + return SBOX_TEST_SUCCEEDED; +} + +TEST(HandleInheritanceTests, TestStdoutInheritance) { + base::ScopedTempDir temp_directory; + base::FilePath temp_file_name; + ASSERT_TRUE(temp_directory.CreateUniqueTempDir()); + ASSERT_TRUE( + CreateTemporaryFileInDir(temp_directory.GetPath(), &temp_file_name)); + + SECURITY_ATTRIBUTES attrs = {}; + attrs.nLength = sizeof(attrs); + attrs.bInheritHandle = true; + base::win::ScopedHandle tmp_handle( + CreateFile(temp_file_name.value().c_str(), GENERIC_WRITE, + FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, &attrs, + OPEN_EXISTING, 0, nullptr)); + ASSERT_TRUE(tmp_handle.IsValid()); + + TestRunner runner; + ASSERT_EQ(SBOX_ALL_OK, runner.GetPolicy()->SetStdoutHandle(tmp_handle.Get())); + int result = runner.RunTest(L"HandleInheritanceTests_PrintToStdout"); + ASSERT_EQ(SBOX_TEST_SUCCEEDED, result); + + std::string data; + ASSERT_TRUE(base::ReadFileToString(base::FilePath(temp_file_name), &data)); + // Redirection uses a feature that was added in Windows Vista. + ASSERT_EQ("Example output to stdout\r\n", data); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/handle_interception.cc b/security/sandbox/chromium/sandbox/win/src/handle_interception.cc new file mode 100644 index 0000000000..53db4a8b27 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_interception.cc @@ -0,0 +1,48 @@ +// 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 "sandbox/win/src/handle_interception.h" + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" +#include "mozilla/sandboxing/sandboxLogging.h" + +namespace sandbox { + +ResultCode DuplicateHandleProxy(HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options) { + *target_handle = NULL; + + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + return SBOX_ERROR_NO_SPACE; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::DUPLICATEHANDLEPROXY, + source_handle, target_process_id, + desired_access, options, &answer); + if (SBOX_ALL_OK != code) + return code; + + if (answer.win32_result) { + ::SetLastError(answer.win32_result); + mozilla::sandboxing::LogBlocked("DuplicateHandle"); + return SBOX_ERROR_GENERIC; + } + + *target_handle = answer.handle; + mozilla::sandboxing::LogAllowed("DuplicateHandle"); + return SBOX_ALL_OK; +} + +} // namespace sandbox + diff --git a/security/sandbox/chromium/sandbox/win/src/handle_interception.h b/security/sandbox/chromium/sandbox/win/src/handle_interception.h new file mode 100644 index 0000000000..6f60811f17 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_interception.h @@ -0,0 +1,24 @@ +// 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 "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +#ifndef SANDBOX_SRC_HANDLE_INTERCEPTION_H_ +#define SANDBOX_SRC_HANDLE_INTERCEPTION_H_ + +namespace sandbox { + +// TODO(jschuh) Add an interception to catch dangerous DuplicateHandle calls. + +ResultCode DuplicateHandleProxy(HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options); + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_INTERCEPTION_H_ + diff --git a/security/sandbox/chromium/sandbox/win/src/handle_policy.cc b/security/sandbox/chromium/sandbox/win/src/handle_policy.cc new file mode 100644 index 0000000000..fa3295ae3f --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_policy.cc @@ -0,0 +1,93 @@ +// 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 "sandbox/win/src/handle_policy.h" + +#include <string> + +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/broker_services.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sandbox_utils.h" + +namespace sandbox { + +bool HandlePolicy::GenerateRules(const wchar_t* type_name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + PolicyRule duplicate_rule(ASK_BROKER); + + switch (semantics) { + case TargetPolicy::HANDLES_DUP_ANY: { + if (!duplicate_rule.AddNumberMatch(IF_NOT, HandleTarget::TARGET, + ::GetCurrentProcessId(), EQUAL)) { + return false; + } + break; + } + + case TargetPolicy::HANDLES_DUP_BROKER: { + if (!duplicate_rule.AddNumberMatch(IF, HandleTarget::TARGET, + ::GetCurrentProcessId(), EQUAL)) { + return false; + } + break; + } + + default: + return false; + } + if (!duplicate_rule.AddStringMatch(IF, HandleTarget::NAME, type_name, + CASE_INSENSITIVE)) { + return false; + } + if (!policy->AddRule(IpcTag::DUPLICATEHANDLEPROXY, &duplicate_rule)) { + return false; + } + return true; +} + +DWORD HandlePolicy::DuplicateHandleProxyAction(EvalResult eval_result, + HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options) { + // The only action supported is ASK_BROKER which means duplicate the handle. + if (ASK_BROKER != eval_result) { + return ERROR_ACCESS_DENIED; + } + + base::win::ScopedHandle remote_target_process; + if (target_process_id != ::GetCurrentProcessId()) { + // Sandboxed children are dynamic, so we check that manually. + if (!BrokerServicesBase::GetInstance()->IsSafeDuplicationTarget( + target_process_id)) { + return ERROR_ACCESS_DENIED; + } + + remote_target_process.Set(::OpenProcess(PROCESS_DUP_HANDLE, FALSE, + target_process_id)); + if (!remote_target_process.IsValid()) + return ::GetLastError(); + } + + // If the policy didn't block us and we have no valid target, then the broker + // (this process) is the valid target. + HANDLE target_process = remote_target_process.IsValid() ? + remote_target_process.Get() : ::GetCurrentProcess(); + if (!::DuplicateHandle(::GetCurrentProcess(), source_handle, target_process, + target_handle, desired_access, FALSE, + options)) { + return ::GetLastError(); + } + + return ERROR_SUCCESS; +} + +} // namespace sandbox + diff --git a/security/sandbox/chromium/sandbox/win/src/handle_policy.h b/security/sandbox/chromium/sandbox/win/src/handle_policy.h new file mode 100644 index 0000000000..29ce5ab666 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_policy.h @@ -0,0 +1,39 @@ +// 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. + +#ifndef SANDBOX_SRC_HANDLE_POLICY_H_ +#define SANDBOX_SRC_HANDLE_POLICY_H_ + +#include <string> + +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +enum EvalResult; + +// This class centralizes most of the knowledge related to handle policy. +class HandlePolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for handles, in particular duplicate action. + static bool GenerateRules(const wchar_t* type_name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Processes a 'TargetPolicy::DuplicateHandle()' request from the target. + static DWORD DuplicateHandleProxyAction(EvalResult eval_result, + HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_POLICY_H_ + diff --git a/security/sandbox/chromium/sandbox/win/src/handle_policy_test.cc b/security/sandbox/chromium/sandbox/win/src/handle_policy_test.cc new file mode 100644 index 0000000000..11382da811 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_policy_test.cc @@ -0,0 +1,114 @@ +// 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 "base/strings/stringprintf.h" +#include "sandbox/win/src/handle_policy.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/win_utils.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Just waits for the supplied number of milliseconds. +SBOX_TESTS_COMMAND int Handle_WaitProcess(int argc, wchar_t **argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + ::Sleep(::wcstoul(argv[0], NULL, 10)); + return SBOX_TEST_TIMED_OUT; +} + +// Attempts to duplicate an event handle into the target process. +SBOX_TESTS_COMMAND int Handle_DuplicateEvent(int argc, wchar_t **argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + // Create a test event to use as a handle. + base::win::ScopedHandle test_event; + test_event.Set(::CreateEvent(NULL, TRUE, TRUE, NULL)); + if (!test_event.IsValid()) + return SBOX_TEST_FIRST_ERROR; + + // Get the target process ID. + DWORD target_process_id = ::wcstoul(argv[0], NULL, 10); + + HANDLE handle = NULL; + ResultCode result = SandboxFactory::GetTargetServices()->DuplicateHandle( + test_event.Get(), target_process_id, &handle, 0, DUPLICATE_SAME_ACCESS); + + return (result == SBOX_ALL_OK) ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED; +} + +// Tests that duplicating an object works only when the policy allows it. +TEST(HandlePolicyTest, DuplicateHandle) { + TestRunner target; + TestRunner runner; + + // Kick off an asynchronous target process for testing. + target.SetAsynchronous(true); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, target.RunTest(L"Handle_WaitProcess 30000")); + + // First test that we fail to open the event. + base::string16 cmd_line = base::StringPrintf(L"Handle_DuplicateEvent %d", + target.process_id()); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(cmd_line.c_str())); + + // Now successfully open the event after adding a duplicate handle rule. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_HANDLES, + TargetPolicy::HANDLES_DUP_ANY, + L"Event")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(cmd_line.c_str())); +} + +// Tests that duplicating an object works only when the policy allows it. +TEST(HandlePolicyTest, DuplicatePeerHandle) { + TestRunner target; + TestRunner runner; + + // Kick off an asynchronous target process for testing. + target.SetAsynchronous(true); + target.SetUnsandboxed(true); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, target.RunTest(L"Handle_WaitProcess 30000")); + + // First test that we fail to open the event. + base::string16 cmd_line = base::StringPrintf(L"Handle_DuplicateEvent %d", + target.process_id()); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(cmd_line.c_str())); + + // Now successfully open the event after adding a duplicate handle rule. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_HANDLES, + TargetPolicy::HANDLES_DUP_ANY, + L"Event")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(cmd_line.c_str())); +} + +// Tests that duplicating an object works only when the policy allows it. +TEST(HandlePolicyTest, DuplicateBrokerHandle) { + TestRunner runner; + + // First test that we fail to open the event. + base::string16 cmd_line = base::StringPrintf(L"Handle_DuplicateEvent %d", + ::GetCurrentProcessId()); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(cmd_line.c_str())); + + // Add the peer rule and make sure we fail again. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_HANDLES, + TargetPolicy::HANDLES_DUP_ANY, + L"Event")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(cmd_line.c_str())); + + + // Now successfully open the event after adding a broker handle rule. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_HANDLES, + TargetPolicy::HANDLES_DUP_BROKER, + L"Event")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(cmd_line.c_str())); +} + +} // namespace sandbox + diff --git a/security/sandbox/chromium/sandbox/win/src/heap_helper.cc b/security/sandbox/chromium/sandbox/win/src/heap_helper.cc new file mode 100644 index 0000000000..b0f4498fea --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/heap_helper.cc @@ -0,0 +1,124 @@ +// Copyright 2017 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/win/src/heap_helper.h" + +#include <windows.h> + +#include "base/memory/ref_counted.h" +#include "base/win/windows_version.h" + +namespace sandbox { +namespace { +#pragma pack(1) + +// These are undocumented, but readily found on the internet. +constexpr DWORD kHeapClass8 = 0x00008000; // CSR port heap +constexpr DWORD kHeapClassMask = 0x0000f000; + +constexpr DWORD kHeapSegmentSignature = 0xffeeffee; +constexpr DWORD kHeapSignature = 0xeeffeeff; + +typedef struct _HEAP_ENTRY { + PVOID Data1; + PVOID Data2; +} HEAP_ENTRY, *PHEAP_ENTRY; + +// The _HEAP struct is not documented, so char arrays are used to space out the +// struct of the fields that are not relevant. However, this spacing is +// different because of the different pointer widths between 32 and 64-bit. +// So 32 and 64 bit structs are defined. +struct _HEAP_32 { + HEAP_ENTRY HeapEntry; + DWORD SegmentSignature; + DWORD SegmentFlags; + LIST_ENTRY SegmentListEntry; + struct _HEAP_32* Heap; + char Unknown0[0x24]; + // Offset 0x40 + DWORD Flags; + // Offset 0x60 + char Unknown1[0x1c]; + DWORD Signature; + // Other stuff that is not relevant. +}; + +struct _HEAP_64 { + HEAP_ENTRY HeapEntry; + DWORD SegmentSignature; + DWORD SegmentFlags; + LIST_ENTRY SegmentListEntry; + struct _HEAP_64* Heap; + char Unknown0[0x40]; + // Offset 0x70 + DWORD Flags; + // Offset 0x98 + char Unknown1[0x24]; + DWORD Signature; + // Other stuff that is not relevant. +}; + +#if defined(_WIN64) +using _HEAP = _HEAP_64; +#else // defined(_WIN64) +using _HEAP = _HEAP_32; +#endif // defined(_WIN64) + +bool ValidateHeap(_HEAP* heap) { + if (heap->SegmentSignature != kHeapSegmentSignature) + return false; + if (heap->Heap != heap) + return false; + if (heap->Signature != kHeapSignature) + return false; + return true; +} + +} // namespace + +bool HeapFlags(HANDLE handle, DWORD* flags) { + if (!handle || !flags) { + // This is an error. + return false; + } + _HEAP* heap = reinterpret_cast<_HEAP*>(handle); + if (!ValidateHeap(heap)) { + DLOG(ERROR) << "unable to validate heap"; + return false; + } + *flags = heap->Flags; + return true; +} + +HANDLE FindCsrPortHeap() { + if (base::win::GetVersion() < base::win::Version::WIN10) { + // This functionality has not been verified on versions before Win10. + return nullptr; + } + DWORD number_of_heaps = ::GetProcessHeaps(0, nullptr); + std::unique_ptr<HANDLE[]> all_heaps(new HANDLE[number_of_heaps]); + if (::GetProcessHeaps(number_of_heaps, all_heaps.get()) != number_of_heaps) + return nullptr; + + // Search for the CSR port heap handle, identified purely based on flags. + HANDLE csr_port_heap = nullptr; + for (size_t i = 0; i < number_of_heaps; ++i) { + HANDLE handle = all_heaps[i]; + DWORD flags = 0; + if (!HeapFlags(handle, &flags)) { + DLOG(ERROR) << "Unable to get flags for this heap"; + continue; + } + if ((flags & kHeapClassMask) == kHeapClass8) { + if (csr_port_heap) { + DLOG(ERROR) << "Found multiple suitable CSR Port heaps"; + return nullptr; + } + csr_port_heap = handle; + } + } + return csr_port_heap; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/heap_helper.h b/security/sandbox/chromium/sandbox/win/src/heap_helper.h new file mode 100644 index 0000000000..302a3c4e29 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/heap_helper.h @@ -0,0 +1,26 @@ +// Copyright 2017 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. + +#ifndef SANDBOX_WIN_SRC_HEAP_HELPER_H_ +#define SANDBOX_WIN_SRC_HEAP_HELPER_H_ + +#include <windows.h> + +#include "base/win/windows_version.h" + +namespace sandbox { +// These helper functions are not expected to be used generally, but are exposed +// only to allow direct testing of this functionality. + +// Return the flags for this heap handle. Limited verification of the handle is +// performed. No verification of the flags is performed. +bool HeapFlags(HANDLE handle, DWORD* flags); + +// Return the handle to the CSR Port Heap, return nullptr if none or more than +// one candidate was found. +HANDLE FindCsrPortHeap(); + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_HEAP_HELPER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/integrity_level_test.cc b/security/sandbox/chromium/sandbox/win/src/integrity_level_test.cc new file mode 100644 index 0000000000..a4788af028 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/integrity_level_test.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2011 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 <atlsecurity.h> + +#include "base/process/process_info.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +SBOX_TESTS_COMMAND int CheckUntrustedIntegrityLevel(int argc, wchar_t** argv) { + return (base::GetCurrentProcessIntegrityLevel() == base::UNTRUSTED_INTEGRITY) + ? SBOX_TEST_SUCCEEDED + : SBOX_TEST_FAILED; +} + +SBOX_TESTS_COMMAND int CheckLowIntegrityLevel(int argc, wchar_t** argv) { + return (base::GetCurrentProcessIntegrityLevel() == base::LOW_INTEGRITY) + ? SBOX_TEST_SUCCEEDED + : SBOX_TEST_FAILED; +} + +SBOX_TESTS_COMMAND int CheckIntegrityLevel(int argc, wchar_t** argv) { + ATL::CAccessToken token; + if (!token.GetEffectiveToken(TOKEN_READ)) + return SBOX_TEST_FAILED; + + char* buffer[100]; + DWORD buf_size = 100; + if (!::GetTokenInformation(token.GetHandle(), TokenIntegrityLevel, + reinterpret_cast<void*>(buffer), buf_size, + &buf_size)) + return SBOX_TEST_FAILED; + + TOKEN_MANDATORY_LABEL* label = + reinterpret_cast<TOKEN_MANDATORY_LABEL*>(buffer); + + PSID sid_low = nullptr; + if (!::ConvertStringSidToSid(L"S-1-16-4096", &sid_low)) + return SBOX_TEST_FAILED; + + bool is_low_sid = ::EqualSid(label->Label.Sid, sid_low); + + ::LocalFree(sid_low); + + if (is_low_sid) + return SBOX_TEST_SUCCEEDED; + + return SBOX_TEST_DENIED; +} + +TEST(IntegrityLevelTest, TestLowILReal) { + TestRunner runner(JOB_LOCKDOWN, USER_INTERACTIVE, USER_INTERACTIVE); + + runner.SetTimeout(INFINITE); + + runner.GetPolicy()->SetAlternateDesktop(true); + runner.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckIntegrityLevel")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckIntegrityLevel")); +} + +TEST(DelayedIntegrityLevelTest, TestLowILDelayed) { + TestRunner runner(JOB_LOCKDOWN, USER_INTERACTIVE, USER_INTERACTIVE); + + runner.SetTimeout(INFINITE); + + runner.GetPolicy()->SetDelayedIntegrityLevel(INTEGRITY_LEVEL_LOW); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckIntegrityLevel")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"CheckIntegrityLevel")); +} + +TEST(IntegrityLevelTest, TestNoILChange) { + TestRunner runner(JOB_LOCKDOWN, USER_INTERACTIVE, USER_INTERACTIVE); + + runner.SetTimeout(INFINITE); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"CheckIntegrityLevel")); +} + +TEST(IntegrityLevelTest, TestUntrustedIL) { + TestRunner runner(JOB_LOCKDOWN, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); + runner.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); + runner.GetPolicy()->SetDelayedIntegrityLevel(INTEGRITY_LEVEL_UNTRUSTED); + runner.GetPolicy()->SetLockdownDefaultDacl(); + + runner.SetTimeout(INFINITE); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"CheckUntrustedIntegrityLevel")); +} + +TEST(IntegrityLevelTest, TestLowIL) { + TestRunner runner(JOB_LOCKDOWN, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); + runner.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); + runner.GetPolicy()->SetDelayedIntegrityLevel(INTEGRITY_LEVEL_LOW); + runner.GetPolicy()->SetLockdownDefaultDacl(); + + runner.SetTimeout(INFINITE); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckLowIntegrityLevel")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/interception.cc b/security/sandbox/chromium/sandbox/win/src/interception.cc new file mode 100644 index 0000000000..b2c0bc47ea --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interception.cc @@ -0,0 +1,512 @@ +// 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. + +// For information about interceptions as a whole see +// http://dev.chromium.org/developers/design-documents/sandbox . + +#include "sandbox/win/src/interception.h" + +#include <stddef.h> + +#include <memory> +#include <set> +#include <string> + +#include "base/logging.h" +#include "base/scoped_native_library.h" +#include "base/win/pe_image.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/interception_internal.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_rand.h" +#include "sandbox/win/src/service_resolver.h" +#include "sandbox/win/src/target_interceptions.h" +#include "sandbox/win/src/target_process.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +namespace { + +// Standard allocation granularity and page size for Windows. +const size_t kAllocGranularity = 65536; +const size_t kPageSize = 4096; + +} // namespace + +namespace internal { + +// Find a random offset within 64k and aligned to ceil(log2(size)). +size_t GetGranularAlignedRandomOffset(size_t size) { + CHECK_LE(size, kAllocGranularity); + unsigned int offset; + + do { + GetRandom(&offset); + offset &= (kAllocGranularity - 1); + } while (offset > (kAllocGranularity - size)); + + // Find an alignment between 64 and the page size (4096). + size_t align_size = kPageSize; + for (size_t new_size = align_size / 2; new_size >= size; new_size /= 2) { + align_size = new_size; + } + return offset & ~(align_size - 1); +} + +} // namespace internal + +SANDBOX_INTERCEPT SharedMemory* g_interceptions; + +// Table of the unpatched functions that we intercept. Mapped from the parent. +SANDBOX_INTERCEPT OriginalFunctions g_originals = {nullptr}; + +// Magic constant that identifies that this function is not to be patched. +const char kUnloadDLLDummyFunction[] = "@"; + +InterceptionManager::InterceptionData::InterceptionData() {} + +InterceptionManager::InterceptionData::InterceptionData( + const InterceptionData& other) = default; + +InterceptionManager::InterceptionData::~InterceptionData() {} + +InterceptionManager::InterceptionManager(TargetProcess* child_process, + bool relaxed) + : child_(child_process), names_used_(false), relaxed_(relaxed) { + child_->AddRef(); +} +InterceptionManager::~InterceptionManager() { + child_->Release(); +} + +bool InterceptionManager::AddToPatchedFunctions( + const wchar_t* dll_name, + const char* function_name, + InterceptionType interception_type, + const void* replacement_code_address, + InterceptorId id) { + InterceptionData function; + function.type = interception_type; + function.id = id; + function.dll = dll_name; + function.function = function_name; + function.interceptor_address = replacement_code_address; + + interceptions_.push_back(function); + return true; +} + +bool InterceptionManager::AddToPatchedFunctions( + const wchar_t* dll_name, + const char* function_name, + InterceptionType interception_type, + const char* replacement_function_name, + InterceptorId id) { + InterceptionData function; + function.type = interception_type; + function.id = id; + function.dll = dll_name; + function.function = function_name; + function.interceptor = replacement_function_name; + function.interceptor_address = nullptr; + + interceptions_.push_back(function); + names_used_ = true; + return true; +} + +bool InterceptionManager::AddToUnloadModules(const wchar_t* dll_name) { + InterceptionData module_to_unload; + module_to_unload.type = INTERCEPTION_UNLOAD_MODULE; + module_to_unload.dll = dll_name; + // The next two are dummy values that make the structures regular, instead + // of having special cases. They should not be used. + module_to_unload.function = kUnloadDLLDummyFunction; + module_to_unload.interceptor_address = reinterpret_cast<void*>(1); + + interceptions_.push_back(module_to_unload); + return true; +} + +ResultCode InterceptionManager::InitializeInterceptions() { + if (interceptions_.empty()) + return SBOX_ALL_OK; // Nothing to do here + + size_t buffer_bytes = GetBufferSize(); + std::unique_ptr<char[]> local_buffer(new char[buffer_bytes]); + + if (!SetupConfigBuffer(local_buffer.get(), buffer_bytes)) + return SBOX_ERROR_CANNOT_SETUP_INTERCEPTION_CONFIG_BUFFER; + + void* remote_buffer; + if (!CopyToChildMemory(child_->Process(), local_buffer.get(), buffer_bytes, + &remote_buffer)) + return SBOX_ERROR_CANNOT_COPY_DATA_TO_CHILD; + + bool hot_patch_needed = (0 != buffer_bytes); + ResultCode rc = PatchNtdll(hot_patch_needed); + + if (rc != SBOX_ALL_OK) + return rc; + + g_interceptions = reinterpret_cast<SharedMemory*>(remote_buffer); + rc = child_->TransferVariable("g_interceptions", &g_interceptions, + sizeof(g_interceptions)); + return rc; +} + +size_t InterceptionManager::GetBufferSize() const { + std::set<std::wstring> dlls; + size_t buffer_bytes = 0; + + for (const auto& interception : interceptions_) { + // skip interceptions that are performed from the parent + if (!IsInterceptionPerformedByChild(interception)) + continue; + + if (!dlls.count(interception.dll)) { + // NULL terminate the dll name on the structure + size_t dll_name_bytes = (interception.dll.size() + 1) * sizeof(wchar_t); + + // include the dll related size + buffer_bytes += RoundUpToMultiple( + offsetof(DllPatchInfo, dll_name) + dll_name_bytes, sizeof(size_t)); + dlls.insert(interception.dll); + } + + // we have to NULL terminate the strings on the structure + size_t strings_chars = + interception.function.size() + interception.interceptor.size() + 2; + + // a new FunctionInfo is required per function + size_t record_bytes = offsetof(FunctionInfo, function) + strings_chars; + record_bytes = RoundUpToMultiple(record_bytes, sizeof(size_t)); + buffer_bytes += record_bytes; + } + + if (0 != buffer_bytes) + // add the part of SharedMemory that we have not counted yet + buffer_bytes += offsetof(SharedMemory, dll_list); + + return buffer_bytes; +} + +// Basically, walk the list of interceptions moving them to the config buffer, +// but keeping together all interceptions that belong to the same dll. +// The config buffer is a local buffer, not the one allocated on the child. +bool InterceptionManager::SetupConfigBuffer(void* buffer, size_t buffer_bytes) { + if (0 == buffer_bytes) + return true; + + DCHECK(buffer_bytes > sizeof(SharedMemory)); + + SharedMemory* shared_memory = reinterpret_cast<SharedMemory*>(buffer); + DllPatchInfo* dll_info = shared_memory->dll_list; + int num_dlls = 0; + + shared_memory->interceptor_base = + names_used_ ? child_->MainModule() : nullptr; + + buffer_bytes -= offsetof(SharedMemory, dll_list); + buffer = dll_info; + + std::list<InterceptionData>::iterator it = interceptions_.begin(); + for (; it != interceptions_.end();) { + // skip interceptions that are performed from the parent + if (!IsInterceptionPerformedByChild(*it)) { + ++it; + continue; + } + + const std::wstring dll = it->dll; + if (!SetupDllInfo(*it, &buffer, &buffer_bytes)) + return false; + + // walk the interceptions from this point, saving the ones that are + // performed on this dll, and removing the entry from the list. + // advance the iterator before removing the element from the list + std::list<InterceptionData>::iterator rest = it; + for (; rest != interceptions_.end();) { + if (rest->dll == dll) { + if (!SetupInterceptionInfo(*rest, &buffer, &buffer_bytes, dll_info)) + return false; + if (it == rest) + ++it; + rest = interceptions_.erase(rest); + } else { + ++rest; + } + } + dll_info = reinterpret_cast<DllPatchInfo*>(buffer); + ++num_dlls; + } + + shared_memory->num_intercepted_dlls = num_dlls; + return true; +} + +// Fills up just the part that depends on the dll, not the info that depends on +// the actual interception. +bool InterceptionManager::SetupDllInfo(const InterceptionData& data, + void** buffer, + size_t* buffer_bytes) const { + DCHECK(buffer_bytes); + DCHECK(buffer); + DCHECK(*buffer); + + DllPatchInfo* dll_info = reinterpret_cast<DllPatchInfo*>(*buffer); + + // the strings have to be zero terminated + size_t required = offsetof(DllPatchInfo, dll_name) + + (data.dll.size() + 1) * sizeof(wchar_t); + required = RoundUpToMultiple(required, sizeof(size_t)); + if (*buffer_bytes < required) + return false; + + *buffer_bytes -= required; + *buffer = reinterpret_cast<char*>(*buffer) + required; + + // set up the dll info to be what we know about it at this time + dll_info->unload_module = (data.type == INTERCEPTION_UNLOAD_MODULE); + dll_info->record_bytes = required; + dll_info->offset_to_functions = required; + dll_info->num_functions = 0; + data.dll.copy(dll_info->dll_name, data.dll.size()); + dll_info->dll_name[data.dll.size()] = L'\0'; + + return true; +} + +bool InterceptionManager::SetupInterceptionInfo(const InterceptionData& data, + void** buffer, + size_t* buffer_bytes, + DllPatchInfo* dll_info) const { + DCHECK(buffer_bytes); + DCHECK(buffer); + DCHECK(*buffer); + + if ((dll_info->unload_module) && (data.function != kUnloadDLLDummyFunction)) { + // Can't specify a dll for both patch and unload. + NOTREACHED(); + } + + FunctionInfo* function = reinterpret_cast<FunctionInfo*>(*buffer); + + size_t name_bytes = data.function.size(); + size_t interceptor_bytes = data.interceptor.size(); + + // the strings at the end of the structure are zero terminated + size_t required = + offsetof(FunctionInfo, function) + name_bytes + interceptor_bytes + 2; + required = RoundUpToMultiple(required, sizeof(size_t)); + if (*buffer_bytes < required) + return false; + + // update the caller's values + *buffer_bytes -= required; + *buffer = reinterpret_cast<char*>(*buffer) + required; + + function->record_bytes = required; + function->type = data.type; + function->id = data.id; + function->interceptor_address = data.interceptor_address; + char* names = function->function; + + data.function.copy(names, name_bytes); + names += name_bytes; + *names++ = '\0'; + + // interceptor follows the function_name + data.interceptor.copy(names, interceptor_bytes); + names += interceptor_bytes; + *names++ = '\0'; + + // update the dll table + dll_info->num_functions++; + dll_info->record_bytes += required; + + return true; +} + +// Only return true if the child should be able to perform this interception. +bool InterceptionManager::IsInterceptionPerformedByChild( + const InterceptionData& data) const { + if (INTERCEPTION_INVALID == data.type) + return false; + + if (INTERCEPTION_SERVICE_CALL == data.type) + return false; + + if (data.type >= INTERCEPTION_LAST) + return false; + + std::wstring ntdll(kNtdllName); + if (ntdll == data.dll) + return false; // ntdll has to be intercepted from the parent + + return true; +} + +ResultCode InterceptionManager::PatchNtdll(bool hot_patch_needed) { + // Maybe there is nothing to do + if (!hot_patch_needed && interceptions_.empty()) + return SBOX_ALL_OK; + + if (hot_patch_needed) { +#if defined(SANDBOX_EXPORTS) +// Make sure the functions are not excluded by the linker. +#if defined(_WIN64) +#pragma comment(linker, "/include:TargetNtMapViewOfSection64") +#pragma comment(linker, "/include:TargetNtUnmapViewOfSection64") +#else +#pragma comment(linker, "/include:_TargetNtMapViewOfSection@44") +#pragma comment(linker, "/include:_TargetNtUnmapViewOfSection@12") +#endif +#endif // defined(SANDBOX_EXPORTS) + ADD_NT_INTERCEPTION(NtMapViewOfSection, MAP_VIEW_OF_SECTION_ID, 44); + ADD_NT_INTERCEPTION(NtUnmapViewOfSection, UNMAP_VIEW_OF_SECTION_ID, 12); + } + + // Reserve a full 64k memory range in the child process. + HANDLE child = child_->Process(); + BYTE* thunk_base = reinterpret_cast<BYTE*>(::VirtualAllocEx( + child, nullptr, kAllocGranularity, MEM_RESERVE, PAGE_NOACCESS)); + + // Find an aligned, random location within the reserved range. + size_t thunk_bytes = + interceptions_.size() * sizeof(ThunkData) + sizeof(DllInterceptionData); + size_t thunk_offset = internal::GetGranularAlignedRandomOffset(thunk_bytes); + + // Split the base and offset along page boundaries. + thunk_base += thunk_offset & ~(kPageSize - 1); + thunk_offset &= kPageSize - 1; + + // Make an aligned, padded allocation, and move the pointer to our chunk. + size_t thunk_bytes_padded = (thunk_bytes + kPageSize - 1) & ~(kPageSize - 1); + thunk_base = reinterpret_cast<BYTE*>( + ::VirtualAllocEx(child, thunk_base, thunk_bytes_padded, MEM_COMMIT, + PAGE_EXECUTE_READWRITE)); + CHECK(thunk_base); // If this fails we'd crash anyway on an invalid access. + DllInterceptionData* thunks = + reinterpret_cast<DllInterceptionData*>(thunk_base + thunk_offset); + + DllInterceptionData dll_data; + dll_data.data_bytes = thunk_bytes; + dll_data.num_thunks = 0; + dll_data.used_bytes = offsetof(DllInterceptionData, thunks); + + // Reset all helpers for a new child. + memset(g_originals, 0, sizeof(g_originals)); + + // this should write all the individual thunks to the child's memory + ResultCode rc = PatchClientFunctions(thunks, thunk_bytes, &dll_data); + + if (rc != SBOX_ALL_OK) + return rc; + + // and now write the first part of the table to the child's memory + SIZE_T written; + bool ok = + !!::WriteProcessMemory(child, thunks, &dll_data, + offsetof(DllInterceptionData, thunks), &written); + + if (!ok || (offsetof(DllInterceptionData, thunks) != written)) + return SBOX_ERROR_CANNOT_WRITE_INTERCEPTION_THUNK; + + // Attempt to protect all the thunks, but ignore failure + DWORD old_protection; + ::VirtualProtectEx(child, thunks, thunk_bytes, PAGE_EXECUTE_READ, + &old_protection); + + ResultCode ret = + child_->TransferVariable("g_originals", g_originals, sizeof(g_originals)); + return ret; +} + +ResultCode InterceptionManager::PatchClientFunctions( + DllInterceptionData* thunks, + size_t thunk_bytes, + DllInterceptionData* dll_data) { + DCHECK(thunks); + DCHECK(dll_data); + + HMODULE ntdll_base = ::GetModuleHandle(kNtdllName); + if (!ntdll_base) + return SBOX_ERROR_NO_HANDLE; + + char* interceptor_base = nullptr; + +#if defined(SANDBOX_EXPORTS) + interceptor_base = reinterpret_cast<char*>(child_->MainModule()); + base::ScopedNativeLibrary local_interceptor(::LoadLibrary(child_->Name())); +#endif // defined(SANDBOX_EXPORTS) + + std::unique_ptr<ServiceResolverThunk> thunk; +#if defined(_WIN64) + thunk.reset(new ServiceResolverThunk(child_->Process(), relaxed_)); +#else + base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); + if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) { + if (os_info->version() >= base::win::Version::WIN10) + thunk.reset(new Wow64W10ResolverThunk(child_->Process(), relaxed_)); + else if (os_info->version() >= base::win::Version::WIN8) + thunk.reset(new Wow64W8ResolverThunk(child_->Process(), relaxed_)); + else + thunk.reset(new Wow64ResolverThunk(child_->Process(), relaxed_)); + } else if (os_info->version() >= base::win::Version::WIN8) { + thunk.reset(new Win8ResolverThunk(child_->Process(), relaxed_)); + } else { + thunk.reset(new ServiceResolverThunk(child_->Process(), relaxed_)); + } +#endif + + for (auto interception : interceptions_) { + const std::wstring ntdll(kNtdllName); + if (interception.dll != ntdll) + return SBOX_ERROR_BAD_PARAMS; + + if (INTERCEPTION_SERVICE_CALL != interception.type) + return SBOX_ERROR_BAD_PARAMS; + +#if defined(SANDBOX_EXPORTS) + // We may be trying to patch by function name. + if (!interception.interceptor_address) { + const char* address; + NTSTATUS ret = thunk->ResolveInterceptor( + local_interceptor.get(), interception.interceptor.c_str(), + reinterpret_cast<const void**>(&address)); + if (!NT_SUCCESS(ret)) { + ::SetLastError(GetLastErrorFromNtStatus(ret)); + return SBOX_ERROR_CANNOT_RESOLVE_INTERCEPTION_THUNK; + } + + // Translate the local address to an address on the child. + interception.interceptor_address = + interceptor_base + + (address - reinterpret_cast<char*>(local_interceptor.get())); + } +#endif // defined(SANDBOX_EXPORTS) + NTSTATUS ret = thunk->Setup( + ntdll_base, interceptor_base, interception.function.c_str(), + interception.interceptor.c_str(), interception.interceptor_address, + &thunks->thunks[dll_data->num_thunks], + thunk_bytes - dll_data->used_bytes, nullptr); + if (!NT_SUCCESS(ret)) { + ::SetLastError(GetLastErrorFromNtStatus(ret)); + return SBOX_ERROR_CANNOT_SETUP_INTERCEPTION_THUNK; + } + + DCHECK(!g_originals[interception.id]); + g_originals[interception.id] = &thunks->thunks[dll_data->num_thunks]; + + dll_data->num_thunks++; + dll_data->used_bytes += sizeof(ThunkData); + } + + return SBOX_ALL_OK; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/interception.h b/security/sandbox/chromium/sandbox/win/src/interception.h new file mode 100644 index 0000000000..6b4612fb6c --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interception.h @@ -0,0 +1,290 @@ +// Copyright (c) 2011 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. + +// Defines InterceptionManager, the class in charge of setting up interceptions +// for the sandboxed process. For more details see +// http://dev.chromium.org/developers/design-documents/sandbox . + +#ifndef SANDBOX_SRC_INTERCEPTION_H_ +#define SANDBOX_SRC_INTERCEPTION_H_ + +#include <stddef.h> + +#include <list> +#include <string> + +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +class TargetProcess; + +// Internal structures used for communication between the broker and the target. +struct DllPatchInfo; +struct DllInterceptionData; + +// The InterceptionManager executes on the parent application, and it is in +// charge of setting up the desired interceptions, and placing the Interception +// Agent into the child application. +// +// The exposed API consists of two methods: AddToPatchedFunctions to set up a +// particular interception, and InitializeInterceptions to actually go ahead and +// perform all interceptions and transfer data to the child application. +// +// The typical usage is something like this: +// +// InterceptionManager interception_manager(child); +// if (!interception_manager.AddToPatchedFunctions( +// L"ntdll.dll", "NtCreateFile", +// sandbox::INTERCEPTION_SERVICE_CALL, &MyNtCreateFile, MY_ID_1)) +// return false; +// +// if (!interception_manager.AddToPatchedFunctions( +// L"kernel32.dll", "CreateDirectoryW", +// sandbox::INTERCEPTION_EAT, L"MyCreateDirectoryW@12", MY_ID_2)) +// return false; +// +// sandbox::ResultCode rc = interception_manager.InitializeInterceptions(); +// if (rc != sandbox::SBOX_ALL_OK) { +// DWORD error = ::GetLastError(); +// return rc; +// } +// +// Any required syncronization must be performed outside this class. Also, it is +// not possible to perform further interceptions after InitializeInterceptions +// is called. +// +class InterceptionManager { + // The unit test will access private members. + // Allow tests to be marked DISABLED_. Note that FLAKY_ and FAILS_ prefixes + // do not work with sandbox tests. + FRIEND_TEST_ALL_PREFIXES(InterceptionManagerTest, BufferLayout1); + FRIEND_TEST_ALL_PREFIXES(InterceptionManagerTest, BufferLayout2); + + public: + // An interception manager performs interceptions on a given child process. + // If we are allowed to intercept functions that have been patched by somebody + // else, relaxed should be set to true. + // Note: We increase the child's reference count internally. + InterceptionManager(TargetProcess* child_process, bool relaxed); + ~InterceptionManager(); + + // Patches function_name inside dll_name to point to replacement_code_address. + // function_name has to be an exported symbol of dll_name. + // Returns true on success. + // + // The new function should match the prototype and calling convention of the + // function to intercept except for one extra argument (the first one) that + // contains a pointer to the original function, to simplify the development + // of interceptors (for IA32). In x64, there is no extra argument to the + // interceptor, so the provided InterceptorId is used to keep a table of + // intercepted functions so that the interceptor can index that table to get + // the pointer that would have been the first argument (g_originals[id]). + // + // For example, to intercept NtClose, the following code could be used: + // + // typedef NTSTATUS (WINAPI *NtCloseFunction) (IN HANDLE Handle); + // NTSTATUS WINAPI MyNtCose(IN NtCloseFunction OriginalClose, + // IN HANDLE Handle) { + // // do something + // // call the original function + // return OriginalClose(Handle); + // } + // + // And in x64: + // + // typedef NTSTATUS (WINAPI *NtCloseFunction) (IN HANDLE Handle); + // NTSTATUS WINAPI MyNtCose64(IN HANDLE Handle) { + // // do something + // // call the original function + // NtCloseFunction OriginalClose = g_originals[NT_CLOSE_ID]; + // return OriginalClose(Handle); + // } + bool AddToPatchedFunctions(const wchar_t* dll_name, + const char* function_name, + InterceptionType interception_type, + const void* replacement_code_address, + InterceptorId id); + + // Patches function_name inside dll_name to point to + // replacement_function_name. + bool AddToPatchedFunctions(const wchar_t* dll_name, + const char* function_name, + InterceptionType interception_type, + const char* replacement_function_name, + InterceptorId id); + + // The interception agent will unload the dll with dll_name. + bool AddToUnloadModules(const wchar_t* dll_name); + + // Initializes all interceptions on the client. + // Returns SBOX_ALL_OK on success, or an appropriate error code. + // + // The child process must be created suspended, and cannot be resumed until + // after this method returns. In addition, no action should be performed on + // the child that may cause it to resume momentarily, such as injecting + // threads or APCs. + // + // This function must be called only once, after all interceptions have been + // set up using AddToPatchedFunctions. + ResultCode InitializeInterceptions(); + + private: + // Used to store the interception information until the actual set-up. + struct InterceptionData { + InterceptionData(); + InterceptionData(const InterceptionData& other); + ~InterceptionData(); + + InterceptionType type; // Interception type. + InterceptorId id; // Interceptor id. + std::wstring dll; // Name of dll to intercept. + std::string function; // Name of function to intercept. + std::string interceptor; // Name of interceptor function. + const void* interceptor_address; // Interceptor's entry point. + }; + + // Calculates the size of the required configuration buffer. + size_t GetBufferSize() const; + + // Rounds up the size of a given buffer, considering alignment (padding). + // value is the current size of the buffer, and alignment is specified in + // bytes. + static inline size_t RoundUpToMultiple(size_t value, size_t alignment) { + return ((value + alignment - 1) / alignment) * alignment; + } + + // Sets up a given buffer with all the information that has to be transfered + // to the child. + // Returns true on success. + // + // The buffer size should be at least the value returned by GetBufferSize + bool SetupConfigBuffer(void* buffer, size_t buffer_bytes); + + // Fills up the part of the transfer buffer that corresponds to information + // about one dll to patch. + // data is the first recorded interception for this dll. + // Returns true on success. + // + // On successful return, buffer will be advanced from it's current position + // to the point where the next block of configuration data should be written + // (the actual interception info), and the current size of the buffer will + // decrease to account the space used by this method. + bool SetupDllInfo(const InterceptionData& data, + void** buffer, + size_t* buffer_bytes) const; + + // Fills up the part of the transfer buffer that corresponds to a single + // function to patch. + // dll_info points to the dll being updated with the interception stored on + // data. The buffer pointer and remaining size are updated by this call. + // Returns true on success. + bool SetupInterceptionInfo(const InterceptionData& data, + void** buffer, + size_t* buffer_bytes, + DllPatchInfo* dll_info) const; + + // Returns true if this interception is to be performed by the child + // as opposed to from the parent. + bool IsInterceptionPerformedByChild(const InterceptionData& data) const; + + // Allocates a buffer on the child's address space (returned on + // remote_buffer), and fills it with the contents of a local buffer. + // Returns SBOX_ALL_OK on success. + ResultCode CopyDataToChild(const void* local_buffer, + size_t buffer_bytes, + void** remote_buffer) const; + + // Performs the cold patch (from the parent) of ntdll. + // Returns SBOX_ALL_OK on success. + // + // This method will insert additional interceptions to launch the interceptor + // agent on the child process, if there are additional interceptions to do. + ResultCode PatchNtdll(bool hot_patch_needed); + + // Peforms the actual interceptions on ntdll. + // thunks is the memory to store all the thunks for this dll (on the child), + // and dll_data is a local buffer to hold global dll interception info. + // Returns SBOX_ALL_OK on success. + ResultCode PatchClientFunctions(DllInterceptionData* thunks, + size_t thunk_bytes, + DllInterceptionData* dll_data); + + // The process to intercept. + TargetProcess* child_; + // Holds all interception info until the call to initialize (perform the + // actual patch). + std::list<InterceptionData> interceptions_; + + // Keep track of patches added by name. + bool names_used_; + + // true if we are allowed to patch already-patched functions. + bool relaxed_; + + DISALLOW_COPY_AND_ASSIGN(InterceptionManager); +}; + +// This macro simply calls interception_manager.AddToPatchedFunctions with +// the given service to intercept (INTERCEPTION_SERVICE_CALL), and assumes that +// the interceptor is called "TargetXXX", where XXX is the name of the service. +// Note that num_params is the number of bytes to pop out of the stack for +// the exported interceptor, following the calling convention of a service call +// (WINAPI = with the "C" underscore). +#if SANDBOX_EXPORTS +#if defined(_WIN64) +#define MAKE_SERVICE_NAME(service, params) "Target" #service "64" +#else +#define MAKE_SERVICE_NAME(service, params) "_Target" #service "@" #params +#endif + +#define ADD_NT_INTERCEPTION(service, id, num_params) \ + AddToPatchedFunctions(kNtdllName, #service, \ + sandbox::INTERCEPTION_SERVICE_CALL, \ + MAKE_SERVICE_NAME(service, num_params), id) + +#define INTERCEPT_NT(manager, service, id, num_params) \ + ((&Target##service) ? manager->ADD_NT_INTERCEPTION(service, id, num_params) \ + : false) + +// When intercepting the EAT it is important that the patched version of the +// function not call any functions imported from system libraries unless +// |TargetServices::InitCalled()| returns true, because it is only then that +// we are guaranteed that our IAT has been initialized. +#define INTERCEPT_EAT(manager, dll, function, id, num_params) \ + ((&Target##function) ? manager->AddToPatchedFunctions( \ + dll, #function, sandbox::INTERCEPTION_EAT, \ + MAKE_SERVICE_NAME(function, num_params), id) \ + : false) +#else // SANDBOX_EXPORTS +#if defined(_WIN64) +#define MAKE_SERVICE_NAME(service) &Target##service##64 +#else +#define MAKE_SERVICE_NAME(service) &Target##service +#endif + +#define ADD_NT_INTERCEPTION(service, id, num_params) \ + AddToPatchedFunctions( \ + kNtdllName, #service, sandbox::INTERCEPTION_SERVICE_CALL, \ + reinterpret_cast<void*>(MAKE_SERVICE_NAME(service)), id) + +#define INTERCEPT_NT(manager, service, id, num_params) \ + manager->ADD_NT_INTERCEPTION(service, id, num_params) + +// When intercepting the EAT it is important that the patched version of the +// function not call any functions imported from system libraries unless +// |TargetServices::InitCalled()| returns true, because it is only then that +// we are guaranteed that our IAT has been initialized. +#define INTERCEPT_EAT(manager, dll, function, id, num_params) \ + manager->AddToPatchedFunctions( \ + dll, #function, sandbox::INTERCEPTION_EAT, \ + reinterpret_cast<void*>(MAKE_SERVICE_NAME(function)), id) +#endif // SANDBOX_EXPORTS + +} // namespace sandbox + +#endif // SANDBOX_SRC_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/interception_agent.cc b/security/sandbox/chromium/sandbox/win/src/interception_agent.cc new file mode 100644 index 0000000000..e095328a71 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interception_agent.cc @@ -0,0 +1,234 @@ +// Copyright (c) 2006-2010 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. + +// For information about interceptions as a whole see +// http://dev.chromium.org/developers/design-documents/sandbox . + +#include "sandbox/win/src/interception_agent.h" + +#include <windows.h> + +#include <stddef.h> + +#include "sandbox/win/src/eat_resolver.h" +#include "sandbox/win/src/interception_internal.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sidestep_resolver.h" + +namespace { + +// Returns true if target lies between base and base + range. +bool IsWithinRange(const void* base, size_t range, const void* target) { + const char* end = reinterpret_cast<const char*>(base) + range; + return reinterpret_cast<const char*>(target) < end; +} + +} // namespace + +namespace sandbox { + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt; + +// The list of intercepted functions back-pointers. +SANDBOX_INTERCEPT OriginalFunctions g_originals; + +// Memory buffer mapped from the parent, with the list of interceptions. +SANDBOX_INTERCEPT SharedMemory* g_interceptions = nullptr; + +InterceptionAgent* InterceptionAgent::GetInterceptionAgent() { + static InterceptionAgent* s_singleton = nullptr; + if (!s_singleton) { + if (!g_interceptions) + return nullptr; + + size_t array_bytes = g_interceptions->num_intercepted_dlls * sizeof(void*); + s_singleton = reinterpret_cast<InterceptionAgent*>( + new (NT_ALLOC) char[array_bytes + sizeof(InterceptionAgent)]); + + bool success = s_singleton->Init(g_interceptions); + if (!success) { + operator delete(s_singleton, NT_ALLOC); + s_singleton = nullptr; + } + } + return s_singleton; +} + +bool InterceptionAgent::Init(SharedMemory* shared_memory) { + interceptions_ = shared_memory; + for (int i = 0; i < shared_memory->num_intercepted_dlls; i++) + dlls_[i] = nullptr; + return true; +} + +bool InterceptionAgent::DllMatch(const UNICODE_STRING* full_path, + const UNICODE_STRING* name, + const DllPatchInfo* dll_info) { + UNICODE_STRING current_name; + current_name.Length = + static_cast<USHORT>(g_nt.wcslen(dll_info->dll_name) * sizeof(wchar_t)); + current_name.MaximumLength = current_name.Length; + current_name.Buffer = const_cast<wchar_t*>(dll_info->dll_name); + + BOOLEAN case_insensitive = TRUE; + if (full_path && + !g_nt.RtlCompareUnicodeString(¤t_name, full_path, case_insensitive)) + return true; + + if (name && + !g_nt.RtlCompareUnicodeString(¤t_name, name, case_insensitive)) + return true; + + return false; +} + +bool InterceptionAgent::OnDllLoad(const UNICODE_STRING* full_path, + const UNICODE_STRING* name, + void* base_address) { + DllPatchInfo* dll_info = interceptions_->dll_list; + int i = 0; + for (; i < interceptions_->num_intercepted_dlls; i++) { + if (DllMatch(full_path, name, dll_info)) + break; + + dll_info = reinterpret_cast<DllPatchInfo*>( + reinterpret_cast<char*>(dll_info) + dll_info->record_bytes); + } + + // Return now if the dll is not in our list of interest. + if (i == interceptions_->num_intercepted_dlls) + return true; + + // The dll must be unloaded. + if (dll_info->unload_module) + return false; + + // Purify causes this condition to trigger. + if (dlls_[i]) + return true; + + size_t buffer_bytes = offsetof(DllInterceptionData, thunks) + + dll_info->num_functions * sizeof(ThunkData); + dlls_[i] = reinterpret_cast<DllInterceptionData*>( + new (NT_PAGE, base_address) char[buffer_bytes]); + + DCHECK_NT(dlls_[i]); + if (!dlls_[i]) + return true; + + dlls_[i]->data_bytes = buffer_bytes; + dlls_[i]->num_thunks = 0; + dlls_[i]->base = base_address; + dlls_[i]->used_bytes = offsetof(DllInterceptionData, thunks); + + VERIFY(PatchDll(dll_info, dlls_[i])); + + ULONG old_protect; + SIZE_T real_size = buffer_bytes; + void* to_protect = dlls_[i]; + VERIFY_SUCCESS(g_nt.ProtectVirtualMemory(NtCurrentProcess, &to_protect, + &real_size, PAGE_EXECUTE_READ, + &old_protect)); + return true; +} + +void InterceptionAgent::OnDllUnload(void* base_address) { + for (int i = 0; i < interceptions_->num_intercepted_dlls; i++) { + if (dlls_[i] && dlls_[i]->base == base_address) { + operator delete(dlls_[i], NT_PAGE); + dlls_[i] = nullptr; + break; + } + } +} + +// TODO(rvargas): We have to deal with prebinded dlls. I see two options: change +// the timestamp of the patched dll, or modify the info on the prebinded dll. +// the first approach messes matching of debug symbols, the second one is more +// complicated. +bool InterceptionAgent::PatchDll(const DllPatchInfo* dll_info, + DllInterceptionData* thunks) { + DCHECK_NT(thunks); + DCHECK_NT(dll_info); + + const FunctionInfo* function = reinterpret_cast<const FunctionInfo*>( + reinterpret_cast<const char*>(dll_info) + dll_info->offset_to_functions); + + for (int i = 0; i < dll_info->num_functions; i++) { + if (!IsWithinRange(dll_info, dll_info->record_bytes, function->function)) { + NOTREACHED_NT(); + return false; + } + + ResolverThunk* resolver = GetResolver(function->type); + if (!resolver) + return false; + + const char* interceptor = + function->function + g_nt.strlen(function->function) + 1; + + if (!IsWithinRange(function, function->record_bytes, interceptor) || + !IsWithinRange(dll_info, dll_info->record_bytes, interceptor)) { + NOTREACHED_NT(); + return false; + } + + NTSTATUS ret = resolver->Setup( + thunks->base, interceptions_->interceptor_base, function->function, + interceptor, function->interceptor_address, &thunks->thunks[i], + sizeof(ThunkData), nullptr); + if (!NT_SUCCESS(ret)) { + NOTREACHED_NT(); + return false; + } + + DCHECK_NT(!g_originals[function->id] || + g_originals[function->id] == &thunks->thunks[i]); + g_originals[function->id] = &thunks->thunks[i]; + + thunks->num_thunks++; + thunks->used_bytes += sizeof(ThunkData); + + function = reinterpret_cast<const FunctionInfo*>( + reinterpret_cast<const char*>(function) + function->record_bytes); + } + + return true; +} + +// This method is called from within the loader lock +ResolverThunk* InterceptionAgent::GetResolver(InterceptionType type) { + static EatResolverThunk* eat_resolver = nullptr; + static SidestepResolverThunk* sidestep_resolver = nullptr; + static SmartSidestepResolverThunk* smart_sidestep_resolver = nullptr; + + if (!eat_resolver) + eat_resolver = new (NT_ALLOC) EatResolverThunk; + +#if !defined(_WIN64) + // Sidestep is not supported for x64. + if (!sidestep_resolver) + sidestep_resolver = new (NT_ALLOC) SidestepResolverThunk; + + if (!smart_sidestep_resolver) + smart_sidestep_resolver = new (NT_ALLOC) SmartSidestepResolverThunk; +#endif + + switch (type) { + case INTERCEPTION_EAT: + return eat_resolver; + case INTERCEPTION_SIDESTEP: + return sidestep_resolver; + case INTERCEPTION_SMART_SIDESTEP: + return smart_sidestep_resolver; + default: + NOTREACHED_NT(); + } + + return nullptr; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/interception_agent.h b/security/sandbox/chromium/sandbox/win/src/interception_agent.h new file mode 100644 index 0000000000..b2bce08b09 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interception_agent.h @@ -0,0 +1,87 @@ +// Copyright (c) 2006-2008 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. + +// Defines InterceptionAgent, the class in charge of setting up interceptions +// from the inside of the sandboxed process. For more details see +// http://dev.chromium.org/developers/design-documents/sandbox . + +#ifndef SANDBOX_SRC_INTERCEPTION_AGENT_H__ +#define SANDBOX_SRC_INTERCEPTION_AGENT_H__ + +#include "base/macros.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +// Internal structures used for communication between the broker and the target. +struct DllInterceptionData; +struct SharedMemory; +struct DllPatchInfo; + +class ResolverThunk; + +// The InterceptionAgent executes on the target application, and it is in charge +// of setting up the desired interceptions or indicating what module needs to +// be unloaded. +// +// The exposed API consists of three methods: GetInterceptionAgent to retrieve +// the single class instance, OnDllLoad and OnDllUnload to process a dll being +// loaded and unloaded respectively. +// +// This class assumes that it will get called for every dll being loaded, +// starting with kernel32, so the singleton will be instantiated from within the +// loader lock. +class InterceptionAgent { + public: + // Returns the single InterceptionAgent object for this process. + static InterceptionAgent* GetInterceptionAgent(); + + // This method should be invoked whenever a new dll is loaded to perform the + // required patches. If the return value is false, this dll should not be + // allowed to load. + // + // full_path is the (optional) full name of the module being loaded and name + // is the internal module name. If full_path is provided, it will be used + // before the internal name to determine if we care about this dll. + bool OnDllLoad(const UNICODE_STRING* full_path, const UNICODE_STRING* name, + void* base_address); + + // Performs cleanup when a dll is unloaded. + void OnDllUnload(void* base_address); + + private: + ~InterceptionAgent() {} + + // Performs initialization of the singleton. + bool Init(SharedMemory* shared_memory); + + // Returns true if we are interested on this dll. dll_info is an entry of the + // list of intercepted dlls. + bool DllMatch(const UNICODE_STRING* full_path, const UNICODE_STRING* name, + const DllPatchInfo* dll_info); + + // Performs the patching of the dll loaded at base_address. + // The patches to perform are described on dll_info, and thunks is the thunk + // storage for the whole dll. + // Returns true on success. + bool PatchDll(const DllPatchInfo* dll_info, DllInterceptionData* thunks); + + // Returns a resolver for a given interception type. + ResolverThunk* GetResolver(InterceptionType type); + + // Shared memory containing the list of functions to intercept. + SharedMemory* interceptions_; + + // Array of thunk data buffers for the intercepted dlls. This object singleton + // is allocated with a placement new with enough space to hold the complete + // array of pointers, not just the first element. + DllInterceptionData* dlls_[1]; + + DISALLOW_IMPLICIT_CONSTRUCTORS(InterceptionAgent); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_INTERCEPTION_AGENT_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/interception_internal.h b/security/sandbox/chromium/sandbox/win/src/interception_internal.h new file mode 100644 index 0000000000..bf452e049e --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interception_internal.h @@ -0,0 +1,77 @@ +// Copyright (c) 2006-2010 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. + +// Defines InterceptionManager, the class in charge of setting up interceptions +// for the sandboxed process. For more details see: +// http://dev.chromium.org/developers/design-documents/sandbox . + +#ifndef SANDBOX_SRC_INTERCEPTION_INTERNAL_H_ +#define SANDBOX_SRC_INTERCEPTION_INTERNAL_H_ + +#include <stddef.h> + +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +const int kMaxThunkDataBytes = 64; + +// The following structures contain variable size fields at the end, and will be +// used to transfer information between two processes. In order to guarantee +// our ability to follow the chain of structures, the alignment should be fixed, +// hence this pragma. +#pragma pack(push, 4) + +// Structures for the shared memory that contains patching information +// for the InterceptionAgent. +// A single interception: +struct FunctionInfo { + size_t record_bytes; // rounded to sizeof(size_t) bytes + InterceptionType type; + InterceptorId id; + const void* interceptor_address; + char function[1]; // placeholder for null terminated name + // char interceptor[] // followed by the interceptor function +}; + +// A single dll: +struct DllPatchInfo { + size_t record_bytes; // rounded to sizeof(size_t) bytes + size_t offset_to_functions; + int num_functions; + bool unload_module; + wchar_t dll_name[1]; // placeholder for null terminated name + // FunctionInfo function_info[] // followed by the functions to intercept +}; + +// All interceptions: +struct SharedMemory { + int num_intercepted_dlls; + void* interceptor_base; + DllPatchInfo dll_list[1]; // placeholder for the list of dlls +}; + +// Dummy single thunk: +struct ThunkData { + char data[kMaxThunkDataBytes]; +}; + +// In-memory representation of the interceptions for a given dll: +struct DllInterceptionData { + size_t data_bytes; + size_t used_bytes; + void* base; + int num_thunks; +#if defined(_WIN64) + int dummy; // Improve alignment. +#endif + ThunkData thunks[1]; +}; + +#pragma pack(pop) + +} // namespace sandbox + +#endif // SANDBOX_SRC_INTERCEPTION_INTERNAL_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/interception_unittest.cc b/security/sandbox/chromium/sandbox/win/src/interception_unittest.cc new file mode 100644 index 0000000000..0120682bcb --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interception_unittest.cc @@ -0,0 +1,263 @@ +// Copyright (c) 2011 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. + +// This file contains unit tests for InterceptionManager. +// The tests require private information so the whole interception.cc file is +// included from this file. + +#include "sandbox/win/src/interception.h" + +#include <windows.h> + +#include <stddef.h> + +#include <algorithm> +#include <memory> +#include <set> + +#include "base/bits.h" +#include "sandbox/win/src/interception_internal.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/target_process.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace internal { +size_t GetGranularAlignedRandomOffset(size_t size); +} + +// Walks the settings buffer, verifying that the values make sense and counting +// objects. +// Arguments: +// buffer (in): the buffer to walk. +// size (in): buffer size +// num_dlls (out): count of the dlls on the buffer. +// num_function (out): count of intercepted functions. +// num_names (out): count of named interceptor functions. +void WalkBuffer(void* buffer, + size_t size, + int* num_dlls, + int* num_functions, + int* num_names) { + ASSERT_TRUE(buffer); + ASSERT_TRUE(num_functions); + ASSERT_TRUE(num_names); + *num_dlls = *num_functions = *num_names = 0; + SharedMemory* memory = reinterpret_cast<SharedMemory*>(buffer); + + ASSERT_GT(size, sizeof(SharedMemory)); + DllPatchInfo* dll = &memory->dll_list[0]; + + for (int i = 0; i < memory->num_intercepted_dlls; i++) { + ASSERT_NE(0u, wcslen(dll->dll_name)); + ASSERT_EQ(0u, dll->record_bytes % sizeof(size_t)); + ASSERT_EQ(0u, dll->offset_to_functions % sizeof(size_t)); + ASSERT_NE(0, dll->num_functions); + + FunctionInfo* function = reinterpret_cast<FunctionInfo*>( + reinterpret_cast<char*>(dll) + dll->offset_to_functions); + + for (int j = 0; j < dll->num_functions; j++) { + ASSERT_EQ(0u, function->record_bytes % sizeof(size_t)); + + char* name = function->function; + size_t length = strlen(name); + ASSERT_NE(0u, length); + name += length + 1; + + // look for overflows + ASSERT_GT(reinterpret_cast<char*>(buffer) + size, name + strlen(name)); + + // look for a named interceptor + if (strlen(name)) { + (*num_names)++; + EXPECT_TRUE(!function->interceptor_address); + } else { + EXPECT_TRUE(function->interceptor_address); + } + + (*num_functions)++; + function = reinterpret_cast<FunctionInfo*>( + reinterpret_cast<char*>(function) + function->record_bytes); + } + + (*num_dlls)++; + dll = reinterpret_cast<DllPatchInfo*>(reinterpret_cast<char*>(dll) + + dll->record_bytes); + } +} + +TEST(InterceptionManagerTest, GetGranularAlignedRandomOffset) { + std::set<size_t> sizes; + + // 544 is current value of interceptions_.size() * sizeof(ThunkData) + + // sizeof(DllInterceptionData). + const size_t kThunkBytes = 544; + + // ciel(log2(544)) = 10. + // Alignment must be 2^10 = 1024. + const size_t kAlignmentBits = base::bits::Log2Ceiling(kThunkBytes); + const size_t kAlignment = static_cast<size_t>(1) << kAlignmentBits; + + const size_t kAllocGranularity = 65536; + + // Generate enough sample data to ensure there is at least one value in each + // potential bucket. + for (size_t i = 0; i < 1000000; i++) + sizes.insert(internal::GetGranularAlignedRandomOffset(kThunkBytes)); + + size_t prev_val = 0; + size_t min_val = kAllocGranularity; + size_t min_nonzero_val = kAllocGranularity; + size_t max_val = 0; + + for (size_t val : sizes) { + ASSERT_LT(val, kAllocGranularity); + if (prev_val) + ASSERT_EQ(val - prev_val, kAlignment); + if (val) + min_nonzero_val = std::min(val, min_nonzero_val); + min_val = std::min(val, min_val); + prev_val = val; + max_val = std::max(val, max_val); + } + ASSERT_EQ(max_val, kAllocGranularity - kAlignment); + ASSERT_EQ(0u, min_val); + ASSERT_EQ(min_nonzero_val, kAlignment); +} + +TEST(InterceptionManagerTest, BufferLayout1) { + wchar_t exe_name[MAX_PATH]; + ASSERT_NE(0u, GetModuleFileName(nullptr, exe_name, MAX_PATH - 1)); + + TargetProcess* target = + MakeTestTargetProcess(::GetCurrentProcess(), ::GetModuleHandle(exe_name)); + + InterceptionManager interceptions(target, true); + + // Any pointer will do for a function pointer. + void* function = &interceptions; + + // We don't care about the interceptor id. + interceptions.AddToPatchedFunctions(L"ntdll.dll", "NtCreateFile", + INTERCEPTION_SERVICE_CALL, function, + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"kernel32.dll", "CreateFileEx", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"kernel32.dll", "SomeFileEx", + INTERCEPTION_SMART_SIDESTEP, function, + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"user32.dll", "FindWindow", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"kernel32.dll", "CreateMutex", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"user32.dll", "PostMsg", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"user32.dll", "PostMsg", + INTERCEPTION_EAT, "replacement", + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"comctl.dll", "SaveAsDlg", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"ntdll.dll", "NtClose", + INTERCEPTION_SERVICE_CALL, function, + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"ntdll.dll", "NtOpenFile", + INTERCEPTION_SIDESTEP, function, + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"some.dll", "Superfn", INTERCEPTION_EAT, + function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"comctl.dll", "SaveAsDlg", + INTERCEPTION_EAT, "a", OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"comctl.dll", "SaveAsDlg", + INTERCEPTION_SIDESTEP, "ab", OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"comctl.dll", "SaveAsDlg", + INTERCEPTION_EAT, "abc", OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"a.dll", "p", INTERCEPTION_EAT, function, + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"b.dll", + "TheIncredibleCallToSaveTheWorld", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"a.dll", "BIsLame", INTERCEPTION_EAT, + function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"a.dll", "ARules", INTERCEPTION_EAT, + function, OPEN_KEY_ID); + + // Verify that all interceptions were added + ASSERT_EQ(18u, interceptions.interceptions_.size()); + + size_t buffer_size = interceptions.GetBufferSize(); + std::unique_ptr<BYTE[]> local_buffer(new BYTE[buffer_size]); + + ASSERT_TRUE(interceptions.SetupConfigBuffer(local_buffer.get(), buffer_size)); + + // At this point, the interceptions should have been separated into two + // groups: one group with the local ("cold") interceptions, consisting of + // everything from ntdll and stuff set as INTRECEPTION_SERVICE_CALL, and + // another group with the interceptions belonging to dlls that will be "hot" + // patched on the client. The second group lives on local_buffer, and the + // first group remains on the list of interceptions (inside the object + // "interceptions"). There are 3 local interceptions (of ntdll); the + // other 15 have to be sent to the child to be performed "hot". + EXPECT_EQ(3u, interceptions.interceptions_.size()); + + int num_dlls, num_functions, num_names; + WalkBuffer(local_buffer.get(), buffer_size, &num_dlls, &num_functions, + &num_names); + + // The 15 interceptions on the buffer (to the child) should be grouped on 6 + // dlls. Only four interceptions are using an explicit name for the + // interceptor function. + EXPECT_EQ(6, num_dlls); + EXPECT_EQ(15, num_functions); + EXPECT_EQ(4, num_names); +} + +TEST(InterceptionManagerTest, BufferLayout2) { + wchar_t exe_name[MAX_PATH]; + ASSERT_NE(0u, GetModuleFileName(nullptr, exe_name, MAX_PATH - 1)); + + TargetProcess* target = + MakeTestTargetProcess(::GetCurrentProcess(), ::GetModuleHandle(exe_name)); + + InterceptionManager interceptions(target, true); + + // Any pointer will do for a function pointer. + void* function = &interceptions; + interceptions.AddToUnloadModules(L"some01.dll"); + // We don't care about the interceptor id. + interceptions.AddToPatchedFunctions(L"ntdll.dll", "NtCreateFile", + INTERCEPTION_SERVICE_CALL, function, + OPEN_FILE_ID); + interceptions.AddToPatchedFunctions(L"kernel32.dll", "CreateFileEx", + INTERCEPTION_EAT, function, OPEN_FILE_ID); + interceptions.AddToUnloadModules(L"some02.dll"); + interceptions.AddToPatchedFunctions(L"kernel32.dll", "SomeFileEx", + INTERCEPTION_SMART_SIDESTEP, function, + OPEN_FILE_ID); + // Verify that all interceptions were added + ASSERT_EQ(5u, interceptions.interceptions_.size()); + + size_t buffer_size = interceptions.GetBufferSize(); + std::unique_ptr<BYTE[]> local_buffer(new BYTE[buffer_size]); + + ASSERT_TRUE(interceptions.SetupConfigBuffer(local_buffer.get(), buffer_size)); + + // At this point, the interceptions should have been separated into two + // groups: one group with the local ("cold") interceptions, and another + // group with the interceptions belonging to dlls that will be "hot" + // patched on the client. The second group lives on local_buffer, and the + // first group remains on the list of interceptions, in this case just one. + EXPECT_EQ(1u, interceptions.interceptions_.size()); + + int num_dlls, num_functions, num_names; + WalkBuffer(local_buffer.get(), buffer_size, &num_dlls, &num_functions, + &num_names); + + EXPECT_EQ(3, num_dlls); + EXPECT_EQ(4, num_functions); + EXPECT_EQ(0, num_names); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/interceptors.h b/security/sandbox/chromium/sandbox/win/src/interceptors.h new file mode 100644 index 0000000000..5788614707 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interceptors.h @@ -0,0 +1,73 @@ +// Copyright (c) 2011 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. + +#ifndef SANDBOX_SRC_INTERCEPTORS_H_ +#define SANDBOX_SRC_INTERCEPTORS_H_ + +#if defined(_WIN64) +#include "sandbox/win/src/interceptors_64.h" +#endif + +namespace sandbox { + +enum InterceptorId { + // Internal use: + MAP_VIEW_OF_SECTION_ID = 0, + UNMAP_VIEW_OF_SECTION_ID, + // Policy broker: + SET_INFORMATION_THREAD_ID, + OPEN_THREAD_TOKEN_ID, + OPEN_THREAD_TOKEN_EX_ID, + OPEN_THREAD_ID, + OPEN_PROCESS_ID, + OPEN_PROCESS_TOKEN_ID, + OPEN_PROCESS_TOKEN_EX_ID, + // Filesystem dispatcher: + CREATE_FILE_ID, + OPEN_FILE_ID, + QUERY_ATTRIB_FILE_ID, + QUERY_FULL_ATTRIB_FILE_ID, + SET_INFO_FILE_ID, + // Named pipe dispatcher: + CREATE_NAMED_PIPE_ID, + // Process-thread dispatcher: + CREATE_PROCESSW_ID, + CREATE_PROCESSA_ID, + CREATE_THREAD_ID, + // Registry dispatcher: + CREATE_KEY_ID, + OPEN_KEY_ID, + OPEN_KEY_EX_ID, + // Sync dispatcher: + CREATE_EVENT_ID, + OPEN_EVENT_ID, + // Process mitigations Win32k dispatcher: + GDIINITIALIZE_ID, + GETSTOCKOBJECT_ID, + REGISTERCLASSW_ID, + ENUMDISPLAYMONITORS_ID, + ENUMDISPLAYDEVICESA_ID, + GETMONITORINFOA_ID, + GETMONITORINFOW_ID, + CREATEOPMPROTECTEDOUTPUTS_ID, + GETCERTIFICATE_ID, + GETCERTIFICATESIZE_ID, + GETCERTIFICATEBYHANDLE_ID, + GETCERTIFICATESIZEBYHANDLE_ID, + DESTROYOPMPROTECTEDOUTPUT_ID, + CONFIGUREOPMPROTECTEDOUTPUT_ID, + GETOPMINFORMATION_ID, + GETOPMRANDOMNUMBER_ID, + GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE_ID, + SETOPMSIGNINGKEYANDSEQUENCENUMBERS_ID, + // Signed dispatcher: + CREATE_SECTION_ID, + INTERCEPTOR_MAX_ID +}; + +typedef void* OriginalFunctions[INTERCEPTOR_MAX_ID]; + +} // namespace sandbox + +#endif // SANDBOX_SRC_INTERCEPTORS_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/interceptors_64.cc b/security/sandbox/chromium/sandbox/win/src/interceptors_64.cc new file mode 100644 index 0000000000..0ac2671fcc --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interceptors_64.cc @@ -0,0 +1,531 @@ +// Copyright (c) 2011 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/win/src/interceptors_64.h" + +#include "sandbox/win/src/filesystem_interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/named_pipe_interception.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/process_mitigations_win32k_interception.h" +#include "sandbox/win/src/process_thread_interception.h" +#include "sandbox/win/src/registry_interception.h" +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/signed_interception.h" +#include "sandbox/win/src/sync_interception.h" +#include "sandbox/win/src/target_interceptions.h" + +namespace sandbox { + +SANDBOX_INTERCEPT NtExports g_nt; +SANDBOX_INTERCEPT OriginalFunctions g_originals; + +NTSTATUS WINAPI TargetNtMapViewOfSection64(HANDLE section, + HANDLE process, + PVOID* base, + ULONG_PTR zero_bits, + SIZE_T commit_size, + PLARGE_INTEGER offset, + PSIZE_T view_size, + SECTION_INHERIT inherit, + ULONG allocation_type, + ULONG protect) { + NtMapViewOfSectionFunction orig_fn = + reinterpret_cast<NtMapViewOfSectionFunction>( + g_originals[MAP_VIEW_OF_SECTION_ID]); + + return TargetNtMapViewOfSection(orig_fn, section, process, base, zero_bits, + commit_size, offset, view_size, inherit, + allocation_type, protect); +} + +NTSTATUS WINAPI TargetNtUnmapViewOfSection64(HANDLE process, PVOID base) { + NtUnmapViewOfSectionFunction orig_fn = + reinterpret_cast<NtUnmapViewOfSectionFunction>( + g_originals[UNMAP_VIEW_OF_SECTION_ID]); + return TargetNtUnmapViewOfSection(orig_fn, process, base); +} + +// ----------------------------------------------------------------------- + +NTSTATUS WINAPI +TargetNtSetInformationThread64(HANDLE thread, + NT_THREAD_INFORMATION_CLASS thread_info_class, + PVOID thread_information, + ULONG thread_information_bytes) { + NtSetInformationThreadFunction orig_fn = + reinterpret_cast<NtSetInformationThreadFunction>( + g_originals[SET_INFORMATION_THREAD_ID]); + return TargetNtSetInformationThread(orig_fn, thread, thread_info_class, + thread_information, + thread_information_bytes); +} + +NTSTATUS WINAPI TargetNtOpenThreadToken64(HANDLE thread, + ACCESS_MASK desired_access, + BOOLEAN open_as_self, + PHANDLE token) { + NtOpenThreadTokenFunction orig_fn = + reinterpret_cast<NtOpenThreadTokenFunction>( + g_originals[OPEN_THREAD_TOKEN_ID]); + return TargetNtOpenThreadToken(orig_fn, thread, desired_access, open_as_self, + token); +} + +NTSTATUS WINAPI TargetNtOpenThreadTokenEx64(HANDLE thread, + ACCESS_MASK desired_access, + BOOLEAN open_as_self, + ULONG handle_attributes, + PHANDLE token) { + NtOpenThreadTokenExFunction orig_fn = + reinterpret_cast<NtOpenThreadTokenExFunction>( + g_originals[OPEN_THREAD_TOKEN_EX_ID]); + return TargetNtOpenThreadTokenEx(orig_fn, thread, desired_access, + open_as_self, handle_attributes, token); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateFile64(PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + PLARGE_INTEGER allocation_size, + ULONG file_attributes, + ULONG sharing, + ULONG disposition, + ULONG options, + PVOID ea_buffer, + ULONG ea_length) { + NtCreateFileFunction orig_fn = + reinterpret_cast<NtCreateFileFunction>(g_originals[CREATE_FILE_ID]); + return TargetNtCreateFile(orig_fn, file, desired_access, object_attributes, + io_status, allocation_size, file_attributes, + sharing, disposition, options, ea_buffer, + ea_length); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenFile64(PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + ULONG sharing, + ULONG options) { + NtOpenFileFunction orig_fn = + reinterpret_cast<NtOpenFileFunction>(g_originals[OPEN_FILE_ID]); + return TargetNtOpenFile(orig_fn, file, desired_access, object_attributes, + io_status, sharing, options); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtQueryAttributesFile64(POBJECT_ATTRIBUTES object_attributes, + PFILE_BASIC_INFORMATION file_attributes) { + NtQueryAttributesFileFunction orig_fn = + reinterpret_cast<NtQueryAttributesFileFunction>( + g_originals[QUERY_ATTRIB_FILE_ID]); + return TargetNtQueryAttributesFile(orig_fn, object_attributes, + file_attributes); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtQueryFullAttributesFile64( + POBJECT_ATTRIBUTES object_attributes, + PFILE_NETWORK_OPEN_INFORMATION file_attributes) { + NtQueryFullAttributesFileFunction orig_fn = + reinterpret_cast<NtQueryFullAttributesFileFunction>( + g_originals[QUERY_FULL_ATTRIB_FILE_ID]); + return TargetNtQueryFullAttributesFile(orig_fn, object_attributes, + file_attributes); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtSetInformationFile64(HANDLE file, + PIO_STATUS_BLOCK io_status, + PVOID file_information, + ULONG length, + FILE_INFORMATION_CLASS file_information_class) { + NtSetInformationFileFunction orig_fn = + reinterpret_cast<NtSetInformationFileFunction>( + g_originals[SET_INFO_FILE_ID]); + return TargetNtSetInformationFile(orig_fn, file, io_status, file_information, + length, file_information_class); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT HANDLE WINAPI +TargetCreateNamedPipeW64(LPCWSTR pipe_name, + DWORD open_mode, + DWORD pipe_mode, + DWORD max_instance, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes) { + CreateNamedPipeWFunction orig_fn = reinterpret_cast<CreateNamedPipeWFunction>( + g_originals[CREATE_NAMED_PIPE_ID]); + return TargetCreateNamedPipeW(orig_fn, pipe_name, open_mode, pipe_mode, + max_instance, out_buffer_size, in_buffer_size, + default_timeout, security_attributes); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenThread64(PHANDLE thread, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id) { + NtOpenThreadFunction orig_fn = + reinterpret_cast<NtOpenThreadFunction>(g_originals[OPEN_THREAD_ID]); + return TargetNtOpenThread(orig_fn, thread, desired_access, object_attributes, + client_id); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcess64(PHANDLE process, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id) { + NtOpenProcessFunction orig_fn = + reinterpret_cast<NtOpenProcessFunction>(g_originals[OPEN_PROCESS_ID]); + return TargetNtOpenProcess(orig_fn, process, desired_access, + object_attributes, client_id); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcessToken64(HANDLE process, + ACCESS_MASK desired_access, + PHANDLE token) { + NtOpenProcessTokenFunction orig_fn = + reinterpret_cast<NtOpenProcessTokenFunction>( + g_originals[OPEN_PROCESS_TOKEN_ID]); + return TargetNtOpenProcessToken(orig_fn, process, desired_access, token); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcessTokenEx64(HANDLE process, + ACCESS_MASK desired_access, + ULONG handle_attributes, + PHANDLE token) { + NtOpenProcessTokenExFunction orig_fn = + reinterpret_cast<NtOpenProcessTokenExFunction>( + g_originals[OPEN_PROCESS_TOKEN_EX_ID]); + return TargetNtOpenProcessTokenEx(orig_fn, process, desired_access, + handle_attributes, token); +} + +SANDBOX_INTERCEPT BOOL WINAPI +TargetCreateProcessW64(LPCWSTR application_name, + LPWSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCWSTR current_directory, + LPSTARTUPINFOW startup_info, + LPPROCESS_INFORMATION process_information) { + CreateProcessWFunction orig_fn = + reinterpret_cast<CreateProcessWFunction>(g_originals[CREATE_PROCESSW_ID]); + return TargetCreateProcessW( + orig_fn, application_name, command_line, process_attributes, + thread_attributes, inherit_handles, flags, environment, current_directory, + startup_info, process_information); +} + +SANDBOX_INTERCEPT BOOL WINAPI +TargetCreateProcessA64(LPCSTR application_name, + LPSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCSTR current_directory, + LPSTARTUPINFOA startup_info, + LPPROCESS_INFORMATION process_information) { + CreateProcessAFunction orig_fn = + reinterpret_cast<CreateProcessAFunction>(g_originals[CREATE_PROCESSA_ID]); + return TargetCreateProcessA( + orig_fn, application_name, command_line, process_attributes, + thread_attributes, inherit_handles, flags, environment, current_directory, + startup_info, process_information); +} + +SANDBOX_INTERCEPT HANDLE WINAPI +TargetCreateThread64(LPSECURITY_ATTRIBUTES thread_attributes, + SIZE_T stack_size, + LPTHREAD_START_ROUTINE start_address, + PVOID parameter, + DWORD creation_flags, + LPDWORD thread_id) { + CreateThreadFunction orig_fn = + reinterpret_cast<CreateThreadFunction>(g_originals[CREATE_THREAD_ID]); + return TargetCreateThread(orig_fn, thread_attributes, stack_size, + start_address, parameter, creation_flags, + thread_id); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateKey64(PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + ULONG title_index, + PUNICODE_STRING class_name, + ULONG create_options, + PULONG disposition) { + NtCreateKeyFunction orig_fn = + reinterpret_cast<NtCreateKeyFunction>(g_originals[CREATE_KEY_ID]); + return TargetNtCreateKey(orig_fn, key, desired_access, object_attributes, + title_index, class_name, create_options, + disposition); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenKey64(PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes) { + NtOpenKeyFunction orig_fn = + reinterpret_cast<NtOpenKeyFunction>(g_originals[OPEN_KEY_ID]); + return TargetNtOpenKey(orig_fn, key, desired_access, object_attributes); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenKeyEx64(PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + ULONG open_options) { + NtOpenKeyExFunction orig_fn = + reinterpret_cast<NtOpenKeyExFunction>(g_originals[OPEN_KEY_EX_ID]); + return TargetNtOpenKeyEx(orig_fn, key, desired_access, object_attributes, + open_options); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateEvent64(PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + EVENT_TYPE event_type, + BOOLEAN initial_state) { + NtCreateEventFunction orig_fn = + reinterpret_cast<NtCreateEventFunction>(g_originals[CREATE_EVENT_ID]); + return TargetNtCreateEvent(orig_fn, event_handle, desired_access, + object_attributes, event_type, initial_state); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenEvent64(PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes) { + NtOpenEventFunction orig_fn = + reinterpret_cast<NtOpenEventFunction>(g_originals[OPEN_EVENT_ID]); + return TargetNtOpenEvent(orig_fn, event_handle, desired_access, + object_attributes); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT BOOL WINAPI TargetGdiDllInitialize64(HANDLE dll, + DWORD reason) { + GdiDllInitializeFunction orig_fn = + reinterpret_cast<GdiDllInitializeFunction>(g_originals[GDIINITIALIZE_ID]); + return TargetGdiDllInitialize(orig_fn, dll, reason); +} + +SANDBOX_INTERCEPT HGDIOBJ WINAPI TargetGetStockObject64(int object) { + GetStockObjectFunction orig_fn = + reinterpret_cast<GetStockObjectFunction>(g_originals[GETSTOCKOBJECT_ID]); + return TargetGetStockObject(orig_fn, object); +} + +SANDBOX_INTERCEPT ATOM WINAPI +TargetRegisterClassW64(const WNDCLASS* wnd_class) { + RegisterClassWFunction orig_fn = + reinterpret_cast<RegisterClassWFunction>(g_originals[REGISTERCLASSW_ID]); + return TargetRegisterClassW(orig_fn, wnd_class); +} + +SANDBOX_INTERCEPT BOOL WINAPI +TargetEnumDisplayMonitors64(HDC hdc, + LPCRECT clip_rect, + MONITORENUMPROC enum_function, + LPARAM data_pointer) { + EnumDisplayMonitorsFunction orig_fn = + reinterpret_cast<EnumDisplayMonitorsFunction>( + g_originals[ENUMDISPLAYMONITORS_ID]); + return TargetEnumDisplayMonitors(orig_fn, hdc, clip_rect, enum_function, + data_pointer); +} + +SANDBOX_INTERCEPT BOOL WINAPI +TargetEnumDisplayDevicesA64(LPCSTR device, + DWORD device_num, + PDISPLAY_DEVICEA display_device, + DWORD flags) { + EnumDisplayDevicesAFunction orig_fn = + reinterpret_cast<EnumDisplayDevicesAFunction>( + g_originals[ENUMDISPLAYDEVICESA_ID]); + return TargetEnumDisplayDevicesA(orig_fn, device, device_num, display_device, + flags); +} + +SANDBOX_INTERCEPT BOOL WINAPI +TargetGetMonitorInfoA64(HMONITOR monitor, LPMONITORINFO monitor_info) { + GetMonitorInfoAFunction orig_fn = reinterpret_cast<GetMonitorInfoAFunction>( + g_originals[GETMONITORINFOA_ID]); + return TargetGetMonitorInfoA(orig_fn, monitor, monitor_info); +} + +SANDBOX_INTERCEPT BOOL WINAPI +TargetGetMonitorInfoW64(HMONITOR monitor, LPMONITORINFO monitor_info) { + GetMonitorInfoWFunction orig_fn = reinterpret_cast<GetMonitorInfoWFunction>( + g_originals[GETMONITORINFOW_ID]); + return TargetGetMonitorInfoW(orig_fn, monitor, monitor_info); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetSuggestedOPMProtectedOutputArraySize64( + PUNICODE_STRING device_name, + DWORD* suggested_output_array_size) { + GetSuggestedOPMProtectedOutputArraySizeFunction orig_fn = + reinterpret_cast<GetSuggestedOPMProtectedOutputArraySizeFunction>( + g_originals[GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE_ID]); + return TargetGetSuggestedOPMProtectedOutputArraySize( + orig_fn, device_name, suggested_output_array_size); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetCreateOPMProtectedOutputs64( + PUNICODE_STRING device_name, + DXGKMDT_OPM_VIDEO_OUTPUT_SEMANTICS vos, + DWORD protected_output_array_size, + DWORD* num_output_handles, + OPM_PROTECTED_OUTPUT_HANDLE* protected_output_array) { + CreateOPMProtectedOutputsFunction orig_fn = + reinterpret_cast<CreateOPMProtectedOutputsFunction>( + g_originals[CREATEOPMPROTECTEDOUTPUTS_ID]); + return TargetCreateOPMProtectedOutputs( + orig_fn, device_name, vos, protected_output_array_size, + num_output_handles, protected_output_array); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificate64(PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length) { + GetCertificateFunction orig_fn = + reinterpret_cast<GetCertificateFunction>(g_originals[GETCERTIFICATE_ID]); + return TargetGetCertificate(orig_fn, device_name, certificate_type, + certificate, certificate_length); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificateSize64(PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length) { + GetCertificateSizeFunction orig_fn = + reinterpret_cast<GetCertificateSizeFunction>( + g_originals[GETCERTIFICATESIZE_ID]); + return TargetGetCertificateSize(orig_fn, device_name, certificate_type, + certificate_length); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificateByHandle64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length) { + GetCertificateByHandleFunction orig_fn = + reinterpret_cast<GetCertificateByHandleFunction>( + g_originals[GETCERTIFICATE_ID]); + return TargetGetCertificateByHandle(orig_fn, protected_output, + certificate_type, certificate, + certificate_length); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificateSizeByHandle64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length) { + GetCertificateSizeByHandleFunction orig_fn = + reinterpret_cast<GetCertificateSizeByHandleFunction>( + g_originals[GETCERTIFICATESIZE_ID]); + return TargetGetCertificateSizeByHandle(orig_fn, protected_output, + certificate_type, certificate_length); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetDestroyOPMProtectedOutput64( + OPM_PROTECTED_OUTPUT_HANDLE protected_output) { + DestroyOPMProtectedOutputFunction orig_fn = + reinterpret_cast<DestroyOPMProtectedOutputFunction>( + g_originals[DESTROYOPMPROTECTEDOUTPUT_ID]); + return TargetDestroyOPMProtectedOutput(orig_fn, protected_output); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetOPMInformation64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_GET_INFO_PARAMETERS* parameters, + DXGKMDT_OPM_REQUESTED_INFORMATION* requested_info) { + GetOPMInformationFunction orig_fn = + reinterpret_cast<GetOPMInformationFunction>( + g_originals[GETOPMINFORMATION_ID]); + return TargetGetOPMInformation(orig_fn, protected_output, parameters, + requested_info); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetOPMRandomNumber64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_OPM_RANDOM_NUMBER* random_number) { + GetOPMRandomNumberFunction orig_fn = + reinterpret_cast<GetOPMRandomNumberFunction>( + g_originals[GETOPMRANDOMNUMBER_ID]); + return TargetGetOPMRandomNumber(orig_fn, protected_output, random_number); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetSetOPMSigningKeyAndSequenceNumbers64( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_ENCRYPTED_PARAMETERS* parameters) { + SetOPMSigningKeyAndSequenceNumbersFunction orig_fn = + reinterpret_cast<SetOPMSigningKeyAndSequenceNumbersFunction>( + g_originals[SETOPMSIGNINGKEYANDSEQUENCENUMBERS_ID]); + return TargetSetOPMSigningKeyAndSequenceNumbers(orig_fn, protected_output, + parameters); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetConfigureOPMProtectedOutput64( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_CONFIGURE_PARAMETERS* parameters, + ULONG additional_parameters_size, + const BYTE* additional_parameters) { + ConfigureOPMProtectedOutputFunction orig_fn = + reinterpret_cast<ConfigureOPMProtectedOutputFunction>( + g_originals[CONFIGUREOPMPROTECTEDOUTPUT_ID]); + return TargetConfigureOPMProtectedOutput( + orig_fn, protected_output, parameters, additional_parameters_size, + additional_parameters); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateSection64(PHANDLE section_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PLARGE_INTEGER maximum_size, + ULONG section_page_protection, + ULONG allocation_attributes, + HANDLE file_handle) { + NtCreateSectionFunction orig_fn = + reinterpret_cast<NtCreateSectionFunction>(g_originals[CREATE_SECTION_ID]); + return TargetNtCreateSection( + orig_fn, section_handle, desired_access, object_attributes, maximum_size, + section_page_protection, allocation_attributes, file_handle); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/interceptors_64.h b/security/sandbox/chromium/sandbox/win/src/interceptors_64.h new file mode 100644 index 0000000000..f34627dcd2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interceptors_64.h @@ -0,0 +1,330 @@ +// Copyright (c) 2011 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. + +#ifndef SANDBOX_WIN_SRC_INTERCEPTORS_64_H_ +#define SANDBOX_WIN_SRC_INTERCEPTORS_64_H_ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +// Interception of NtMapViewOfSection on the child process. +// It should never be called directly. This function provides the means to +// detect dlls being loaded, so we can patch them if needed. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtMapViewOfSection64(HANDLE section, + HANDLE process, + PVOID* base, + ULONG_PTR zero_bits, + SIZE_T commit_size, + PLARGE_INTEGER offset, + PSIZE_T view_size, + SECTION_INHERIT inherit, + ULONG allocation_type, + ULONG protect); + +// Interception of NtUnmapViewOfSection on the child process. +// It should never be called directly. This function provides the means to +// detect dlls being unloaded, so we can clean up our interceptions. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtUnmapViewOfSection64(HANDLE process, + PVOID base); + +// ----------------------------------------------------------------------- +// Interceptors without IPC. + +// Interception of NtSetInformationThread on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtSetInformationThread64(HANDLE thread, + NT_THREAD_INFORMATION_CLASS thread_info_class, + PVOID thread_information, + ULONG thread_information_bytes); + +// Interception of NtOpenThreadToken on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenThreadToken64(HANDLE thread, + ACCESS_MASK desired_access, + BOOLEAN open_as_self, + PHANDLE token); + +// Interception of NtOpenThreadTokenEx on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenThreadTokenEx64(HANDLE thread, + ACCESS_MASK desired_access, + BOOLEAN open_as_self, + ULONG handle_attributes, + PHANDLE token); + +// ----------------------------------------------------------------------- +// Interceptors handled by the file system dispatcher. + +// Interception of NtCreateFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateFile64(PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + PLARGE_INTEGER allocation_size, + ULONG file_attributes, + ULONG sharing, + ULONG disposition, + ULONG options, + PVOID ea_buffer, + ULONG ea_length); + +// Interception of NtOpenFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenFile64(PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + ULONG sharing, + ULONG options); + +// Interception of NtQueryAtttributesFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtQueryAttributesFile64(POBJECT_ATTRIBUTES object_attributes, + PFILE_BASIC_INFORMATION file_attributes); + +// Interception of NtQueryFullAtttributesFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtQueryFullAttributesFile64( + POBJECT_ATTRIBUTES object_attributes, + PFILE_NETWORK_OPEN_INFORMATION file_attributes); + +// Interception of NtSetInformationFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtSetInformationFile64(HANDLE file, + PIO_STATUS_BLOCK io_status, + PVOID file_information, + ULONG length, + FILE_INFORMATION_CLASS file_information_class); + +// ----------------------------------------------------------------------- +// Interceptors handled by the named pipe dispatcher. + +// Interception of CreateNamedPipeW in kernel32.dll +SANDBOX_INTERCEPT HANDLE WINAPI +TargetCreateNamedPipeW64(LPCWSTR pipe_name, + DWORD open_mode, + DWORD pipe_mode, + DWORD max_instance, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes); + +// ----------------------------------------------------------------------- +// Interceptors handled by the process-thread dispatcher. + +// Interception of NtOpenThread on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenThread64(PHANDLE thread, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id); + +// Interception of NtOpenProcess on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcess64(PHANDLE process, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id); + +// Interception of NtOpenProcessToken on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcessToken64(HANDLE process, + ACCESS_MASK desired_access, + PHANDLE token); + +// Interception of NtOpenProcessTokenEx on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcessTokenEx64(HANDLE process, + ACCESS_MASK desired_access, + ULONG handle_attributes, + PHANDLE token); + +// Interception of CreateProcessW in kernel32.dll. +SANDBOX_INTERCEPT BOOL WINAPI +TargetCreateProcessW64(LPCWSTR application_name, + LPWSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCWSTR current_directory, + LPSTARTUPINFOW startup_info, + LPPROCESS_INFORMATION process_information); + +// Interception of CreateProcessA in kernel32.dll. +SANDBOX_INTERCEPT BOOL WINAPI +TargetCreateProcessA64(LPCSTR application_name, + LPSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCSTR current_directory, + LPSTARTUPINFOA startup_info, + LPPROCESS_INFORMATION process_information); + +// Interception of CreateThread in kernel32.dll. +SANDBOX_INTERCEPT HANDLE WINAPI +TargetCreateThread64(LPSECURITY_ATTRIBUTES thread_attributes, + SIZE_T stack_size, + LPTHREAD_START_ROUTINE start_address, + PVOID parameter, + DWORD creation_flags, + LPDWORD thread_id); + +// ----------------------------------------------------------------------- +// Interceptors handled by the registry dispatcher. + +// Interception of NtCreateKey on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateKey64(PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + ULONG title_index, + PUNICODE_STRING class_name, + ULONG create_options, + PULONG disposition); + +// Interception of NtOpenKey on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenKey64(PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes); + +// Interception of NtOpenKeyEx on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenKeyEx64(PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + ULONG open_options); + +// ----------------------------------------------------------------------- +// Interceptors handled by the sync dispatcher. + +// Interception of NtCreateEvent/NtOpenEvent on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateEvent64(PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + EVENT_TYPE event_type, + BOOLEAN initial_state); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenEvent64(PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes); + +// ----------------------------------------------------------------------- +// Interceptors handled by the process mitigations win32k lockdown code. + +// Interceptor for the GdiDllInitialize function. +SANDBOX_INTERCEPT BOOL WINAPI TargetGdiDllInitialize64(HANDLE dll, + DWORD reason); + +// Interceptor for the GetStockObject function. +SANDBOX_INTERCEPT HGDIOBJ WINAPI TargetGetStockObject64(int object); + +// Interceptor for the RegisterClassW function. +SANDBOX_INTERCEPT ATOM WINAPI TargetRegisterClassW64(const WNDCLASS* wnd_class); + +SANDBOX_INTERCEPT BOOL WINAPI +TargetEnumDisplayMonitors64(HDC hdc, + LPCRECT lprcClip, + MONITORENUMPROC lpfnEnum, + LPARAM dwData); + +SANDBOX_INTERCEPT BOOL WINAPI +TargetEnumDisplayDevicesA64(LPCSTR lpDevice, + DWORD iDevNum, + PDISPLAY_DEVICEA lpDisplayDevice, + DWORD dwFlags); + +SANDBOX_INTERCEPT BOOL WINAPI TargetGetMonitorInfoA64(HMONITOR hMonitor, + LPMONITORINFO lpmi); + +SANDBOX_INTERCEPT BOOL WINAPI TargetGetMonitorInfoW64(HMONITOR hMonitor, + LPMONITORINFO lpmi); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetSuggestedOPMProtectedOutputArraySize64( + PUNICODE_STRING device_name, + DWORD* suggested_output_array_size); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetCreateOPMProtectedOutputs64( + PUNICODE_STRING device_name, + DXGKMDT_OPM_VIDEO_OUTPUT_SEMANTICS vos, + DWORD protected_output_array_size, + DWORD* num_output_handles, + OPM_PROTECTED_OUTPUT_HANDLE* protected_output_array); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificate64(PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificateSize64(PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificateByHandle64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificateSizeByHandle64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetDestroyOPMProtectedOutput64(OPM_PROTECTED_OUTPUT_HANDLE protected_output); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetOPMInformation64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_GET_INFO_PARAMETERS* parameters, + DXGKMDT_OPM_REQUESTED_INFORMATION* requested_info); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetOPMRandomNumber64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_OPM_RANDOM_NUMBER* random_number); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetSetOPMSigningKeyAndSequenceNumbers64( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_ENCRYPTED_PARAMETERS* parameters); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetConfigureOPMProtectedOutput64( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_CONFIGURE_PARAMETERS* parameters, + ULONG additional_parameters_size, + const BYTE* additional_parameters); + +// ----------------------------------------------------------------------- +// Interceptors handled by the signed process code. + +// Interception of NtCreateSection on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateSection64(PHANDLE section_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PLARGE_INTEGER maximum_size, + ULONG section_page_protection, + ULONG allocation_attributes, + HANDLE file_handle); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_INTERCEPTORS_64_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/internal_types.h b/security/sandbox/chromium/sandbox/win/src/internal_types.h new file mode 100644 index 0000000000..9971da81d0 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/internal_types.h @@ -0,0 +1,68 @@ +// 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. + +#ifndef SANDBOX_WIN_SRC_INTERNAL_TYPES_H_ +#define SANDBOX_WIN_SRC_INTERNAL_TYPES_H_ + +#include <stdint.h> + +namespace sandbox { + +const wchar_t kNtdllName[] = L"ntdll.dll"; +const wchar_t kKerneldllName[] = L"kernel32.dll"; +const wchar_t kKernelBasedllName[] = L"kernelbase.dll"; + +// Defines the supported C++ types encoding to numeric id. Like a simplified +// RTTI. Note that true C++ RTTI will not work because the types are not +// polymorphic anyway. +enum ArgType { + INVALID_TYPE = 0, + WCHAR_TYPE, + UINT32_TYPE, + UNISTR_TYPE, + VOIDPTR_TYPE, + INPTR_TYPE, + INOUTPTR_TYPE, + LAST_TYPE +}; + +// Encapsulates a pointer to a buffer and the size of the buffer. +class CountedBuffer { + public: + CountedBuffer(void* buffer, uint32_t size) : size_(size), buffer_(buffer) {} + + uint32_t Size() const { return size_; } + + void* Buffer() const { return buffer_; } + + private: + uint32_t size_; + void* buffer_; +}; + +// Helper class to convert void-pointer packed ints for both +// 32 and 64 bit builds. This construct is non-portable. +class IPCInt { + public: + explicit IPCInt(void* buffer) { buffer_.vp = buffer; } + + explicit IPCInt(uint32_t i32) { + buffer_.vp = nullptr; + buffer_.i32 = i32; + } + + uint32_t As32Bit() const { return buffer_.i32; } + + void* AsVoidPtr() const { return buffer_.vp; } + + private: + union U { + void* vp; + uint32_t i32; + } buffer_; +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_INTERNAL_TYPES_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/ipc_args.cc b/security/sandbox/chromium/sandbox/win/src/ipc_args.cc new file mode 100644 index 0000000000..c0c64f1864 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/ipc_args.cc @@ -0,0 +1,96 @@ +// Copyright 2017 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/win/src/ipc_args.h" + +#include <stddef.h> + +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/crosscall_server.h" + +namespace sandbox { + +// Releases memory allocated for IPC arguments, if needed. +void ReleaseArgs(const IPCParams* ipc_params, void* args[kMaxIpcParams]) { + for (size_t i = 0; i < kMaxIpcParams; i++) { + switch (ipc_params->args[i]) { + case WCHAR_TYPE: { + delete reinterpret_cast<std::wstring*>(args[i]); + args[i] = nullptr; + break; + } + case INPTR_TYPE: + case INOUTPTR_TYPE: { + delete reinterpret_cast<CountedBuffer*>(args[i]); + args[i] = nullptr; + break; + } + default: + break; + } + } +} + +// Fills up the list of arguments (args and ipc_params) for an IPC call. +bool GetArgs(CrossCallParamsEx* params, + IPCParams* ipc_params, + void* args[kMaxIpcParams]) { + if (kMaxIpcParams < params->GetParamsCount()) + return false; + + for (uint32_t i = 0; i < params->GetParamsCount(); i++) { + uint32_t size; + ArgType type; + args[i] = params->GetRawParameter(i, &size, &type); + if (args[i]) { + ipc_params->args[i] = type; + switch (type) { + case WCHAR_TYPE: { + std::unique_ptr<std::wstring> data(new std::wstring); + if (!params->GetParameterStr(i, data.get())) { + args[i] = 0; + ReleaseArgs(ipc_params, args); + return false; + } + args[i] = data.release(); + break; + } + case UINT32_TYPE: { + uint32_t data; + if (!params->GetParameter32(i, &data)) { + ReleaseArgs(ipc_params, args); + return false; + } + IPCInt ipc_int(data); + args[i] = ipc_int.AsVoidPtr(); + break; + } + case VOIDPTR_TYPE: { + void* data; + if (!params->GetParameterVoidPtr(i, &data)) { + ReleaseArgs(ipc_params, args); + return false; + } + args[i] = data; + break; + } + case INPTR_TYPE: + case INOUTPTR_TYPE: { + if (!args[i]) { + ReleaseArgs(ipc_params, args); + return false; + } + CountedBuffer* buffer = new CountedBuffer(args[i], size); + args[i] = buffer; + break; + } + default: + break; + } + } + } + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/ipc_args.h b/security/sandbox/chromium/sandbox/win/src/ipc_args.h new file mode 100644 index 0000000000..58b7507dee --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/ipc_args.h @@ -0,0 +1,24 @@ +// Copyright 2017 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. + +#ifndef SANDBOX_WIN_SRC_IPC_ARGS_H_ +#define SANDBOX_WIN_SRC_IPC_ARGS_H_ + +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/crosscall_server.h" + +namespace sandbox { + +// Releases memory allocated for IPC arguments. +void ReleaseArgs(const IPCParams* ipc_params, void* args[kMaxIpcParams]); + +// Fills up the list of arguments (args and ipc_params) for an IPC call. +// Call ReleaseArgs on |ipc_params| and |args| after calling this. +bool GetArgs(CrossCallParamsEx* params, + IPCParams* ipc_params, + void* args[kMaxIpcParams]); + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_IPC_ARGS_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/ipc_ping_test.cc b/security/sandbox/chromium/sandbox/win/src/ipc_ping_test.cc new file mode 100644 index 0000000000..44f6be433d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/ipc_ping_test.cc @@ -0,0 +1,58 @@ +// Copyright (c) 2006-2008 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/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" + +namespace sandbox { + +// Tests that the IPC is working by issuing a special IPC that is not exposed +// in the public API. +SBOX_TESTS_COMMAND int IPC_Ping(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED; + + TargetServices* ts = SandboxFactory::GetTargetServices(); + if (!ts) + return SBOX_TEST_FAILED; + + // Downcast because we have internal knowledge of the object returned. + TargetServicesBase* ts_base = reinterpret_cast<TargetServicesBase*>(ts); + + int version = 0; + if (L'1' == argv[0][0]) + version = 1; + else + version = 2; + + if (!ts_base->TestIPCPing(version)) + return SBOX_TEST_FAILED; + + ::Sleep(1); + if (!ts_base->TestIPCPing(version)) + return SBOX_TEST_FAILED; + + return SBOX_TEST_SUCCEEDED; +} + +// The IPC ping test should work before and after the token drop. +TEST(IPCTest, IPCPingTestSimple) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"IPC_Ping 1")); +} + +TEST(IPCTest, IPCPingTestWithOutput) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"IPC_Ping 2")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"IPC_Ping 2")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/ipc_tags.h b/security/sandbox/chromium/sandbox/win/src/ipc_tags.h new file mode 100644 index 0000000000..ec6de4a66a --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/ipc_tags.h @@ -0,0 +1,59 @@ +// 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. + +#ifndef SANDBOX_SRC_IPC_TAGS_H__ +#define SANDBOX_SRC_IPC_TAGS_H__ + +namespace sandbox { + +enum class IpcTag { + UNUSED = 0, + PING1, // Takes a cookie in parameters and returns the cookie + // multiplied by 2 and the tick_count. Used for testing only. + PING2, // Takes an in/out cookie in parameters and modify the cookie + // to be multiplied by 3. Used for testing only. + NTCREATEFILE, + NTOPENFILE, + NTQUERYATTRIBUTESFILE, + NTQUERYFULLATTRIBUTESFILE, + NTSETINFO_RENAME, + CREATENAMEDPIPEW, + NTOPENTHREAD, + NTOPENPROCESS, + NTOPENPROCESSTOKEN, + NTOPENPROCESSTOKENEX, + CREATEPROCESSW, + CREATEEVENT, + OPENEVENT, + NTCREATEKEY, + NTOPENKEY, + DUPLICATEHANDLEPROXY, + GDI_GDIDLLINITIALIZE, + GDI_GETSTOCKOBJECT, + USER_REGISTERCLASSW, + CREATETHREAD, + USER_ENUMDISPLAYMONITORS, + USER_ENUMDISPLAYDEVICES, + USER_GETMONITORINFO, + GDI_CREATEOPMPROTECTEDOUTPUTS, + GDI_GETCERTIFICATE, + GDI_GETCERTIFICATESIZE, + GDI_DESTROYOPMPROTECTEDOUTPUT, + GDI_CONFIGUREOPMPROTECTEDOUTPUT, + GDI_GETOPMINFORMATION, + GDI_GETOPMRANDOMNUMBER, + GDI_GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE, + GDI_SETOPMSIGNINGKEYANDSEQUENCENUMBERS, + NTCREATESECTION, + GETCOMPLEXLINEBREAKS, + LAST +}; + +constexpr size_t kMaxServiceCount = 64; +constexpr size_t kMaxIpcTag = static_cast<size_t>(IpcTag::LAST); +static_assert(kMaxIpcTag <= kMaxServiceCount, "kMaxServiceCount is too low"); + +} // namespace sandbox + +#endif // SANDBOX_SRC_IPC_TAGS_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/ipc_unittest.cc b/security/sandbox/chromium/sandbox/win/src/ipc_unittest.cc new file mode 100644 index 0000000000..71f7aff4cc --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/ipc_unittest.cc @@ -0,0 +1,632 @@ +// 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 <stddef.h> +#include <stdint.h> + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/sharedmem_ipc_server.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Helper function to make the fake shared memory with some +// basic elements initialized. +IPCControl* MakeChannels(size_t channel_size, + size_t total_shared_size, + size_t* base_start) { + // Allocate memory + char* mem = new char[total_shared_size]; + memset(mem, 0, total_shared_size); + // Calculate how many channels we can fit in the shared memory. + total_shared_size -= offsetof(IPCControl, channels); + size_t channel_count = + total_shared_size / (sizeof(ChannelControl) + channel_size); + // Calculate the start of the first channel. + *base_start = + (sizeof(ChannelControl) * channel_count) + offsetof(IPCControl, channels); + // Setup client structure. + IPCControl* client_control = reinterpret_cast<IPCControl*>(mem); + client_control->channels_count = channel_count; + return client_control; +} + +enum TestFixMode { FIX_NO_EVENTS, FIX_PONG_READY, FIX_PONG_NOT_READY }; + +void FixChannels(IPCControl* client_control, + size_t base_start, + size_t channel_size, + TestFixMode mode) { + for (size_t ix = 0; ix != client_control->channels_count; ++ix) { + ChannelControl& channel = client_control->channels[ix]; + channel.channel_base = base_start; + channel.state = kFreeChannel; + if (mode != FIX_NO_EVENTS) { + bool signaled = (FIX_PONG_READY == mode) ? true : false; + channel.ping_event = ::CreateEventW(nullptr, false, false, nullptr); + channel.pong_event = ::CreateEventW(nullptr, false, signaled, nullptr); + } + base_start += channel_size; + } +} + +void CloseChannelEvents(IPCControl* client_control) { + for (size_t ix = 0; ix != client_control->channels_count; ++ix) { + ChannelControl& channel = client_control->channels[ix]; + ::CloseHandle(channel.ping_event); + ::CloseHandle(channel.pong_event); + } +} + +TEST(IPCTest, ChannelMaker) { + // Test that our testing rig is computing offsets properly. We should have + // 5 channnels and the offset to the first channel is 108 bytes in 32 bits + // and 216 in 64 bits. + size_t channel_start = 0; + IPCControl* client_control = MakeChannels(12 * 64, 4096, &channel_start); + ASSERT_TRUE(client_control); + EXPECT_EQ(5u, client_control->channels_count); +#if defined(_WIN64) + EXPECT_EQ(216u, channel_start); +#else + EXPECT_EQ(108u, channel_start); +#endif + delete[] reinterpret_cast<char*>(client_control); +} + +TEST(IPCTest, ClientLockUnlock) { + // Make 7 channels of kIPCChannelSize (1kb) each. Test that we lock and + // unlock channels properly. + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(kIPCChannelSize, 4096 * 2, &base_start); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_NO_EVENTS); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + // Test that we lock the first 3 channels in sequence. + void* buff0 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[0].channel_base == buff0); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + void* buff1 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[1].channel_base == buff1); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + void* buff2 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[2].channel_base == buff2); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + // Test that we unlock and re-lock the right channel. + client.FreeBuffer(buff1); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + void* buff2b = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[1].channel_base == buff2b); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + client.FreeBuffer(buff0); + EXPECT_EQ(kFreeChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + delete[] reinterpret_cast<char*>(client_control); +} + +TEST(IPCTest, CrossCallStrPacking) { + // This test tries the CrossCall object with null and non-null string + // combination of parameters, integer types and verifies that the unpacker + // can read them properly. + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(kIPCChannelSize, 4096 * 4, &base_start); + client_control->server_alive = HANDLE(1); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_READY); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + CrossCallReturn answer; + IpcTag tag1 = IpcTag::PING1; + const wchar_t* text = L"98765 - 43210"; + std::wstring copied_text; + CrossCallParamsEx* actual_params; + + CrossCall(client, tag1, text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(1u, actual_params->GetParamsCount()); + EXPECT_EQ(tag1, actual_params->GetTag()); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text)); + EXPECT_STREQ(text, copied_text.c_str()); + copied_text.clear(); + + // Check with an empty string. + IpcTag tag2 = IpcTag::PING2; + const wchar_t* null_text = nullptr; + CrossCall(client, tag2, null_text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(1u, actual_params->GetParamsCount()); + EXPECT_EQ(tag2, actual_params->GetTag()); + uint32_t param_size = 1; + ArgType type = INVALID_TYPE; + void* param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); + EXPECT_TRUE(param_addr); + EXPECT_EQ(0u, param_size); + EXPECT_EQ(WCHAR_TYPE, type); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text)); + EXPECT_TRUE(copied_text.empty()); + + IpcTag tag3 = IpcTag::PING1; + param_size = 1; + copied_text.clear(); + + // Check with an empty string and a non-empty string. + CrossCall(client, tag3, null_text, text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(2u, actual_params->GetParamsCount()); + EXPECT_EQ(tag3, actual_params->GetTag()); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); + EXPECT_TRUE(param_addr); + EXPECT_EQ(0u, param_size); + EXPECT_EQ(WCHAR_TYPE, type); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text)); + EXPECT_TRUE(copied_text.empty()); + EXPECT_TRUE(actual_params->GetParameterStr(1, &copied_text)); + EXPECT_STREQ(text, copied_text.c_str()); + + param_size = 1; + std::wstring copied_text_p0, copied_text_p2; + + const wchar_t* text2 = L"AeFG"; + CrossCall(client, tag1, text2, null_text, text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(3u, actual_params->GetParamsCount()); + EXPECT_EQ(tag1, actual_params->GetTag()); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text_p0)); + EXPECT_STREQ(text2, copied_text_p0.c_str()); + EXPECT_TRUE(actual_params->GetParameterStr(2, &copied_text_p2)); + EXPECT_STREQ(text, copied_text_p2.c_str()); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(1, ¶m_size, &type); + EXPECT_TRUE(param_addr); + EXPECT_EQ(0u, param_size); + EXPECT_EQ(WCHAR_TYPE, type); + + CloseChannelEvents(client_control); + delete[] reinterpret_cast<char*>(client_control); +} + +TEST(IPCTest, CrossCallIntPacking) { + // Check handling for regular 32 bit integers used in Windows. + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(kIPCChannelSize, 4096 * 4, &base_start); + client_control->server_alive = HANDLE(1); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_READY); + + IpcTag tag1 = IpcTag::PING1; + IpcTag tag2 = IpcTag::PING2; + const wchar_t* text = L"godzilla"; + CrossCallParamsEx* actual_params; + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + CrossCallReturn answer; + DWORD dw = 0xE6578; + CrossCall(client, tag2, dw, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(1u, actual_params->GetParamsCount()); + EXPECT_EQ(tag2, actual_params->GetTag()); + ArgType type = INVALID_TYPE; + uint32_t param_size = 1; + void* param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); + ASSERT_EQ(sizeof(dw), param_size); + EXPECT_EQ(UINT32_TYPE, type); + ASSERT_TRUE(param_addr); + EXPECT_EQ(0, memcmp(&dw, param_addr, param_size)); + + // Check handling for windows HANDLES. + HANDLE h = HANDLE(0x70000500); + CrossCall(client, tag1, text, h, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(2u, actual_params->GetParamsCount()); + EXPECT_EQ(tag1, actual_params->GetTag()); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(1, ¶m_size, &type); + ASSERT_EQ(sizeof(h), param_size); + EXPECT_EQ(VOIDPTR_TYPE, type); + ASSERT_TRUE(param_addr); + EXPECT_EQ(0, memcmp(&h, param_addr, param_size)); + + // Check combination of 32 and 64 bits. + CrossCall(client, tag2, h, dw, h, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(3u, actual_params->GetParamsCount()); + EXPECT_EQ(tag2, actual_params->GetTag()); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); + ASSERT_EQ(sizeof(h), param_size); + EXPECT_EQ(VOIDPTR_TYPE, type); + ASSERT_TRUE(param_addr); + EXPECT_EQ(0, memcmp(&h, param_addr, param_size)); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(1, ¶m_size, &type); + ASSERT_EQ(sizeof(dw), param_size); + EXPECT_EQ(UINT32_TYPE, type); + ASSERT_TRUE(param_addr); + EXPECT_EQ(0, memcmp(&dw, param_addr, param_size)); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(2, ¶m_size, &type); + ASSERT_EQ(sizeof(h), param_size); + EXPECT_EQ(VOIDPTR_TYPE, type); + ASSERT_TRUE(param_addr); + EXPECT_EQ(0, memcmp(&h, param_addr, param_size)); + + CloseChannelEvents(client_control); + delete[] reinterpret_cast<char*>(client_control); +} + +TEST(IPCTest, CrossCallValidation) { + // First a sanity test with a well formed parameter object. + unsigned long value = 124816; + IpcTag kTag = IpcTag::PING1; + const uint32_t kBufferSize = 256; + ActualCallParams<1, kBufferSize> params_1(kTag); + params_1.CopyParamIn(0, &value, sizeof(value), false, UINT32_TYPE); + void* buffer = const_cast<void*>(params_1.GetBuffer()); + + uint32_t out_size = 0; + CrossCallParamsEx* ccp = 0; + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, params_1.GetSize(), + &out_size); + ASSERT_TRUE(ccp); + EXPECT_TRUE(ccp->GetBuffer() != buffer); + EXPECT_EQ(kTag, ccp->GetTag()); + EXPECT_EQ(1u, ccp->GetParamsCount()); + delete[](reinterpret_cast<char*>(ccp)); + + // Test that we handle integer overflow on the number of params + // correctly. We use a test-only ctor for ActualCallParams that + // allows to create malformed cross-call buffers. + const int32_t kPtrDiffSz = sizeof(ptrdiff_t); + for (int32_t ix = -1; ix != 3; ++ix) { + uint32_t fake_num_params = (UINT32_MAX / kPtrDiffSz) + ix; + ActualCallParams<1, kBufferSize> params_2(kTag, fake_num_params); + params_2.CopyParamIn(0, &value, sizeof(value), false, UINT32_TYPE); + buffer = const_cast<void*>(params_2.GetBuffer()); + + EXPECT_TRUE(buffer); + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, params_1.GetSize(), + &out_size); + // If the buffer is malformed the return is nullptr. + EXPECT_TRUE(!ccp); + } + + ActualCallParams<1, kBufferSize> params_3(kTag, 1); + params_3.CopyParamIn(0, &value, sizeof(value), false, UINT32_TYPE); + buffer = const_cast<void*>(params_3.GetBuffer()); + EXPECT_TRUE(buffer); + + uint32_t correct_size = params_3.OverrideSize(1); + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); + EXPECT_TRUE(!ccp); + + // The correct_size is 8 bytes aligned. + params_3.OverrideSize(correct_size - 7); + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); + EXPECT_TRUE(!ccp); + + params_3.OverrideSize(correct_size); + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); + EXPECT_TRUE(ccp); + + // Make sure that two parameters work as expected. + ActualCallParams<2, kBufferSize> params_4(kTag, 2); + params_4.CopyParamIn(0, &value, sizeof(value), false, UINT32_TYPE); + params_4.CopyParamIn(1, buffer, sizeof(buffer), false, VOIDPTR_TYPE); + buffer = const_cast<void*>(params_4.GetBuffer()); + EXPECT_TRUE(buffer); + + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); + EXPECT_TRUE(ccp); + +#if defined(_WIN64) + correct_size = params_4.OverrideSize(1); + params_4.OverrideSize(correct_size - 1); + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); + EXPECT_TRUE(!ccp); +#endif +} + +// This structure is passed to the mock server threads to simulate +// the server side IPC so it has the required kernel objects. +struct ServerEvents { + HANDLE ping; + HANDLE pong; + volatile LONG* state; + HANDLE mutex; +}; + +// This is the server thread that quicky answers an IPC and exits. +DWORD WINAPI QuickResponseServer(PVOID param) { + ServerEvents* events = reinterpret_cast<ServerEvents*>(param); + DWORD wait_result = 0; + wait_result = ::WaitForSingleObject(events->ping, INFINITE); + ::InterlockedExchange(events->state, kAckChannel); + ::SetEvent(events->pong); + return wait_result; +} + +class CrossCallParamsMock : public CrossCallParams { + public: + CrossCallParamsMock(IpcTag tag, uint32_t params_count) + : CrossCallParams(tag, params_count) {} +}; + +void FakeOkAnswerInChannel(void* channel) { + CrossCallReturn* answer = reinterpret_cast<CrossCallReturn*>(channel); + answer->call_outcome = SBOX_ALL_OK; +} + +// Create two threads that will quickly answer IPCs; the first one +// using channel 1 (channel 0 is busy) and one using channel 0. No time-out +// should occur. +TEST(IPCTest, ClientFastServer) { + const size_t channel_size = kIPCChannelSize; + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(channel_size, 4096 * 2, &base_start); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_NOT_READY); + client_control->server_alive = ::CreateMutex(nullptr, false, nullptr); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + ServerEvents events = {0}; + events.ping = client_control->channels[1].ping_event; + events.pong = client_control->channels[1].pong_event; + events.state = &client_control->channels[1].state; + + HANDLE t1 = + ::CreateThread(nullptr, 0, QuickResponseServer, &events, 0, nullptr); + ASSERT_TRUE(t1); + ::CloseHandle(t1); + + void* buff0 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[0].channel_base == buff0); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + void* buff1 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[1].channel_base == buff1); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + EXPECT_EQ(IpcTag::UNUSED, client_control->channels[1].ipc_tag); + + IpcTag tag = IpcTag::PING1; + CrossCallReturn answer; + CrossCallParamsMock* params1 = new (buff1) CrossCallParamsMock(tag, 1); + FakeOkAnswerInChannel(buff1); + + ResultCode result = client.DoCall(params1, &answer); + if (SBOX_ERROR_CHANNEL_ERROR != result) + client.FreeBuffer(buff1); + + EXPECT_TRUE(SBOX_ALL_OK == result); + EXPECT_EQ(tag, client_control->channels[1].ipc_tag); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + HANDLE t2 = + ::CreateThread(nullptr, 0, QuickResponseServer, &events, 0, nullptr); + ASSERT_TRUE(t2); + ::CloseHandle(t2); + + client.FreeBuffer(buff0); + events.ping = client_control->channels[0].ping_event; + events.pong = client_control->channels[0].pong_event; + events.state = &client_control->channels[0].state; + + tag = IpcTag::PING2; + CrossCallParamsMock* params2 = new (buff0) CrossCallParamsMock(tag, 1); + FakeOkAnswerInChannel(buff0); + + result = client.DoCall(params2, &answer); + if (SBOX_ERROR_CHANNEL_ERROR != result) + client.FreeBuffer(buff0); + + EXPECT_TRUE(SBOX_ALL_OK == result); + EXPECT_EQ(tag, client_control->channels[0].ipc_tag); + EXPECT_EQ(kFreeChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + CloseChannelEvents(client_control); + ::CloseHandle(client_control->server_alive); + + delete[] reinterpret_cast<char*>(client_control); +} + +// This is the server thread that very slowly answers an IPC and exits. Note +// that the pong event needs to be signaled twice. +DWORD WINAPI SlowResponseServer(PVOID param) { + ServerEvents* events = reinterpret_cast<ServerEvents*>(param); + DWORD wait_result = 0; + wait_result = ::WaitForSingleObject(events->ping, INFINITE); + ::Sleep(kIPCWaitTimeOut1 + kIPCWaitTimeOut2 + 200); + ::InterlockedExchange(events->state, kAckChannel); + ::SetEvent(events->pong); + return wait_result; +} + +// This thread's job is to keep the mutex locked. +DWORD WINAPI MainServerThread(PVOID param) { + ServerEvents* events = reinterpret_cast<ServerEvents*>(param); + DWORD wait_result = 0; + wait_result = ::WaitForSingleObject(events->mutex, INFINITE); + Sleep(kIPCWaitTimeOut1 * 20); + return wait_result; +} + +// Creates a server thread that answers the IPC so slow that is guaranteed to +// trigger the time-out code path in the client. A second thread is created +// to hold locked the server_alive mutex: this signals the client that the +// server is not dead and it retries the wait. +TEST(IPCTest, ClientSlowServer) { + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(kIPCChannelSize, 4096 * 2, &base_start); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_NOT_READY); + client_control->server_alive = ::CreateMutex(nullptr, false, nullptr); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + ServerEvents events = {0}; + events.ping = client_control->channels[0].ping_event; + events.pong = client_control->channels[0].pong_event; + events.state = &client_control->channels[0].state; + + HANDLE t1 = + ::CreateThread(nullptr, 0, SlowResponseServer, &events, 0, nullptr); + ASSERT_TRUE(t1); + ::CloseHandle(t1); + + ServerEvents events2 = {0}; + events2.pong = events.pong; + events2.mutex = client_control->server_alive; + + HANDLE t2 = + ::CreateThread(nullptr, 0, MainServerThread, &events2, 0, nullptr); + ASSERT_TRUE(t2); + ::CloseHandle(t2); + + ::Sleep(1); + + void* buff0 = client.GetBuffer(); + IpcTag tag = IpcTag::PING1; + CrossCallReturn answer; + CrossCallParamsMock* params1 = new (buff0) CrossCallParamsMock(tag, 1); + FakeOkAnswerInChannel(buff0); + + ResultCode result = client.DoCall(params1, &answer); + if (SBOX_ERROR_CHANNEL_ERROR != result) + client.FreeBuffer(buff0); + + EXPECT_TRUE(SBOX_ALL_OK == result); + EXPECT_EQ(tag, client_control->channels[0].ipc_tag); + EXPECT_EQ(kFreeChannel, client_control->channels[0].state); + + CloseChannelEvents(client_control); + ::CloseHandle(client_control->server_alive); + delete[] reinterpret_cast<char*>(client_control); +} + +// This test-only IPC dispatcher has two handlers with the same signature +// but only CallOneHandler should be used. +class UnitTestIPCDispatcher : public Dispatcher { + public: + UnitTestIPCDispatcher(); + ~UnitTestIPCDispatcher() override {} + + bool SetupService(InterceptionManager* manager, IpcTag service) override { + return true; + } + + private: + bool CallOneHandler(IPCInfo* ipc, HANDLE p1, uint32_t p2) { + ipc->return_info.extended[0].handle = p1; + ipc->return_info.extended[1].unsigned_int = p2; + return true; + } + + bool CallTwoHandler(IPCInfo* ipc, HANDLE p1, uint32_t p2) { return true; } +}; + +UnitTestIPCDispatcher::UnitTestIPCDispatcher() { + static const IPCCall call_one = {{IpcTag::PING1, {VOIDPTR_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &UnitTestIPCDispatcher::CallOneHandler)}; + static const IPCCall call_two = {{IpcTag::PING2, {VOIDPTR_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &UnitTestIPCDispatcher::CallTwoHandler)}; + ipc_calls_.push_back(call_one); + ipc_calls_.push_back(call_two); +} + +// This test does most of the shared memory IPC client-server roundtrip +// and tests the packing, unpacking and call dispatching. +TEST(IPCTest, SharedMemServerTests) { + size_t base_start = 0; + IPCControl* client_control = MakeChannels(kIPCChannelSize, 4096, &base_start); + client_control->server_alive = HANDLE(1); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_READY); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + CrossCallReturn answer; + HANDLE bar = HANDLE(191919); + DWORD foo = 6767676; + CrossCall(client, IpcTag::PING1, bar, foo, &answer); + void* buff = client.GetBuffer(); + ASSERT_TRUE(buff); + + UnitTestIPCDispatcher dispatcher; + // Since we are directly calling InvokeCallback, most of this structure + // can be set to nullptr. + sandbox::SharedMemIPCServer::ServerControl srv_control = {}; + srv_control.channel_size = kIPCChannelSize; + srv_control.shared_base = reinterpret_cast<char*>(client_control); + srv_control.dispatcher = &dispatcher; + + sandbox::CrossCallReturn call_return = {0}; + EXPECT_TRUE( + SharedMemIPCServer::InvokeCallback(&srv_control, buff, &call_return)); + EXPECT_EQ(SBOX_ALL_OK, call_return.call_outcome); + EXPECT_TRUE(bar == call_return.extended[0].handle); + EXPECT_EQ(foo, call_return.extended[1].unsigned_int); + + CloseChannelEvents(client_control); + delete[] reinterpret_cast<char*>(client_control); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/job.cc b/security/sandbox/chromium/sandbox/win/src/job.cc new file mode 100644 index 0000000000..39baffb193 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/job.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2011 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/win/src/job.h" + +#include <stddef.h> +#include <utility> + +#include "base/win/windows_version.h" +#include "sandbox/win/src/restricted_token.h" + +namespace sandbox { + +Job::Job() : job_handle_(nullptr) {} + +Job::~Job() {} + +DWORD Job::Init(JobLevel security_level, + const wchar_t* job_name, + DWORD ui_exceptions, + size_t memory_limit) { + if (job_handle_.IsValid()) + return ERROR_ALREADY_INITIALIZED; + + job_handle_.Set(::CreateJobObject(nullptr, // No security attribute + job_name)); + if (!job_handle_.IsValid()) + return ::GetLastError(); + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {}; + JOBOBJECT_BASIC_UI_RESTRICTIONS jbur = {}; + + // Set the settings for the different security levels. Note: The higher levels + // inherit from the lower levels. + switch (security_level) { + case JOB_LOCKDOWN: { + jeli.BasicLimitInformation.LimitFlags |= + JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION; + FALLTHROUGH; + } + case JOB_RESTRICTED: { + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_WRITECLIPBOARD; + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_READCLIPBOARD; + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_HANDLES; + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_GLOBALATOMS; + FALLTHROUGH; + } + case JOB_LIMITED_USER: { + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_DISPLAYSETTINGS; + jeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_ACTIVE_PROCESS; + jeli.BasicLimitInformation.ActiveProcessLimit = 1; + FALLTHROUGH; + } + case JOB_INTERACTIVE: { + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS; + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_DESKTOP; + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_EXITWINDOWS; + FALLTHROUGH; + } + case JOB_UNPROTECTED: { + if (memory_limit) { + jeli.BasicLimitInformation.LimitFlags |= + JOB_OBJECT_LIMIT_PROCESS_MEMORY; + jeli.ProcessMemoryLimit = memory_limit; + } + + jeli.BasicLimitInformation.LimitFlags |= + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + break; + } + default: { return ERROR_BAD_ARGUMENTS; } + } + + if (!::SetInformationJobObject(job_handle_.Get(), + JobObjectExtendedLimitInformation, &jeli, + sizeof(jeli))) { + return ::GetLastError(); + } + + jbur.UIRestrictionsClass = jbur.UIRestrictionsClass & (~ui_exceptions); + if (!::SetInformationJobObject(job_handle_.Get(), + JobObjectBasicUIRestrictions, &jbur, + sizeof(jbur))) { + return ::GetLastError(); + } + + return ERROR_SUCCESS; +} + +DWORD Job::UserHandleGrantAccess(HANDLE handle) { + if (!job_handle_.IsValid()) + return ERROR_NO_DATA; + + if (!::UserHandleGrantAccess(handle, job_handle_.Get(), + true)) { // Access allowed. + return ::GetLastError(); + } + + return ERROR_SUCCESS; +} + +base::win::ScopedHandle Job::Take() { + return std::move(job_handle_); +} + +DWORD Job::AssignProcessToJob(HANDLE process_handle) { + if (!job_handle_.IsValid()) + return ERROR_NO_DATA; + + if (!::AssignProcessToJobObject(job_handle_.Get(), process_handle)) + return ::GetLastError(); + + return ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/job.h b/security/sandbox/chromium/sandbox/win/src/job.h new file mode 100644 index 0000000000..153080bf6b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/job.h @@ -0,0 +1,66 @@ +// Copyright (c) 2010 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. + +#ifndef SANDBOX_SRC_JOB_H_ +#define SANDBOX_SRC_JOB_H_ + +#include <stddef.h> + +#include "base/macros.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/restricted_token_utils.h" + +namespace sandbox { + +// Handles the creation of job objects based on a security profile. +// Sample usage: +// Job job; +// job.Init(JOB_LOCKDOWN, nullptr); //no job name +// job.AssignProcessToJob(process_handle); +class Job { + public: + Job(); + + ~Job(); + + // Initializes and creates the job object. The security of the job is based + // on the security_level parameter. + // job_name can be nullptr if the job is unnamed. + // If the chosen profile has too many ui restrictions, you can disable some + // by specifying them in the ui_exceptions parameters. + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD Init(JobLevel security_level, + const wchar_t* job_name, + DWORD ui_exceptions, + size_t memory_limit); + + // Assigns the process referenced by process_handle to the job. + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD AssignProcessToJob(HANDLE process_handle); + + // Grants access to "handle" to the job. All processes in the job can + // subsequently recognize and use the handle. + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD UserHandleGrantAccess(HANDLE handle); + + // Revokes ownership to the job handle and returns it. + // If the object is not yet initialized, it returns an invalid handle. + base::win::ScopedHandle Take(); + + private: + // Handle to the job referenced by the object. + base::win::ScopedHandle job_handle_; + + DISALLOW_COPY_AND_ASSIGN(Job); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_JOB_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/job_unittest.cc b/security/sandbox/chromium/sandbox/win/src/job_unittest.cc new file mode 100644 index 0000000000..536900dc40 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/job_unittest.cc @@ -0,0 +1,197 @@ +// 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. + +// This file contains unit tests for the job object. + +#include "sandbox/win/src/job.h" + +#include "base/win/scoped_process_information.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Tests the creation and destruction of the job. +TEST(JobTest, TestCreation) { + // Scope the creation of Job. + { + // Create the job. + Job job; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); + + // check if the job exists. + HANDLE job_handle = + ::OpenJobObjectW(GENERIC_ALL, false, L"my_test_job_name"); + ASSERT_TRUE(job_handle); + + if (job_handle) + CloseHandle(job_handle); + } + + // Check if the job is destroyed when the object goes out of scope. + HANDLE job_handle = ::OpenJobObjectW(GENERIC_ALL, false, L"my_test_job_name"); + ASSERT_TRUE(!job_handle); + ASSERT_EQ(static_cast<DWORD>(ERROR_FILE_NOT_FOUND), ::GetLastError()); +} + +// Tests the method "Take". +TEST(JobTest, Take) { + base::win::ScopedHandle job_handle; + // Scope the creation of Job. + { + // Create the job. + Job job; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); + + job_handle = job.Take(); + ASSERT_TRUE(job_handle.IsValid()); + } + + // Check to be sure that the job is still alive even after the object is gone + // out of scope. + HANDLE job_handle_dup = + ::OpenJobObjectW(GENERIC_ALL, false, L"my_test_job_name"); + ASSERT_TRUE(job_handle_dup); + + // Remove all references. + if (job_handle_dup) + ::CloseHandle(job_handle_dup); + + job_handle.Close(); + + // Check if the jbo is really dead. + job_handle_dup = ::OpenJobObjectW(GENERIC_ALL, false, L"my_test_job_name"); + ASSERT_TRUE(!job_handle_dup); + ASSERT_EQ(static_cast<DWORD>(ERROR_FILE_NOT_FOUND), ::GetLastError()); +} + +// Tests the ui exceptions +TEST(JobTest, TestExceptions) { + base::win::ScopedHandle job_handle; + // Scope the creation of Job. + { + // Create the job. + Job job; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job.Init(JOB_LOCKDOWN, L"my_test_job_name", + JOB_OBJECT_UILIMIT_READCLIPBOARD, 0)); + + job_handle = job.Take(); + ASSERT_TRUE(job_handle.IsValid()); + + JOBOBJECT_BASIC_UI_RESTRICTIONS jbur = {0}; + DWORD size = sizeof(jbur); + ASSERT_TRUE(::QueryInformationJobObject( + job_handle.Get(), JobObjectBasicUIRestrictions, &jbur, size, &size)); + + ASSERT_EQ(0u, jbur.UIRestrictionsClass & JOB_OBJECT_UILIMIT_READCLIPBOARD); + job_handle.Close(); + } + + // Scope the creation of Job. + { + // Create the job. + Job job; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); + + job_handle = job.Take(); + ASSERT_TRUE(job_handle.IsValid()); + + JOBOBJECT_BASIC_UI_RESTRICTIONS jbur = {0}; + DWORD size = sizeof(jbur); + ASSERT_TRUE(::QueryInformationJobObject( + job_handle.Get(), JobObjectBasicUIRestrictions, &jbur, size, &size)); + + ASSERT_EQ(static_cast<DWORD>(JOB_OBJECT_UILIMIT_READCLIPBOARD), + jbur.UIRestrictionsClass & JOB_OBJECT_UILIMIT_READCLIPBOARD); + } +} + +// Tests the error case when the job is initialized twice. +TEST(JobTest, DoubleInit) { + // Create the job. + Job job; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); + ASSERT_EQ(static_cast<DWORD>(ERROR_ALREADY_INITIALIZED), + job.Init(JOB_LOCKDOWN, L"test", 0, 0)); +} + +// Tests the error case when we use a method and the object is not yet +// initialized. +TEST(JobTest, NoInit) { + Job job; + ASSERT_EQ(static_cast<DWORD>(ERROR_NO_DATA), + job.UserHandleGrantAccess(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_NO_DATA), job.AssignProcessToJob(nullptr)); + ASSERT_FALSE(job.Take().IsValid()); +} + +// Tests the initialization of the job with different security level. +TEST(JobTest, SecurityLevel) { + Job job1; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job1.Init(JOB_LOCKDOWN, L"job1", 0, 0)); + + Job job2; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job2.Init(JOB_RESTRICTED, L"job2", 0, 0)); + + Job job3; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job3.Init(JOB_LIMITED_USER, L"job3", 0, 0)); + + Job job4; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job4.Init(JOB_INTERACTIVE, L"job4", 0, 0)); + + Job job5; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job5.Init(JOB_UNPROTECTED, L"job5", 0, 0)); + + // JOB_NONE means we run without a job object so Init should fail. + Job job6; + ASSERT_EQ(static_cast<DWORD>(ERROR_BAD_ARGUMENTS), + job6.Init(JOB_NONE, L"job6", 0, 0)); + + Job job7; + ASSERT_EQ(static_cast<DWORD>(ERROR_BAD_ARGUMENTS), + job7.Init(static_cast<JobLevel>(JOB_NONE + 1), L"job7", 0, 0)); +} + +// Tests the method "AssignProcessToJob". +TEST(JobTest, ProcessInJob) { + // Create the job. + Job job; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job.Init(JOB_UNPROTECTED, L"job_test_process", 0, 0)); + + wchar_t notepad[] = L"notepad"; + STARTUPINFO si = {sizeof(si)}; + PROCESS_INFORMATION temp_process_info = {}; + ASSERT_TRUE(::CreateProcess(nullptr, notepad, nullptr, nullptr, false, 0, + nullptr, nullptr, &si, &temp_process_info)); + base::win::ScopedProcessInformation pi(temp_process_info); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job.AssignProcessToJob(pi.process_handle())); + + // Get the job handle. + base::win::ScopedHandle job_handle = job.Take(); + + // Check if the process is in the job. + JOBOBJECT_BASIC_PROCESS_ID_LIST jbpidl = {0}; + DWORD size = sizeof(jbpidl); + EXPECT_TRUE(::QueryInformationJobObject( + job_handle.Get(), JobObjectBasicProcessIdList, &jbpidl, size, &size)); + + EXPECT_EQ(1u, jbpidl.NumberOfAssignedProcesses); + EXPECT_EQ(1u, jbpidl.NumberOfProcessIdsInList); + EXPECT_EQ(pi.process_id(), jbpidl.ProcessIdList[0]); + + EXPECT_TRUE(::TerminateProcess(pi.process_handle(), 0)); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/named_pipe_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/named_pipe_dispatcher.cc new file mode 100644 index 0000000000..85ffebe6e6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/named_pipe_dispatcher.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2013 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/win/src/named_pipe_dispatcher.h" + +#include <stdint.h> + +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/named_pipe_interception.h" +#include "sandbox/win/src/named_pipe_policy.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox.h" + +namespace sandbox { + +NamedPipeDispatcher::NamedPipeDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall create_params = { + {IpcTag::CREATENAMEDPIPEW, + {WCHAR_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE, + UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>(&NamedPipeDispatcher::CreateNamedPipe)}; + + ipc_calls_.push_back(create_params); +} + +bool NamedPipeDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + if (IpcTag::CREATENAMEDPIPEW == service) + return INTERCEPT_EAT(manager, kKerneldllName, CreateNamedPipeW, + CREATE_NAMED_PIPE_ID, 36); + + return false; +} + +bool NamedPipeDispatcher::CreateNamedPipe(IPCInfo* ipc, + std::wstring* name, + uint32_t open_mode, + uint32_t pipe_mode, + uint32_t max_instances, + uint32_t out_buffer_size, + uint32_t in_buffer_size, + uint32_t default_timeout) { + ipc->return_info.win32_result = ERROR_ACCESS_DENIED; + ipc->return_info.handle = INVALID_HANDLE_VALUE; + + base::StringPiece16 dotdot(STRING16_LITERAL("..")); + + for (const base::StringPiece16& path : base::SplitStringPiece( + base::AsStringPiece16(*name), STRING16_LITERAL("/"), + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { + for (const base::StringPiece16& inner : + base::SplitStringPiece(path, STRING16_LITERAL("\\"), + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { + if (inner == dotdot) + return true; + } + } + + const wchar_t* pipe_name = name->c_str(); + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(pipe_name); + + EvalResult eval = + policy_base_->EvalPolicy(IpcTag::CREATENAMEDPIPEW, params.GetBase()); + + // "For file I/O, the "\\?\" prefix to a path string tells the Windows APIs to + // disable all string parsing and to send the string that follows it straight + // to the file system." + // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx + // This ensures even if there is a path traversal in the pipe name, and it is + // able to get past the checks above, it will still not be allowed to escape + // our allowed namespace. + if (name->compare(0, 4, L"\\\\.\\") == 0) + name->replace(0, 4, L"\\\\\?\\"); + + HANDLE pipe; + DWORD ret = NamedPipePolicy::CreateNamedPipeAction( + eval, *ipc->client_info, *name, open_mode, pipe_mode, max_instances, + out_buffer_size, in_buffer_size, default_timeout, &pipe); + + ipc->return_info.win32_result = ret; + ipc->return_info.handle = pipe; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/named_pipe_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/named_pipe_dispatcher.h new file mode 100644 index 0000000000..a14f658506 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/named_pipe_dispatcher.h @@ -0,0 +1,46 @@ +// Copyright (c) 2010 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. + +#ifndef SANDBOX_SRC_NAMED_PIPE_DISPATCHER_H__ +#define SANDBOX_SRC_NAMED_PIPE_DISPATCHER_H__ + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles named pipe related IPC calls. +class NamedPipeDispatcher : public Dispatcher { + public: + explicit NamedPipeDispatcher(PolicyBase* policy_base); + ~NamedPipeDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Processes IPC requests coming from calls to CreateNamedPipeW() in the + // target. + bool CreateNamedPipe(IPCInfo* ipc, + std::wstring* name, + uint32_t open_mode, + uint32_t pipe_mode, + uint32_t max_instances, + uint32_t out_buffer_size, + uint32_t in_buffer_size, + uint32_t default_timeout); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(NamedPipeDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_NAMED_PIPE_DISPATCHER_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/named_pipe_interception.cc b/security/sandbox/chromium/sandbox/win/src/named_pipe_interception.cc new file mode 100644 index 0000000000..aa3d5dc358 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/named_pipe_interception.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2006-2008 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/win/src/named_pipe_interception.h" + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" +#include "mozilla/sandboxing/sandboxLogging.h" + +namespace sandbox { + +HANDLE WINAPI +TargetCreateNamedPipeW(CreateNamedPipeWFunction orig_CreateNamedPipeW, + LPCWSTR pipe_name, + DWORD open_mode, + DWORD pipe_mode, + DWORD max_instance, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes) { + HANDLE pipe = orig_CreateNamedPipeW( + pipe_name, open_mode, pipe_mode, max_instance, out_buffer_size, + in_buffer_size, default_timeout, security_attributes); + if (INVALID_HANDLE_VALUE != pipe) + return pipe; + + mozilla::sandboxing::LogBlocked("CreateNamedPipeW", pipe_name); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return INVALID_HANDLE_VALUE; + + DWORD original_error = ::GetLastError(); + + // We don't support specific Security Attributes. + if (security_attributes) + return INVALID_HANDLE_VALUE; + + do { + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(pipe_name); + + if (!QueryBroker(IpcTag::CREATENAMEDPIPEW, params.GetBase())) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = + CrossCall(ipc, IpcTag::CREATENAMEDPIPEW, pipe_name, open_mode, + pipe_mode, max_instance, out_buffer_size, in_buffer_size, + default_timeout, &answer); + if (SBOX_ALL_OK != code) + break; + + ::SetLastError(answer.win32_result); + + if (ERROR_SUCCESS != answer.win32_result) + return INVALID_HANDLE_VALUE; + + mozilla::sandboxing::LogAllowed("CreateNamedPipeW", pipe_name); + return answer.handle; + } while (false); + + ::SetLastError(original_error); + return INVALID_HANDLE_VALUE; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/named_pipe_interception.h b/security/sandbox/chromium/sandbox/win/src/named_pipe_interception.h new file mode 100644 index 0000000000..8eedd14b31 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/named_pipe_interception.h @@ -0,0 +1,41 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_WIN_SRC_NAMED_PIPE_INTERCEPTION_H_ +#define SANDBOX_WIN_SRC_NAMED_PIPE_INTERCEPTION_H_ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +typedef HANDLE(WINAPI* CreateNamedPipeWFunction)( + LPCWSTR lpName, + DWORD dwOpenMode, + DWORD dwPipeMode, + DWORD nMaxInstances, + DWORD nOutBufferSize, + DWORD nInBufferSize, + DWORD nDefaultTimeOut, + LPSECURITY_ATTRIBUTES lpSecurityAttributes); + +// Interception of CreateNamedPipeW in kernel32.dll +SANDBOX_INTERCEPT HANDLE WINAPI +TargetCreateNamedPipeW(CreateNamedPipeWFunction orig_CreateNamedPipeW, + LPCWSTR pipe_name, + DWORD open_mode, + DWORD pipe_mode, + DWORD max_instance, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_NAMED_PIPE_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/named_pipe_policy.cc b/security/sandbox/chromium/sandbox/win/src/named_pipe_policy.cc new file mode 100644 index 0000000000..bfad75852d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/named_pipe_policy.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2006-2008 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/win/src/named_pipe_policy.h" + +#include <string> + +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace { + +// Creates a named pipe and duplicates the handle to 'target_process'. The +// remaining parameters are the same as CreateNamedPipeW(). +HANDLE CreateNamedPipeHelper(HANDLE target_process, + LPCWSTR pipe_name, + DWORD open_mode, + DWORD pipe_mode, + DWORD max_instances, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes) { + HANDLE pipe = ::CreateNamedPipeW( + pipe_name, open_mode, pipe_mode, max_instances, out_buffer_size, + in_buffer_size, default_timeout, security_attributes); + if (INVALID_HANDLE_VALUE == pipe) + return pipe; + + HANDLE new_pipe; + if (!::DuplicateHandle(::GetCurrentProcess(), pipe, target_process, &new_pipe, + 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return INVALID_HANDLE_VALUE; + } + + return new_pipe; +} + +} // namespace + +namespace sandbox { + +bool NamedPipePolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + if (TargetPolicy::NAMEDPIPES_ALLOW_ANY != semantics) { + return false; + } + PolicyRule pipe(ASK_BROKER); + if (!pipe.AddStringMatch(IF, NameBased::NAME, name, CASE_INSENSITIVE)) { + return false; + } + if (!policy->AddRule(IpcTag::CREATENAMEDPIPEW, &pipe)) { + return false; + } + return true; +} + +DWORD NamedPipePolicy::CreateNamedPipeAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& name, + DWORD open_mode, + DWORD pipe_mode, + DWORD max_instances, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, + HANDLE* pipe) { + *pipe = INVALID_HANDLE_VALUE; + // The only action supported is ASK_BROKER which means create the pipe. + if (ASK_BROKER != eval_result) { + return ERROR_ACCESS_DENIED; + } + + *pipe = CreateNamedPipeHelper(client_info.process, name.c_str(), open_mode, + pipe_mode, max_instances, out_buffer_size, + in_buffer_size, default_timeout, nullptr); + + if (INVALID_HANDLE_VALUE == *pipe) + return ERROR_ACCESS_DENIED; + + return ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/named_pipe_policy.h b/security/sandbox/chromium/sandbox/win/src/named_pipe_policy.h new file mode 100644 index 0000000000..4ad272f16f --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/named_pipe_policy.h @@ -0,0 +1,43 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_SRC_NAMED_PIPE_POLICY_H__ +#define SANDBOX_SRC_NAMED_PIPE_POLICY_H__ + +#include <string> + +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +// This class centralizes most of the knowledge related to named pipe creation. +class NamedPipePolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level. + // policy rule for named pipe creation + // 'name' is the named pipe to be created + // 'semantics' is the desired semantics. + // 'policy' is the policy generator to which the rules are going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Processes a 'CreateNamedPipeW()' request from the target. + static DWORD CreateNamedPipeAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& name, + DWORD open_mode, + DWORD pipe_mode, + DWORD max_instances, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, + HANDLE* pipe); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_NAMED_PIPE_POLICY_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/named_pipe_policy_test.cc b/security/sandbox/chromium/sandbox/win/src/named_pipe_policy_test.cc new file mode 100644 index 0000000000..db532d618d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/named_pipe_policy_test.cc @@ -0,0 +1,121 @@ +// Copyright (c) 2014 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 "base/win/windows_version.h" +#include "sandbox/win/src/handle_closer.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +SBOX_TESTS_COMMAND int NamedPipe_Create(int argc, wchar_t** argv) { + if (argc < 1 || argc > 2 || !argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + HANDLE pipe = ::CreateNamedPipeW( + argv[0], PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, 4096, 4096, 2000, nullptr); + if (INVALID_HANDLE_VALUE == pipe) + return SBOX_TEST_DENIED; + + // The second parameter allows us to enforce an allowlist for where the + // pipe should be in the object namespace after creation. + if (argc == 2) { + std::wstring handle_name; + if (GetHandleName(pipe, &handle_name)) { + if (handle_name.compare(0, wcslen(argv[1]), argv[1]) != 0) + return SBOX_TEST_FAILED; + } else { + return SBOX_TEST_FAILED; + } + } + + OVERLAPPED overlapped = {0}; + overlapped.hEvent = ::CreateEvent(nullptr, true, true, nullptr); + bool result = ::ConnectNamedPipe(pipe, &overlapped); + + if (!result) { + DWORD error = ::GetLastError(); + if (ERROR_PIPE_CONNECTED != error && ERROR_IO_PENDING != error) { + return SBOX_TEST_FAILED; + } + } + + if (!::CloseHandle(pipe)) + return SBOX_TEST_FAILED; + + ::CloseHandle(overlapped.hEvent); + return SBOX_TEST_SUCCEEDED; +} + +// Tests if we can create a pipe in the sandbox. +TEST(NamedPipePolicyTest, CreatePipe) { + TestRunner runner; + // TODO(nsylvain): This policy is wrong because "*" is a valid char in a + // namedpipe name. Here we apply it like a wildcard. http://b/893603 + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_NAMED_PIPES, + TargetPolicy::NAMEDPIPES_ALLOW_ANY, + L"\\\\.\\pipe\\test*")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\testbleh")); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\bleh")); +} + +// Tests if we can create a pipe with a path traversal in the sandbox. +TEST(NamedPipePolicyTest, CreatePipeTraversal) { + TestRunner runner; + // TODO(nsylvain): This policy is wrong because "*" is a valid char in a + // namedpipe name. Here we apply it like a wildcard. http://b/893603 + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_NAMED_PIPES, + TargetPolicy::NAMEDPIPES_ALLOW_ANY, + L"\\\\.\\pipe\\test*")); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\test\\..\\bleh")); + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\test/../bleh")); + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\test\\../bleh")); + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\test/..\\bleh")); +} + +// This tests that path canonicalization is actually disabled if we use \\?\ +// syntax. +TEST(NamedPipePolicyTest, CreatePipeCanonicalization) { + // "For file I/O, the "\\?\" prefix to a path string tells the Windows APIs to + // disable all string parsing and to send the string that follows it straight + // to the file system." + // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx + const wchar_t* argv[2] = {L"\\\\?\\pipe\\test\\..\\bleh", + L"\\Device\\NamedPipe\\test"}; + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + NamedPipe_Create(2, const_cast<wchar_t**>(argv))); +} + +// The same test as CreatePipe but this time using strict interceptions. +TEST(NamedPipePolicyTest, CreatePipeStrictInterceptions) { + TestRunner runner; + runner.GetPolicy()->SetStrictInterceptions(); + + // TODO(nsylvain): This policy is wrong because "*" is a valid char in a + // namedpipe name. Here we apply it like a wildcard. http://b/893603 + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_NAMED_PIPES, + TargetPolicy::NAMEDPIPES_ALLOW_ANY, + L"\\\\.\\pipe\\test*")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\testbleh")); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\bleh")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/nt_internals.h b/security/sandbox/chromium/sandbox/win/src/nt_internals.h new file mode 100644 index 0000000000..6041566436 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/nt_internals.h @@ -0,0 +1,983 @@ +// Copyright 2013 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. + +// This file holds definitions related to the ntdll API. + +#ifndef SANDBOX_WIN_SRC_NT_INTERNALS_H__ +#define SANDBOX_WIN_SRC_NT_INTERNALS_H__ + +#include <windows.h> + +#include <stddef.h> + +typedef LONG NTSTATUS; +#define NT_SUCCESS(st) (st >= 0) +#define NT_ERROR(st) ((((ULONG)(st)) >> 30) == 3) + +// clang-format off +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L) +#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) +#define STATUS_NOT_IMPLEMENTED ((NTSTATUS)0xC0000002L) +#define STATUS_INVALID_INFO_CLASS ((NTSTATUS)0xC0000003L) +#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) +#ifndef STATUS_INVALID_PARAMETER +// It is now defined in Windows 2008 SDK. +#define STATUS_INVALID_PARAMETER ((NTSTATUS)0xC000000DL) +#endif +#define STATUS_CONFLICTING_ADDRESSES ((NTSTATUS)0xC0000018L) +#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L) +#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) +#define STATUS_OBJECT_NAME_NOT_FOUND ((NTSTATUS)0xC0000034L) +#define STATUS_OBJECT_NAME_COLLISION ((NTSTATUS)0xC0000035L) +#define STATUS_PROCEDURE_NOT_FOUND ((NTSTATUS)0xC000007AL) +#define STATUS_INVALID_IMAGE_FORMAT ((NTSTATUS)0xC000007BL) +#define STATUS_NO_TOKEN ((NTSTATUS)0xC000007CL) +#define STATUS_NOT_SUPPORTED ((NTSTATUS)0xC00000BBL) +#define STATUS_INVALID_IMAGE_HASH ((NTSTATUS)0xC0000428L) +// clang-format on + +#define CURRENT_PROCESS ((HANDLE)-1) +#define CURRENT_THREAD ((HANDLE)-2) +#define NtCurrentProcess CURRENT_PROCESS + +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING; +typedef UNICODE_STRING* PUNICODE_STRING; +typedef const UNICODE_STRING* PCUNICODE_STRING; + +typedef struct _STRING { + USHORT Length; + USHORT MaximumLength; + PCHAR Buffer; +} STRING; +typedef STRING* PSTRING; + +typedef STRING ANSI_STRING; +typedef PSTRING PANSI_STRING; +typedef CONST PSTRING PCANSI_STRING; + +typedef STRING OEM_STRING; +typedef PSTRING POEM_STRING; +typedef CONST STRING* PCOEM_STRING; + +#define OBJ_CASE_INSENSITIVE 0x00000040L +#define OBJ_OPENIF 0x00000080L + +typedef struct _OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} OBJECT_ATTRIBUTES; +typedef OBJECT_ATTRIBUTES* POBJECT_ATTRIBUTES; + +#define InitializeObjectAttributes(p, n, a, r, s) \ + { \ + (p)->Length = sizeof(OBJECT_ATTRIBUTES); \ + (p)->RootDirectory = r; \ + (p)->Attributes = a; \ + (p)->ObjectName = n; \ + (p)->SecurityDescriptor = s; \ + (p)->SecurityQualityOfService = nullptr; \ + } + +typedef struct _IO_STATUS_BLOCK { + union { + NTSTATUS Status; + PVOID Pointer; + }; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +// ----------------------------------------------------------------------- +// File IO + +// Create disposition values. + +#define FILE_SUPERSEDE 0x00000000 +#define FILE_OPEN 0x00000001 +#define FILE_CREATE 0x00000002 +#define FILE_OPEN_IF 0x00000003 +#define FILE_OVERWRITE 0x00000004 +#define FILE_OVERWRITE_IF 0x00000005 +#define FILE_MAXIMUM_DISPOSITION 0x00000005 + +// Create/open option flags. + +#define FILE_DIRECTORY_FILE 0x00000001 +#define FILE_WRITE_THROUGH 0x00000002 +#define FILE_SEQUENTIAL_ONLY 0x00000004 +#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 + +#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 +#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 +#define FILE_NON_DIRECTORY_FILE 0x00000040 +#define FILE_CREATE_TREE_CONNECTION 0x00000080 + +#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 +#define FILE_NO_EA_KNOWLEDGE 0x00000200 +#define FILE_OPEN_REMOTE_INSTANCE 0x00000400 +#define FILE_RANDOM_ACCESS 0x00000800 + +#define FILE_DELETE_ON_CLOSE 0x00001000 +#define FILE_OPEN_BY_FILE_ID 0x00002000 +#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 +#define FILE_NO_COMPRESSION 0x00008000 + +#define FILE_RESERVE_OPFILTER 0x00100000 +#define FILE_OPEN_REPARSE_POINT 0x00200000 +#define FILE_OPEN_NO_RECALL 0x00400000 +#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 + +// Create/open result values. These are the disposition values returned on the +// io status information. +#define FILE_SUPERSEDED 0x00000000 +#define FILE_OPENED 0x00000001 +#define FILE_CREATED 0x00000002 +#define FILE_OVERWRITTEN 0x00000003 +#define FILE_EXISTS 0x00000004 +#define FILE_DOES_NOT_EXIST 0x00000005 + +typedef NTSTATUS(WINAPI* NtCreateFileFunction)( + OUT PHANDLE FileHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN PLARGE_INTEGER AllocationSize OPTIONAL, + IN ULONG FileAttributes, + IN ULONG ShareAccess, + IN ULONG CreateDisposition, + IN ULONG CreateOptions, + IN PVOID EaBuffer OPTIONAL, + IN ULONG EaLength); + +typedef NTSTATUS(WINAPI* NtOpenFileFunction)(OUT PHANDLE FileHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES + ObjectAttributes, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG ShareAccess, + IN ULONG OpenOptions); + +typedef NTSTATUS(WINAPI* NtCloseFunction)(IN HANDLE Handle); + +typedef enum _FILE_INFORMATION_CLASS { + FileRenameInformation = 10 +} FILE_INFORMATION_CLASS, + *PFILE_INFORMATION_CLASS; + +typedef struct _FILE_RENAME_INFORMATION { + BOOLEAN ReplaceIfExists; + HANDLE RootDirectory; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION; + +typedef NTSTATUS(WINAPI* NtSetInformationFileFunction)( + IN HANDLE FileHandle, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN PVOID FileInformation, + IN ULONG Length, + IN FILE_INFORMATION_CLASS FileInformationClass); + +typedef struct FILE_BASIC_INFORMATION { + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + ULONG FileAttributes; +} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION; + +typedef NTSTATUS(WINAPI* NtQueryAttributesFileFunction)( + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PFILE_BASIC_INFORMATION FileAttributes); + +typedef struct _FILE_NETWORK_OPEN_INFORMATION { + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + ULONG FileAttributes; +} FILE_NETWORK_OPEN_INFORMATION, *PFILE_NETWORK_OPEN_INFORMATION; + +typedef NTSTATUS(WINAPI* NtQueryFullAttributesFileFunction)( + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PFILE_NETWORK_OPEN_INFORMATION FileAttributes); + +// ----------------------------------------------------------------------- +// Sections + +typedef NTSTATUS(WINAPI* NtCreateSectionFunction)( + OUT PHANDLE SectionHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, + IN PLARGE_INTEGER MaximumSize OPTIONAL, + IN ULONG SectionPageProtection, + IN ULONG AllocationAttributes, + IN HANDLE FileHandle OPTIONAL); + +typedef ULONG SECTION_INHERIT; +#define ViewShare 1 +#define ViewUnmap 2 + +typedef NTSTATUS(WINAPI* NtMapViewOfSectionFunction)( + IN HANDLE SectionHandle, + IN HANDLE ProcessHandle, + IN OUT PVOID* BaseAddress, + IN ULONG_PTR ZeroBits, + IN SIZE_T CommitSize, + IN OUT PLARGE_INTEGER SectionOffset OPTIONAL, + IN OUT PSIZE_T ViewSize, + IN SECTION_INHERIT InheritDisposition, + IN ULONG AllocationType, + IN ULONG Win32Protect); + +typedef NTSTATUS(WINAPI* NtUnmapViewOfSectionFunction)(IN HANDLE ProcessHandle, + IN PVOID BaseAddress); + +typedef enum _SECTION_INFORMATION_CLASS { + SectionBasicInformation = 0, + SectionImageInformation +} SECTION_INFORMATION_CLASS; + +typedef struct _SECTION_BASIC_INFORMATION { + PVOID BaseAddress; + ULONG Attributes; + LARGE_INTEGER Size; +} SECTION_BASIC_INFORMATION, *PSECTION_BASIC_INFORMATION; + +typedef NTSTATUS(WINAPI* NtQuerySectionFunction)( + IN HANDLE SectionHandle, + IN SECTION_INFORMATION_CLASS SectionInformationClass, + OUT PVOID SectionInformation, + IN SIZE_T SectionInformationLength, + OUT PSIZE_T ReturnLength OPTIONAL); + +// ----------------------------------------------------------------------- +// Process and Thread + +typedef struct _CLIENT_ID { + PVOID UniqueProcess; + PVOID UniqueThread; +} CLIENT_ID, *PCLIENT_ID; + +typedef NTSTATUS(WINAPI* NtOpenThreadFunction)(OUT PHANDLE ThreadHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES + ObjectAttributes, + IN PCLIENT_ID ClientId); + +typedef NTSTATUS(WINAPI* NtOpenProcessFunction)(OUT PHANDLE ProcessHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES + ObjectAttributes, + IN PCLIENT_ID ClientId); + +typedef enum _NT_THREAD_INFORMATION_CLASS { + ThreadBasicInformation, + ThreadTimes, + ThreadPriority, + ThreadBasePriority, + ThreadAffinityMask, + ThreadImpersonationToken, + ThreadDescriptorTableEntry, + ThreadEnableAlignmentFaultFixup, + ThreadEventPair, + ThreadQuerySetWin32StartAddress, + ThreadZeroTlsCell, + ThreadPerformanceCount, + ThreadAmILastThread, + ThreadIdealProcessor, + ThreadPriorityBoost, + ThreadSetTlsArrayAddress, + ThreadIsIoPending, + ThreadHideFromDebugger +} NT_THREAD_INFORMATION_CLASS, + *PNT_THREAD_INFORMATION_CLASS; + +typedef NTSTATUS(WINAPI* NtSetInformationThreadFunction)( + IN HANDLE ThreadHandle, + IN NT_THREAD_INFORMATION_CLASS ThreadInformationClass, + IN PVOID ThreadInformation, + IN ULONG ThreadInformationLength); + +// Partial definition only: +typedef enum _PROCESSINFOCLASS { + ProcessBasicInformation = 0, + ProcessExecuteFlags = 0x22 +} PROCESSINFOCLASS; + +// For the structure documentation, see +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa813741(v=vs.85).aspx +typedef struct _RTL_USER_PROCESS_PARAMETERS { + BYTE Reserved1[16]; + PVOID Reserved2[10]; + UNICODE_STRING ImagePathName; + UNICODE_STRING CommandLine; +} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; + +// Partial definition only, from +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa813706(v=vs.85).aspx +typedef struct _PEB { + BYTE InheritedAddressSpace; + BYTE ReadImageFileExecOptions; + BYTE BeingDebugged; + BYTE SpareBool; + PVOID Mutant; + PVOID ImageBaseAddress; + PVOID Ldr; + PRTL_USER_PROCESS_PARAMETERS ProcessParameters; +} PEB, *PPEB; + +typedef LONG KPRIORITY; + +typedef struct _PROCESS_BASIC_INFORMATION { + union { + NTSTATUS ExitStatus; + PVOID padding_for_x64_0; + }; + PPEB PebBaseAddress; + KAFFINITY AffinityMask; + union { + KPRIORITY BasePriority; + PVOID padding_for_x64_1; + }; + union { + DWORD UniqueProcessId; + PVOID padding_for_x64_2; + }; + union { + DWORD InheritedFromUniqueProcessId; + PVOID padding_for_x64_3; + }; +} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION; + +typedef NTSTATUS(WINAPI* NtQueryInformationProcessFunction)( + IN HANDLE ProcessHandle, + IN PROCESSINFOCLASS ProcessInformationClass, + OUT PVOID ProcessInformation, + IN ULONG ProcessInformationLength, + OUT PULONG ReturnLength OPTIONAL); + +typedef NTSTATUS(WINAPI* NtSetInformationProcessFunction)( + HANDLE ProcessHandle, + IN PROCESSINFOCLASS ProcessInformationClass, + IN PVOID ProcessInformation, + IN ULONG ProcessInformationLength); + +typedef NTSTATUS(WINAPI* NtOpenThreadTokenFunction)(IN HANDLE ThreadHandle, + IN ACCESS_MASK + DesiredAccess, + IN BOOLEAN OpenAsSelf, + OUT PHANDLE TokenHandle); + +typedef NTSTATUS(WINAPI* NtOpenThreadTokenExFunction)(IN HANDLE ThreadHandle, + IN ACCESS_MASK + DesiredAccess, + IN BOOLEAN OpenAsSelf, + IN ULONG HandleAttributes, + OUT PHANDLE TokenHandle); + +typedef NTSTATUS(WINAPI* NtOpenProcessTokenFunction)(IN HANDLE ProcessHandle, + IN ACCESS_MASK + DesiredAccess, + OUT PHANDLE TokenHandle); + +typedef NTSTATUS(WINAPI* NtOpenProcessTokenExFunction)( + IN HANDLE ProcessHandle, + IN ACCESS_MASK DesiredAccess, + IN ULONG HandleAttributes, + OUT PHANDLE TokenHandle); + +typedef NTSTATUS(WINAPI* NtQueryInformationTokenFunction)( + IN HANDLE TokenHandle, + IN TOKEN_INFORMATION_CLASS TokenInformationClass, + OUT PVOID TokenInformation, + IN ULONG TokenInformationLength, + OUT PULONG ReturnLength); + +typedef NTSTATUS(WINAPI* RtlCreateUserThreadFunction)( + IN HANDLE Process, + IN PSECURITY_DESCRIPTOR ThreadSecurityDescriptor, + IN BOOLEAN CreateSuspended, + IN ULONG ZeroBits, + IN SIZE_T MaximumStackSize, + IN SIZE_T CommittedStackSize, + IN LPTHREAD_START_ROUTINE StartAddress, + IN PVOID Parameter, + OUT PHANDLE Thread, + OUT PCLIENT_ID ClientId); + +typedef NTSTATUS(WINAPI* RtlConvertSidToUnicodeStringFunction)( + OUT PUNICODE_STRING UnicodeString, + IN PSID Sid, + IN BOOLEAN AllocateDestinationString); + +typedef VOID(WINAPI* RtlFreeUnicodeStringFunction)( + IN OUT PUNICODE_STRING UnicodeString); + +// ----------------------------------------------------------------------- +// Registry + +typedef enum _KEY_INFORMATION_CLASS { + KeyBasicInformation = 0, + KeyFullInformation = 2 +} KEY_INFORMATION_CLASS, + *PKEY_INFORMATION_CLASS; + +typedef struct _KEY_BASIC_INFORMATION { + LARGE_INTEGER LastWriteTime; + ULONG TitleIndex; + ULONG NameLength; + WCHAR Name[1]; +} KEY_BASIC_INFORMATION, *PKEY_BASIC_INFORMATION; + +typedef struct _KEY_FULL_INFORMATION { + LARGE_INTEGER LastWriteTime; + ULONG TitleIndex; + ULONG ClassOffset; + ULONG ClassLength; + ULONG SubKeys; + ULONG MaxNameLen; + ULONG MaxClassLen; + ULONG Values; + ULONG MaxValueNameLen; + ULONG MaxValueDataLen; + WCHAR Class[1]; +} KEY_FULL_INFORMATION, *PKEY_FULL_INFORMATION; + +typedef enum _KEY_VALUE_INFORMATION_CLASS { + KeyValueFullInformation = 1 +} KEY_VALUE_INFORMATION_CLASS, + *PKEY_VALUE_INFORMATION_CLASS; + +typedef struct _KEY_VALUE_FULL_INFORMATION { + ULONG TitleIndex; + ULONG Type; + ULONG DataOffset; + ULONG DataLength; + ULONG NameLength; + WCHAR Name[1]; +} KEY_VALUE_FULL_INFORMATION, *PKEY_VALUE_FULL_INFORMATION; + +typedef NTSTATUS(WINAPI* NtCreateKeyFunction)(OUT PHANDLE KeyHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES + ObjectAttributes, + IN ULONG TitleIndex, + IN PUNICODE_STRING Class OPTIONAL, + IN ULONG CreateOptions, + OUT PULONG Disposition OPTIONAL); + +typedef NTSTATUS(WINAPI* NtOpenKeyFunction)(OUT PHANDLE KeyHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES + ObjectAttributes); + +typedef NTSTATUS(WINAPI* NtOpenKeyExFunction)(OUT PHANDLE KeyHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES + ObjectAttributes, + IN DWORD open_options); + +typedef NTSTATUS(WINAPI* NtDeleteKeyFunction)(IN HANDLE KeyHandle); + +typedef NTSTATUS(WINAPI* RtlFormatCurrentUserKeyPathFunction)( + OUT PUNICODE_STRING RegistryPath); + +typedef NTSTATUS(WINAPI* NtQueryKeyFunction)(IN HANDLE KeyHandle, + IN KEY_INFORMATION_CLASS + KeyInformationClass, + OUT PVOID KeyInformation, + IN ULONG Length, + OUT PULONG ResultLength); + +typedef NTSTATUS(WINAPI* NtEnumerateKeyFunction)(IN HANDLE KeyHandle, + IN ULONG Index, + IN KEY_INFORMATION_CLASS + KeyInformationClass, + OUT PVOID KeyInformation, + IN ULONG Length, + OUT PULONG ResultLength); + +typedef NTSTATUS(WINAPI* NtQueryValueKeyFunction)(IN HANDLE KeyHandle, + IN PUNICODE_STRING ValueName, + IN KEY_VALUE_INFORMATION_CLASS + KeyValueInformationClass, + OUT PVOID KeyValueInformation, + IN ULONG Length, + OUT PULONG ResultLength); + +typedef NTSTATUS(WINAPI* NtSetValueKeyFunction)(IN HANDLE KeyHandle, + IN PUNICODE_STRING ValueName, + IN ULONG TitleIndex OPTIONAL, + IN ULONG Type, + IN PVOID Data, + IN ULONG DataSize); + +// ----------------------------------------------------------------------- +// Memory + +// Don't really need this structure right now. +typedef PVOID PRTL_HEAP_PARAMETERS; + +typedef PVOID(WINAPI* RtlCreateHeapFunction)(IN ULONG Flags, + IN PVOID HeapBase OPTIONAL, + IN SIZE_T ReserveSize OPTIONAL, + IN SIZE_T CommitSize OPTIONAL, + IN PVOID Lock OPTIONAL, + IN PRTL_HEAP_PARAMETERS Parameters + OPTIONAL); + +typedef PVOID(WINAPI* RtlDestroyHeapFunction)(IN PVOID HeapHandle); + +typedef PVOID(WINAPI* RtlAllocateHeapFunction)(IN PVOID HeapHandle, + IN ULONG Flags, + IN SIZE_T Size); + +typedef BOOLEAN(WINAPI* RtlFreeHeapFunction)(IN PVOID HeapHandle, + IN ULONG Flags, + IN PVOID HeapBase); + +typedef NTSTATUS(WINAPI* NtAllocateVirtualMemoryFunction)( + IN HANDLE ProcessHandle, + IN OUT PVOID* BaseAddress, + IN ULONG_PTR ZeroBits, + IN OUT PSIZE_T RegionSize, + IN ULONG AllocationType, + IN ULONG Protect); + +typedef NTSTATUS(WINAPI* NtFreeVirtualMemoryFunction)(IN HANDLE ProcessHandle, + IN OUT PVOID* BaseAddress, + IN OUT PSIZE_T RegionSize, + IN ULONG FreeType); + +typedef enum _MEMORY_INFORMATION_CLASS { + MemoryBasicInformation = 0, + MemoryWorkingSetList, + MemorySectionName, + MemoryBasicVlmInformation +} MEMORY_INFORMATION_CLASS; + +typedef struct _MEMORY_SECTION_NAME { // Information Class 2 + UNICODE_STRING SectionFileName; +} MEMORY_SECTION_NAME, *PMEMORY_SECTION_NAME; + +typedef NTSTATUS(WINAPI* NtQueryVirtualMemoryFunction)( + IN HANDLE ProcessHandle, + IN PVOID BaseAddress, + IN MEMORY_INFORMATION_CLASS MemoryInformationClass, + OUT PVOID MemoryInformation, + IN SIZE_T MemoryInformationLength, + OUT PSIZE_T ReturnLength OPTIONAL); + +typedef NTSTATUS(WINAPI* NtProtectVirtualMemoryFunction)( + IN HANDLE ProcessHandle, + IN OUT PVOID* BaseAddress, + IN OUT PSIZE_T ProtectSize, + IN ULONG NewProtect, + OUT PULONG OldProtect); + +// ----------------------------------------------------------------------- +// Objects + +typedef enum _OBJECT_INFORMATION_CLASS { + ObjectBasicInformation, + ObjectNameInformation, + ObjectTypeInformation, + ObjectAllInformation, + ObjectDataInformation +} OBJECT_INFORMATION_CLASS, + *POBJECT_INFORMATION_CLASS; + +typedef struct _OBJDIR_INFORMATION { + UNICODE_STRING ObjectName; + UNICODE_STRING ObjectTypeName; + BYTE Data[1]; +} OBJDIR_INFORMATION; + +typedef struct _PUBLIC_OBJECT_BASIC_INFORMATION { + ULONG Attributes; + ACCESS_MASK GrantedAccess; + ULONG HandleCount; + ULONG PointerCount; + ULONG Reserved[10]; // reserved for internal use +} PUBLIC_OBJECT_BASIC_INFORMATION, *PPUBLIC_OBJECT_BASIC_INFORMATION; + +typedef struct __PUBLIC_OBJECT_TYPE_INFORMATION { + UNICODE_STRING TypeName; + ULONG Reserved[22]; // reserved for internal use +} PUBLIC_OBJECT_TYPE_INFORMATION, *PPUBLIC_OBJECT_TYPE_INFORMATION; + +typedef enum _POOL_TYPE { + NonPagedPool, + PagedPool, + NonPagedPoolMustSucceed, + ReservedType, + NonPagedPoolCacheAligned, + PagedPoolCacheAligned, + NonPagedPoolCacheAlignedMustS +} POOL_TYPE; + +typedef struct _OBJECT_BASIC_INFORMATION { + ULONG Attributes; + ACCESS_MASK GrantedAccess; + ULONG HandleCount; + ULONG PointerCount; + ULONG PagedPoolUsage; + ULONG NonPagedPoolUsage; + ULONG Reserved[3]; + ULONG NameInformationLength; + ULONG TypeInformationLength; + ULONG SecurityDescriptorLength; + LARGE_INTEGER CreateTime; +} OBJECT_BASIC_INFORMATION, *POBJECT_BASIC_INFORMATION; + +typedef struct _OBJECT_TYPE_INFORMATION { + UNICODE_STRING Name; + ULONG TotalNumberOfObjects; + ULONG TotalNumberOfHandles; + ULONG TotalPagedPoolUsage; + ULONG TotalNonPagedPoolUsage; + ULONG TotalNamePoolUsage; + ULONG TotalHandleTableUsage; + ULONG HighWaterNumberOfObjects; + ULONG HighWaterNumberOfHandles; + ULONG HighWaterPagedPoolUsage; + ULONG HighWaterNonPagedPoolUsage; + ULONG HighWaterNamePoolUsage; + ULONG HighWaterHandleTableUsage; + ULONG InvalidAttributes; + GENERIC_MAPPING GenericMapping; + ULONG ValidAccess; + BOOLEAN SecurityRequired; + BOOLEAN MaintainHandleCount; + USHORT MaintainTypeList; + POOL_TYPE PoolType; + ULONG PagedPoolUsage; + ULONG NonPagedPoolUsage; +} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; + +typedef enum _SYSTEM_INFORMATION_CLASS { + SystemHandleInformation = 16 +} SYSTEM_INFORMATION_CLASS; + +typedef struct _SYSTEM_HANDLE_INFORMATION { + USHORT ProcessId; + USHORT CreatorBackTraceIndex; + UCHAR ObjectTypeNumber; + UCHAR Flags; + USHORT Handle; + PVOID Object; + ACCESS_MASK GrantedAccess; +} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION; + +typedef struct _SYSTEM_HANDLE_INFORMATION_EX { + ULONG NumberOfHandles; + SYSTEM_HANDLE_INFORMATION Information[1]; +} SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX; + +typedef struct _OBJECT_NAME_INFORMATION { + UNICODE_STRING ObjectName; +} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION; + +typedef NTSTATUS(WINAPI* NtQueryObjectFunction)( + IN HANDLE Handle, + IN OBJECT_INFORMATION_CLASS ObjectInformationClass, + OUT PVOID ObjectInformation OPTIONAL, + IN ULONG ObjectInformationLength, + OUT PULONG ReturnLength OPTIONAL); + +typedef NTSTATUS(WINAPI* NtDuplicateObjectFunction)(IN HANDLE SourceProcess, + IN HANDLE SourceHandle, + IN HANDLE TargetProcess, + OUT PHANDLE TargetHandle, + IN ACCESS_MASK + DesiredAccess, + IN ULONG Attributes, + IN ULONG Options); + +typedef NTSTATUS(WINAPI* NtSignalAndWaitForSingleObjectFunction)( + IN HANDLE HandleToSignal, + IN HANDLE HandleToWait, + IN BOOLEAN Alertable, + IN PLARGE_INTEGER Timeout OPTIONAL); + +typedef NTSTATUS(WINAPI* NtWaitForSingleObjectFunction)( + IN HANDLE ObjectHandle, + IN BOOLEAN Alertable, + IN PLARGE_INTEGER TimeOut OPTIONAL); + +typedef NTSTATUS(WINAPI* NtQuerySystemInformation)( + IN SYSTEM_INFORMATION_CLASS SystemInformationClass, + OUT PVOID SystemInformation, + IN ULONG SystemInformationLength, + OUT PULONG ReturnLength); + +typedef NTSTATUS(WINAPI* NtQueryObject)(IN HANDLE Handle, + IN OBJECT_INFORMATION_CLASS + ObjectInformationClass, + OUT PVOID ObjectInformation, + IN ULONG ObjectInformationLength, + OUT PULONG ReturnLength); + +// ----------------------------------------------------------------------- +// Strings + +typedef int(__cdecl* _strnicmpFunction)(IN const char* _Str1, + IN const char* _Str2, + IN size_t _MaxCount); + +typedef size_t(__cdecl* strlenFunction)(IN const char* _Str); + +typedef size_t(__cdecl* wcslenFunction)(IN const wchar_t* _Str); + +typedef void*(__cdecl* memcpyFunction)(IN void* dest, + IN const void* src, + IN size_t count); + +typedef NTSTATUS(WINAPI* RtlAnsiStringToUnicodeStringFunction)( + IN OUT PUNICODE_STRING DestinationString, + IN PANSI_STRING SourceString, + IN BOOLEAN AllocateDestinationString); + +typedef LONG(WINAPI* RtlCompareUnicodeStringFunction)( + IN PCUNICODE_STRING String1, + IN PCUNICODE_STRING String2, + IN BOOLEAN CaseInSensitive); + +typedef VOID(WINAPI* RtlInitUnicodeStringFunction)(IN OUT PUNICODE_STRING + DestinationString, + IN PCWSTR SourceString); + +typedef ULONG(WINAPI* RtlNtStatusToDosErrorFunction)(NTSTATUS status); + +typedef enum _EVENT_TYPE { + NotificationEvent, + SynchronizationEvent +} EVENT_TYPE, + *PEVENT_TYPE; + +typedef NTSTATUS(WINAPI* NtCreateDirectoryObjectFunction)( + PHANDLE DirectoryHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS(WINAPI* NtOpenDirectoryObjectFunction)( + PHANDLE DirectoryHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS(WINAPI* NtQuerySymbolicLinkObjectFunction)( + HANDLE LinkHandle, + PUNICODE_STRING LinkTarget, + PULONG ReturnedLength); + +typedef NTSTATUS(WINAPI* NtOpenSymbolicLinkObjectFunction)( + PHANDLE LinkHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes); + +#define DIRECTORY_QUERY 0x0001 +#define DIRECTORY_TRAVERSE 0x0002 +#define DIRECTORY_CREATE_OBJECT 0x0004 +#define DIRECTORY_CREATE_SUBDIRECTORY 0x0008 +#define DIRECTORY_ALL_ACCESS 0x000F + +typedef NTSTATUS(WINAPI* NtCreateLowBoxToken)( + OUT PHANDLE token, + IN HANDLE original_handle, + IN ACCESS_MASK access, + IN POBJECT_ATTRIBUTES object_attribute, + IN PSID appcontainer_sid, + IN DWORD capabilityCount, + IN PSID_AND_ATTRIBUTES capabilities, + IN DWORD handle_count, + IN PHANDLE handles); + +typedef NTSTATUS(WINAPI* NtSetInformationProcess)(IN HANDLE process_handle, + IN ULONG info_class, + IN PVOID process_information, + IN ULONG information_length); + +struct PROCESS_ACCESS_TOKEN { + HANDLE token; + HANDLE thread; +}; + +const unsigned int NtProcessInformationAccessToken = 9; + +typedef NTSTATUS(WINAPI* RtlDeriveCapabilitySidsFromNameFunction)( + PCUNICODE_STRING SourceString, + PSID CapabilityGroupSid, + PSID CapabilitySid); + +// ----------------------------------------------------------------------- +// GDI OPM API and Supported Calls + +#define DXGKMDT_OPM_OMAC_SIZE 16 +#define DXGKMDT_OPM_128_BIT_RANDOM_NUMBER_SIZE 16 +#define DXGKMDT_OPM_ENCRYPTED_PARAMETERS_SIZE 256 +#define DXGKMDT_OPM_CONFIGURE_SETTING_DATA_SIZE 4056 +#define DXGKMDT_OPM_GET_INFORMATION_PARAMETERS_SIZE 4056 +#define DXGKMDT_OPM_REQUESTED_INFORMATION_SIZE 4076 +#define DXGKMDT_OPM_HDCP_KEY_SELECTION_VECTOR_SIZE 5 +#define DXGKMDT_OPM_PROTECTION_TYPE_SIZE 4 + +enum DXGKMDT_CERTIFICATE_TYPE { + DXGKMDT_OPM_CERTIFICATE = 0, + DXGKMDT_COPP_CERTIFICATE = 1, + DXGKMDT_UAB_CERTIFICATE = 2, + DXGKMDT_FORCE_ULONG = 0xFFFFFFFF +}; + +enum DXGKMDT_OPM_VIDEO_OUTPUT_SEMANTICS { + DXGKMDT_OPM_VOS_COPP_SEMANTICS = 0, + DXGKMDT_OPM_VOS_OPM_SEMANTICS = 1 +}; + +enum DXGKMDT_DPCP_PROTECTION_LEVEL { + DXGKMDT_OPM_DPCP_OFF = 0, + DXGKMDT_OPM_DPCP_ON = 1, + DXGKMDT_OPM_DPCP_FORCE_ULONG = 0x7fffffff +}; + +enum DXGKMDT_OPM_HDCP_PROTECTION_LEVEL { + DXGKMDT_OPM_HDCP_OFF = 0, + DXGKMDT_OPM_HDCP_ON = 1, + DXGKMDT_OPM_HDCP_FORCE_ULONG = 0x7fffffff +}; + +enum DXGKMDT_OPM_HDCP_FLAG { + DXGKMDT_OPM_HDCP_FLAG_NONE = 0x00, + DXGKMDT_OPM_HDCP_FLAG_REPEATER = 0x01 +}; + +enum DXGKMDT_OPM_PROTECTION_TYPE { + DXGKMDT_OPM_PROTECTION_TYPE_OTHER = 0x80000000, + DXGKMDT_OPM_PROTECTION_TYPE_NONE = 0x00000000, + DXGKMDT_OPM_PROTECTION_TYPE_COPP_COMPATIBLE_HDCP = 0x00000001, + DXGKMDT_OPM_PROTECTION_TYPE_ACP = 0x00000002, + DXGKMDT_OPM_PROTECTION_TYPE_CGMSA = 0x00000004, + DXGKMDT_OPM_PROTECTION_TYPE_HDCP = 0x00000008, + DXGKMDT_OPM_PROTECTION_TYPE_DPCP = 0x00000010, + DXGKMDT_OPM_PROTECTION_TYPE_MASK = 0x8000001F +}; + +typedef void* OPM_PROTECTED_OUTPUT_HANDLE; + +struct DXGKMDT_OPM_ENCRYPTED_PARAMETERS { + BYTE abEncryptedParameters[DXGKMDT_OPM_ENCRYPTED_PARAMETERS_SIZE]; +}; + +struct DXGKMDT_OPM_OMAC { + BYTE abOMAC[DXGKMDT_OPM_OMAC_SIZE]; +}; + +struct DXGKMDT_OPM_CONFIGURE_PARAMETERS { + DXGKMDT_OPM_OMAC omac; + GUID guidSetting; + ULONG ulSequenceNumber; + ULONG cbParametersSize; + BYTE abParameters[DXGKMDT_OPM_CONFIGURE_SETTING_DATA_SIZE]; +}; + +struct DXGKMDT_OPM_RANDOM_NUMBER { + BYTE abRandomNumber[DXGKMDT_OPM_128_BIT_RANDOM_NUMBER_SIZE]; +}; + +struct DXGKMDT_OPM_GET_INFO_PARAMETERS { + DXGKMDT_OPM_OMAC omac; + DXGKMDT_OPM_RANDOM_NUMBER rnRandomNumber; + GUID guidInformation; + ULONG ulSequenceNumber; + ULONG cbParametersSize; + BYTE abParameters[DXGKMDT_OPM_GET_INFORMATION_PARAMETERS_SIZE]; +}; + +struct DXGKMDT_OPM_REQUESTED_INFORMATION { + DXGKMDT_OPM_OMAC omac; + ULONG cbRequestedInformationSize; + BYTE abRequestedInformation[DXGKMDT_OPM_REQUESTED_INFORMATION_SIZE]; +}; + +struct DXGKMDT_OPM_SET_PROTECTION_LEVEL_PARAMETERS { + ULONG ulProtectionType; + ULONG ulProtectionLevel; + ULONG Reserved; + ULONG Reserved2; +}; + +struct DXGKMDT_OPM_STANDARD_INFORMATION { + DXGKMDT_OPM_RANDOM_NUMBER rnRandomNumber; + ULONG ulStatusFlags; + ULONG ulInformation; + ULONG ulReserved; + ULONG ulReserved2; +}; + +typedef NTSTATUS(WINAPI* GetSuggestedOPMProtectedOutputArraySizeFunction)( + PUNICODE_STRING device_name, + DWORD* suggested_output_array_size); + +typedef NTSTATUS(WINAPI* CreateOPMProtectedOutputsFunction)( + PUNICODE_STRING device_name, + DXGKMDT_OPM_VIDEO_OUTPUT_SEMANTICS vos, + DWORD output_array_size, + DWORD* num_in_output_array, + OPM_PROTECTED_OUTPUT_HANDLE* output_array); + +typedef NTSTATUS(WINAPI* GetCertificateFunction)( + PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length); + +typedef NTSTATUS(WINAPI* GetCertificateSizeFunction)( + PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length); + +typedef NTSTATUS(WINAPI* GetCertificateByHandleFunction)( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length); + +typedef NTSTATUS(WINAPI* GetCertificateSizeByHandleFunction)( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length); + +typedef NTSTATUS(WINAPI* DestroyOPMProtectedOutputFunction)( + OPM_PROTECTED_OUTPUT_HANDLE protected_output); + +typedef NTSTATUS(WINAPI* ConfigureOPMProtectedOutputFunction)( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_CONFIGURE_PARAMETERS* parameters, + ULONG additional_parameters_size, + const BYTE* additional_parameters); + +typedef NTSTATUS(WINAPI* GetOPMInformationFunction)( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_GET_INFO_PARAMETERS* parameters, + DXGKMDT_OPM_REQUESTED_INFORMATION* requested_information); + +typedef NTSTATUS(WINAPI* GetOPMRandomNumberFunction)( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_OPM_RANDOM_NUMBER* random_number); + +typedef NTSTATUS(WINAPI* SetOPMSigningKeyAndSequenceNumbersFunction)( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_ENCRYPTED_PARAMETERS* parameters); + +#endif // SANDBOX_WIN_SRC_NT_INTERNALS_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_broker.cc b/security/sandbox/chromium/sandbox/win/src/policy_broker.cc new file mode 100644 index 0000000000..406057acbb --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_broker.cc @@ -0,0 +1,123 @@ +// 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 "sandbox/win/src/policy_broker.h" + +#include <stddef.h> + +#include <map> + +#include "base/logging.h" +#include "base/win/pe_image.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/process_thread_interception.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/target_process.h" + +// This code executes on the broker side, as a callback from the policy on the +// target side (the child). + +namespace sandbox { + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt; + +#define INIT_GLOBAL_NT(member) \ + g_nt.member = reinterpret_cast<Nt##member##Function>( \ + ntdll_image.GetProcAddress("Nt" #member)); \ + if (!g_nt.member) \ + return false + +#define INIT_GLOBAL_RTL(member) \ + g_nt.member = \ + reinterpret_cast<member##Function>(ntdll_image.GetProcAddress(#member)); \ + if (!g_nt.member) \ + return false + +bool InitGlobalNt() { + HMODULE ntdll = ::GetModuleHandle(kNtdllName); + base::win::PEImage ntdll_image(ntdll); + + INIT_GLOBAL_NT(AllocateVirtualMemory); + INIT_GLOBAL_NT(Close); + INIT_GLOBAL_NT(DuplicateObject); + INIT_GLOBAL_NT(FreeVirtualMemory); + INIT_GLOBAL_NT(MapViewOfSection); + INIT_GLOBAL_NT(ProtectVirtualMemory); + INIT_GLOBAL_NT(QueryInformationProcess); + INIT_GLOBAL_NT(QueryObject); + INIT_GLOBAL_NT(QuerySection); + INIT_GLOBAL_NT(QueryVirtualMemory); + INIT_GLOBAL_NT(UnmapViewOfSection); + INIT_GLOBAL_NT(SignalAndWaitForSingleObject); + INIT_GLOBAL_NT(WaitForSingleObject); + + INIT_GLOBAL_RTL(RtlAllocateHeap); + INIT_GLOBAL_RTL(RtlAnsiStringToUnicodeString); + INIT_GLOBAL_RTL(RtlCompareUnicodeString); + INIT_GLOBAL_RTL(RtlCreateHeap); + INIT_GLOBAL_RTL(RtlCreateUserThread); + INIT_GLOBAL_RTL(RtlDestroyHeap); + INIT_GLOBAL_RTL(RtlFreeHeap); + INIT_GLOBAL_RTL(_strnicmp); + INIT_GLOBAL_RTL(strlen); + INIT_GLOBAL_RTL(wcslen); + INIT_GLOBAL_RTL(memcpy); + + return true; +} + +bool SetupNtdllImports(TargetProcess* child) { + if (!InitGlobalNt()) { + return false; + } + +#ifndef NDEBUG + // Verify that the structure is fully initialized. + for (size_t i = 0; i < sizeof(g_nt) / sizeof(void*); i++) + DCHECK(reinterpret_cast<char**>(&g_nt)[i]); +#endif + return (SBOX_ALL_OK == child->TransferVariable("g_nt", &g_nt, sizeof(g_nt))); +} + +#undef INIT_GLOBAL_NT +#undef INIT_GLOBAL_RTL + +bool SetupBasicInterceptions(InterceptionManager* manager, + bool is_csrss_connected) { + // Interceptions provided by process_thread_policy, without actual policy. + if (!INTERCEPT_NT(manager, NtOpenThread, OPEN_THREAD_ID, 20) || + !INTERCEPT_NT(manager, NtOpenProcess, OPEN_PROCESS_ID, 20) || + !INTERCEPT_NT(manager, NtOpenProcessToken, OPEN_PROCESS_TOKEN_ID, 16)) + return false; + + // Interceptions with neither policy nor IPC. + if (!INTERCEPT_NT(manager, NtSetInformationThread, SET_INFORMATION_THREAD_ID, + 20) || + !INTERCEPT_NT(manager, NtOpenThreadToken, OPEN_THREAD_TOKEN_ID, 20)) + return false; + + // This one is also provided by process_thread_policy. + if (!INTERCEPT_NT(manager, NtOpenProcessTokenEx, OPEN_PROCESS_TOKEN_EX_ID, + 20)) + return false; + + if (!INTERCEPT_NT(manager, NtOpenThreadTokenEx, OPEN_THREAD_TOKEN_EX_ID, 24)) + return false; + + if (!is_csrss_connected) { + if (!INTERCEPT_EAT(manager, kKerneldllName, CreateThread, CREATE_THREAD_ID, + 28)) + return false; + } + + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_broker.h b/security/sandbox/chromium/sandbox/win/src/policy_broker.h new file mode 100644 index 0000000000..95b123bd0d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_broker.h @@ -0,0 +1,27 @@ +// Copyright (c) 2006-2010 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. + +#ifndef SANDBOX_SRC_POLICY_BROKER_H_ +#define SANDBOX_SRC_POLICY_BROKER_H_ + +#include "sandbox/win/src/interception.h" + +namespace sandbox { + +class TargetProcess; + +// Initializes global imported symbols from ntdll. +bool InitGlobalNt(); + +// Sets up interceptions not controlled by explicit policies. +bool SetupBasicInterceptions(InterceptionManager* manager, + bool is_csrss_connected); + +// Sets up imports from NTDLL for the given target process so the interceptions +// can work. +bool SetupNtdllImports(TargetProcess* child); + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_BROKER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.cc b/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.cc new file mode 100644 index 0000000000..02c636f4aa --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.cc @@ -0,0 +1,450 @@ +// Copyright (c) 2006-2008 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/win/src/policy_engine_opcodes.h" + +#include <stddef.h> +#include <stdint.h> + +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace { +const unsigned short kMaxUniStrSize = 0xfffc / sizeof(wchar_t); + +bool InitStringUnicode(const wchar_t* source, + size_t length, + UNICODE_STRING* ustring) { + if (length > kMaxUniStrSize) { + return false; + } + ustring->Buffer = const_cast<wchar_t*>(source); + ustring->Length = static_cast<USHORT>(length) * sizeof(wchar_t); + ustring->MaximumLength = source ? ustring->Length + sizeof(wchar_t) : 0; + return true; +} + +} // namespace + +namespace sandbox { + +SANDBOX_INTERCEPT NtExports g_nt; + +// Note: The opcodes are implemented as functions (as opposed to classes derived +// from PolicyOpcode) because you should not add more member variables to the +// PolicyOpcode class since it would cause object slicing on the target. So to +// enforce that (instead of just trusting the developer) the opcodes became +// just functions. +// +// In the code that follows I have keep the evaluation function and the factory +// function together to stress the close relationship between both. For example, +// only the factory method and the evaluation function know the stored argument +// order and meaning. + +template <int> +EvalResult OpcodeEval(PolicyOpcode* opcode, + const ParameterSet* pp, + MatchContext* match); + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpAlwaysFalse: +// Does not require input parameter. + +PolicyOpcode* OpcodeFactory::MakeOpAlwaysFalse(uint32_t options) { + return MakeBase(OP_ALWAYS_FALSE, options, -1); +} + +template <> +EvalResult OpcodeEval<OP_ALWAYS_FALSE>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + return EVAL_FALSE; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpAlwaysTrue: +// Does not require input parameter. + +PolicyOpcode* OpcodeFactory::MakeOpAlwaysTrue(uint32_t options) { + return MakeBase(OP_ALWAYS_TRUE, options, -1); +} + +template <> +EvalResult OpcodeEval<OP_ALWAYS_TRUE>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + return EVAL_TRUE; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpAction: +// Does not require input parameter. +// Argument 0 contains the actual action to return. + +PolicyOpcode* OpcodeFactory::MakeOpAction(EvalResult action, uint32_t options) { + PolicyOpcode* opcode = MakeBase(OP_ACTION, options, -1); + if (!opcode) + return nullptr; + opcode->SetArgument(0, action); + return opcode; +} + +template <> +EvalResult OpcodeEval<OP_ACTION>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + int action = 0; + opcode->GetArgument(0, &action); + return static_cast<EvalResult>(action); +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpNumberMatch: +// Requires a uint32_t or void* in selected_param +// Argument 0 is the stored number to match. +// Argument 1 is the C++ type of the 0th argument. + +PolicyOpcode* OpcodeFactory::MakeOpNumberMatch(int16_t selected_param, + uint32_t match, + uint32_t options) { + PolicyOpcode* opcode = MakeBase(OP_NUMBER_MATCH, options, selected_param); + if (!opcode) + return nullptr; + opcode->SetArgument(0, match); + opcode->SetArgument(1, UINT32_TYPE); + return opcode; +} + +PolicyOpcode* OpcodeFactory::MakeOpVoidPtrMatch(int16_t selected_param, + const void* match, + uint32_t options) { + PolicyOpcode* opcode = MakeBase(OP_NUMBER_MATCH, options, selected_param); + if (!opcode) + return nullptr; + opcode->SetArgument(0, match); + opcode->SetArgument(1, VOIDPTR_TYPE); + return opcode; +} + +template <> +EvalResult OpcodeEval<OP_NUMBER_MATCH>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + uint32_t value_uint32 = 0; + if (param->Get(&value_uint32)) { + uint32_t match_uint32 = 0; + opcode->GetArgument(0, &match_uint32); + return (match_uint32 != value_uint32) ? EVAL_FALSE : EVAL_TRUE; + } else { + const void* value_ptr = nullptr; + if (param->Get(&value_ptr)) { + const void* match_ptr = nullptr; + opcode->GetArgument(0, &match_ptr); + return (match_ptr != value_ptr) ? EVAL_FALSE : EVAL_TRUE; + } + } + return EVAL_ERROR; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpNumberMatchRange +// Requires a uint32_t in selected_param. +// Argument 0 is the stored lower bound to match. +// Argument 1 is the stored upper bound to match. + +PolicyOpcode* OpcodeFactory::MakeOpNumberMatchRange(int16_t selected_param, + uint32_t lower_bound, + uint32_t upper_bound, + uint32_t options) { + if (lower_bound > upper_bound) { + return nullptr; + } + PolicyOpcode* opcode = + MakeBase(OP_NUMBER_MATCH_RANGE, options, selected_param); + if (!opcode) + return nullptr; + opcode->SetArgument(0, lower_bound); + opcode->SetArgument(1, upper_bound); + return opcode; +} + +template <> +EvalResult OpcodeEval<OP_NUMBER_MATCH_RANGE>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + uint32_t value = 0; + if (!param->Get(&value)) + return EVAL_ERROR; + + uint32_t lower_bound = 0; + uint32_t upper_bound = 0; + opcode->GetArgument(0, &lower_bound); + opcode->GetArgument(1, &upper_bound); + return ((lower_bound <= value) && (upper_bound >= value)) ? EVAL_TRUE + : EVAL_FALSE; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpNumberAndMatch: +// Requires a uint32_t in selected_param. +// Argument 0 is the stored number to match. + +PolicyOpcode* OpcodeFactory::MakeOpNumberAndMatch(int16_t selected_param, + uint32_t match, + uint32_t options) { + PolicyOpcode* opcode = MakeBase(OP_NUMBER_AND_MATCH, options, selected_param); + if (!opcode) + return nullptr; + opcode->SetArgument(0, match); + return opcode; +} + +template <> +EvalResult OpcodeEval<OP_NUMBER_AND_MATCH>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + uint32_t value = 0; + if (!param->Get(&value)) + return EVAL_ERROR; + + uint32_t number = 0; + opcode->GetArgument(0, &number); + return (number & value) ? EVAL_TRUE : EVAL_FALSE; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpWStringMatch: +// Requires a wchar_t* in selected_param. +// Argument 0 is the byte displacement of the stored string. +// Argument 1 is the length in chars of the stored string. +// Argument 2 is the offset to apply on the input string. It has special values. +// as noted in the header file. +// Argument 3 is the string matching options. + +PolicyOpcode* OpcodeFactory::MakeOpWStringMatch(int16_t selected_param, + const wchar_t* match_str, + int start_position, + StringMatchOptions match_opts, + uint32_t options) { + if (!match_str) + return nullptr; + if ('\0' == match_str[0]) + return nullptr; + + int length = lstrlenW(match_str); + + PolicyOpcode* opcode = MakeBase(OP_WSTRING_MATCH, options, selected_param); + if (!opcode) + return nullptr; + ptrdiff_t delta_str = AllocRelative(opcode, match_str, wcslen(match_str) + 1); + if (0 == delta_str) + return nullptr; + opcode->SetArgument(0, delta_str); + opcode->SetArgument(1, length); + opcode->SetArgument(2, start_position); + opcode->SetArgument(3, match_opts); + return opcode; +} + +template <> +EvalResult OpcodeEval<OP_WSTRING_MATCH>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + if (!context) { + return EVAL_ERROR; + } + const wchar_t* source_str = nullptr; + if (!param->Get(&source_str)) + return EVAL_ERROR; + + int start_position = 0; + int match_len = 0; + unsigned int match_opts = 0; + opcode->GetArgument(1, &match_len); + opcode->GetArgument(2, &start_position); + opcode->GetArgument(3, &match_opts); + + const wchar_t* match_str = opcode->GetRelativeString(0); + // Advance the source string to the last successfully evaluated position + // according to the match context. + source_str = &source_str[context->position]; + int source_len = static_cast<int>(g_nt.wcslen(source_str)); + + if (0 == source_len) { + // If we reached the end of the source string there is nothing we can + // match against. + return EVAL_FALSE; + } + if (match_len > source_len) { + // There can't be a positive match when the target string is bigger than + // the source string + return EVAL_FALSE; + } + + BOOLEAN case_sensitive = (match_opts & CASE_INSENSITIVE) ? TRUE : FALSE; + + // We have three cases, depending on the value of start_pos: + // Case 1. We skip N characters and compare once. + // Case 2: We skip to the end and compare once. + // Case 3: We match the first substring (if we find any). + if (start_position >= 0) { + if (kSeekToEnd == start_position) { + start_position = source_len - match_len; + } else if (match_opts & EXACT_LENGTH) { + // A sub-case of case 3 is when the EXACT_LENGTH flag is on + // the match needs to be not just substring but full match. + if ((match_len + start_position) != source_len) { + return EVAL_FALSE; + } + } + + // Advance start_pos characters. Warning! this does not consider + // utf16 encodings (surrogate pairs) or other Unicode 'features'. + source_str += start_position; + + // Since we skipped, lets reevaluate just the lengths again. + if ((match_len + start_position) > source_len) { + return EVAL_FALSE; + } + + UNICODE_STRING match_ustr; + UNICODE_STRING source_ustr; + if (!InitStringUnicode(match_str, match_len, &match_ustr) || + !InitStringUnicode(source_str, match_len, &source_ustr)) + return EVAL_ERROR; + + if (0 == g_nt.RtlCompareUnicodeString(&match_ustr, &source_ustr, + case_sensitive)) { + // Match! update the match context. + context->position += start_position + match_len; + return EVAL_TRUE; + } else { + return EVAL_FALSE; + } + } else if (start_position < 0) { + UNICODE_STRING match_ustr; + UNICODE_STRING source_ustr; + if (!InitStringUnicode(match_str, match_len, &match_ustr) || + !InitStringUnicode(source_str, match_len, &source_ustr)) + return EVAL_ERROR; + + do { + if (0 == g_nt.RtlCompareUnicodeString(&match_ustr, &source_ustr, + case_sensitive)) { + // Match! update the match context. + context->position += (source_ustr.Buffer - source_str) + match_len; + return EVAL_TRUE; + } + ++source_ustr.Buffer; + --source_len; + } while (source_len >= match_len); + } + return EVAL_FALSE; +} + +////////////////////////////////////////////////////////////////////////////// +// OpcodeMaker (other member functions). + +PolicyOpcode* OpcodeFactory::MakeBase(OpcodeID opcode_id, + uint32_t options, + int16_t selected_param) { + if (memory_size() < sizeof(PolicyOpcode)) + return nullptr; + + // Create opcode using placement-new on the buffer memory. + PolicyOpcode* opcode = new (memory_top_) PolicyOpcode(); + + // Fill in the standard fields, that every opcode has. + memory_top_ += sizeof(PolicyOpcode); + opcode->opcode_id_ = opcode_id; + opcode->SetOptions(options); + opcode->parameter_ = selected_param; + return opcode; +} + +ptrdiff_t OpcodeFactory::AllocRelative(void* start, + const wchar_t* str, + size_t length) { + size_t bytes = length * sizeof(wchar_t); + if (memory_size() < bytes) + return 0; + memory_bottom_ -= bytes; + if (reinterpret_cast<UINT_PTR>(memory_bottom_) & 1) { + // TODO(cpu) replace this for something better. + ::DebugBreak(); + } + memcpy(memory_bottom_, str, bytes); + ptrdiff_t delta = memory_bottom_ - reinterpret_cast<char*>(start); + return delta; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode evaluation dispatchers. + +// This function is the one and only entry for evaluating any opcode. It is +// in charge of applying any relevant opcode options and calling EvaluateInner +// were the actual dispatch-by-id is made. It would seem at first glance that +// the dispatch should be done by virtual function (vtable) calls but you have +// to remember that the opcodes are made in the broker process and copied as +// raw memory to the target process. + +EvalResult PolicyOpcode::Evaluate(const ParameterSet* call_params, + size_t param_count, + MatchContext* match) { + if (!call_params) + return EVAL_ERROR; + const ParameterSet* selected_param = nullptr; + if (parameter_ >= 0) { + if (static_cast<size_t>(parameter_) >= param_count) { + return EVAL_ERROR; + } + selected_param = &call_params[parameter_]; + } + EvalResult result = EvaluateHelper(selected_param, match); + + // Apply the general options regardless of the particular type of opcode. + if (kPolNone == options_) { + return result; + } + + if (options_ & kPolNegateEval) { + if (EVAL_TRUE == result) { + result = EVAL_FALSE; + } else if (EVAL_FALSE == result) { + result = EVAL_TRUE; + } else if (EVAL_ERROR != result) { + result = EVAL_ERROR; + } + } + if (match) { + if (options_ & kPolClearContext) + match->Clear(); + if (options_ & kPolUseOREval) + match->options = kPolUseOREval; + } + return result; +} + +#define OPCODE_EVAL(op, x, y, z) \ + case op: \ + return OpcodeEval<op>(x, y, z) + +EvalResult PolicyOpcode::EvaluateHelper(const ParameterSet* parameters, + MatchContext* match) { + switch (opcode_id_) { + OPCODE_EVAL(OP_ALWAYS_FALSE, this, parameters, match); + OPCODE_EVAL(OP_ALWAYS_TRUE, this, parameters, match); + OPCODE_EVAL(OP_NUMBER_MATCH, this, parameters, match); + OPCODE_EVAL(OP_NUMBER_MATCH_RANGE, this, parameters, match); + OPCODE_EVAL(OP_NUMBER_AND_MATCH, this, parameters, match); + OPCODE_EVAL(OP_WSTRING_MATCH, this, parameters, match); + OPCODE_EVAL(OP_ACTION, this, parameters, match); + default: + return EVAL_ERROR; + } +} + +#undef OPCODE_EVAL + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.h b/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.h new file mode 100644 index 0000000000..8e8816df1c --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.h @@ -0,0 +1,379 @@ +// Copyright (c) 2010 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. + +#ifndef SANDBOX_WIN_SRC_POLICY_ENGINE_OPCODES_H_ +#define SANDBOX_WIN_SRC_POLICY_ENGINE_OPCODES_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "base/numerics/safe_conversions.h" +#include "sandbox/win/src/policy_engine_params.h" + +// The low-level policy is implemented using the concept of policy 'opcodes'. +// An opcode is a structure that contains enough information to perform one +// comparison against one single input parameter. For example, an opcode can +// encode just one of the following comparison: +// +// - Is input parameter 3 not equal to nullptr? +// - Does input parameter 2 start with L"c:\\"? +// - Is input parameter 5, bit 3 is equal 1? +// +// Each opcode is in fact equivalent to a function invocation where all +// the parameters are known by the opcode except one. So say you have a +// function of this form: +// bool fn(a, b, c, d) with 4 arguments +// +// Then an opcode is: +// op(fn, b, c, d) +// Which stores the function to call and its 3 last arguments +// +// Then and opcode evaluation is: +// op.eval(a) ------------------------> fn(a,b,c,d) +// internally calls +// +// The idea is that complex policy rules can be split into streams of +// opcodes which are evaluated in sequence. The evaluation is done in +// groups of opcodes that have N comparison opcodes plus 1 action opcode: +// +// [comparison 1][comparison 2]...[comparison N][action][comparison 1]... +// ----- evaluation order-----------> +// +// Each opcode group encodes one high-level policy rule. The rule applies +// only if all the conditions on the group evaluate to true. The action +// opcode contains the policy outcome for that particular rule. +// +// Note that this header contains the main building blocks of low-level policy +// but not the low level policy class. +namespace sandbox { + +// These are the possible policy outcomes. Note that some of them might +// not apply and can be removed. Also note that The following values only +// specify what to do, not how to do it and it is acceptable given specific +// cases to ignore the policy outcome. +enum EvalResult { + // Comparison opcode values: + EVAL_TRUE, // Opcode condition evaluated true. + EVAL_FALSE, // Opcode condition evaluated false. + EVAL_ERROR, // Opcode condition generated an error while evaluating. + // Action opcode values: + ASK_BROKER, // The target must generate an IPC to the broker. On the broker + // side, this means grant access to the resource. + DENY_ACCESS, // No access granted to the resource. + GIVE_READONLY, // Give readonly access to the resource. + GIVE_ALLACCESS, // Give full access to the resource. + GIVE_CACHED, // IPC is not required. Target can return a cached handle. + GIVE_FIRST, // TODO(cpu) + SIGNAL_ALARM, // Unusual activity. Generate an alarm. + FAKE_SUCCESS, // Do not call original function. Just return 'success'. + FAKE_ACCESS_DENIED, // Do not call original function. Just return 'denied' + // and do not do IPC. + TERMINATE_PROCESS, // Destroy target process. Do IPC as well. +}; + +// The following are the implemented opcodes. +enum OpcodeID { + OP_ALWAYS_FALSE, // Evaluates to false (EVAL_FALSE). + OP_ALWAYS_TRUE, // Evaluates to true (EVAL_TRUE). + OP_NUMBER_MATCH, // Match a 32-bit integer as n == a. + OP_NUMBER_MATCH_RANGE, // Match a 32-bit integer as a <= n <= b. + OP_NUMBER_AND_MATCH, // Match using bitwise AND; as in: n & a != 0. + OP_WSTRING_MATCH, // Match a string for equality. + OP_ACTION // Evaluates to an action opcode. +}; + +// Options that apply to every opcode. They are specified when creating +// each opcode using OpcodeFactory::MakeOpXXXXX() family of functions +// Do nothing special. +const uint32_t kPolNone = 0; + +// Convert EVAL_TRUE into EVAL_FALSE and vice-versa. This allows to express +// negated conditions such as if ( a && !b). +const uint32_t kPolNegateEval = 1; + +// Zero the MatchContext context structure. This happens after the opcode +// is evaluated. +const uint32_t kPolClearContext = 2; + +// Use OR when evaluating this set of opcodes. The policy evaluator by default +// uses AND when evaluating. Very helpful when +// used with kPolNegateEval. For example if you have a condition best expressed +// as if(! (a && b && c)), the use of this flags allows it to be expressed as +// if ((!a) || (!b) || (!c)). +const uint32_t kPolUseOREval = 4; + +// Keeps the evaluation state between opcode evaluations. This is used +// for string matching where the next opcode needs to continue matching +// from the last character position from the current opcode. The match +// context is preserved across opcode evaluation unless an opcode specifies +// as an option kPolClearContext. +struct MatchContext { + size_t position; + uint32_t options; + + MatchContext() { Clear(); } + + void Clear() { + position = 0; + options = 0; + } +}; + +// Models a policy opcode; that is a condition evaluation were all the +// arguments but one are stored in objects of this class. Use OpcodeFactory +// to create objects of this type. +// This class is just an implementation artifact and not exposed to the +// API clients or visible in the intercepted service. Internally, an +// opcode is just: +// - An integer that identifies the actual opcode. +// - An index to indicate which one is the input argument +// - An array of arguments. +// While an OO hierarchy of objects would have been a natural choice, the fact +// that 1) this code can execute before the CRT is loaded, presents serious +// problems in terms of guarantees about the actual state of the vtables and +// 2) because the opcode objects are generated in the broker process, we need to +// use plain objects. To preserve some minimal type safety templates are used +// when possible. +class PolicyOpcode { + friend class OpcodeFactory; + + public: + // Evaluates the opcode. For a typical comparison opcode the return value + // is EVAL_TRUE or EVAL_FALSE. If there was an error in the evaluation the + // the return is EVAL_ERROR. If the opcode is an action opcode then the + // return can take other values such as ASK_BROKER. + // parameters: An array of all input parameters. This argument is normally + // created by the macros POLPARAMS_BEGIN() POLPARAMS_END. + // count: The number of parameters passed as first argument. + // match: The match context that is persisted across the opcode evaluation + // sequence. + EvalResult Evaluate(const ParameterSet* parameters, + size_t count, + MatchContext* match); + + // Retrieves a stored argument by index. Valid index values are + // from 0 to < kArgumentCount. + template <typename T> + void GetArgument(size_t index, T* argument) const { + static_assert(sizeof(T) <= sizeof(arguments_[0]), "invalid size"); + *argument = *reinterpret_cast<const T*>(&arguments_[index].mem); + } + + // Sets a stored argument by index. Valid index values are + // from 0 to < kArgumentCount. + template <typename T> + void SetArgument(size_t index, const T& argument) { + static_assert(sizeof(T) <= sizeof(arguments_[0]), "invalid size"); + *reinterpret_cast<T*>(&arguments_[index].mem) = argument; + } + + // Retrieves the actual address of a string argument. When using + // GetArgument() to retrieve an index that contains a string, the returned + // value is just an offset to the actual string. + // index: the stored string index. Valid values are from 0 + // to < kArgumentCount. + const wchar_t* GetRelativeString(size_t index) const { + ptrdiff_t str_delta = 0; + GetArgument(index, &str_delta); + const char* delta = reinterpret_cast<const char*>(this) + str_delta; + return reinterpret_cast<const wchar_t*>(delta); + } + + // Returns true if this opcode is an action opcode without actually + // evaluating it. Used to do a quick scan forward to the next opcode group. + bool IsAction() const { return (OP_ACTION == opcode_id_); } + + // Returns the opcode type. + OpcodeID GetID() const { return opcode_id_; } + + // Returns the stored options such as kPolNegateEval and others. + uint32_t GetOptions() const { return options_; } + + // Sets the stored options such as kPolNegateEval. + void SetOptions(uint32_t options) { options_ = options; } + + // Returns the parameter of the function the opcode concerns. + uint16_t GetParameter() const { return parameter_; } + + private: + static const size_t kArgumentCount = 4; // The number of supported argument. + + struct OpcodeArgument { + UINT_PTR mem; + }; + + // Better define placement new in the class instead of relying on the + // global definition which seems to be fubared. + void* operator new(size_t, void* location) { return location; } + + // Helper function to evaluate the opcode. The parameters have the same + // meaning that in Evaluate(). + EvalResult EvaluateHelper(const ParameterSet* parameters, + MatchContext* match); + OpcodeID opcode_id_; + int16_t parameter_; + uint32_t options_; + OpcodeArgument arguments_[PolicyOpcode::kArgumentCount]; +}; + +enum StringMatchOptions { + CASE_SENSITIVE = 0, // Pay or Not attention to the case as defined by + CASE_INSENSITIVE = 1, // RtlCompareUnicodeString windows API. + EXACT_LENGTH = 2 // Don't do substring match. Do full string match. +}; + +// Opcodes that do string comparisons take a parameter that is the starting +// position to perform the comparison so we can do substring matching. There +// are two special values: +// +// Start from the current position and compare strings advancing forward until +// a match is found if any. Similar to CRT strstr(). +const int kSeekForward = -1; +// Perform a match with the end of the string. It only does a single comparison. +const int kSeekToEnd = 0xfffff; + +// A PolicyBuffer is a variable size structure that contains all the opcodes +// that are to be created or evaluated in sequence. +struct PolicyBuffer { + size_t opcode_count; + PolicyOpcode opcodes[1]; +}; + +// Helper class to create any opcode sequence. This class is normally invoked +// only by the high level policy module or when you need to handcraft a special +// policy. +// The factory works by creating the opcodes using a chunk of memory given +// in the constructor. The opcodes themselves are allocated from the beginning +// (top) of the memory, while any string that an opcode needs is allocated from +// the end (bottom) of the memory. +// +// In essence: +// +// low address ---> [opcode 1] +// [opcode 2] +// [opcode 3] +// | | <--- memory_top_ +// | free | +// | | +// | | <--- memory_bottom_ +// [string 1] +// high address --> [string 2] +// +// Note that this class does not keep track of the number of opcodes made and +// it is designed to be a building block for low-level policy. +// +// Note that any of the MakeOpXXXXX member functions below can return nullptr on +// failure. When that happens opcode sequence creation must be aborted. +class OpcodeFactory { + public: + // memory: base pointer to a chunk of memory where the opcodes are created. + // memory_size: the size in bytes of the memory chunk. + OpcodeFactory(char* memory, size_t memory_size) : memory_top_(memory) { + memory_bottom_ = &memory_top_[memory_size]; + } + + // policy: contains the raw memory where the opcodes are created. + // memory_size: contains the actual size of the policy argument. + OpcodeFactory(PolicyBuffer* policy, size_t memory_size) { + memory_top_ = reinterpret_cast<char*>(&policy->opcodes[0]); + memory_bottom_ = &memory_top_[memory_size]; + } + + // Returns the available memory to make opcodes. + size_t memory_size() const { + DCHECK_GE(memory_bottom_, memory_top_); + return memory_bottom_ - memory_top_; + } + + // Creates an OpAlwaysFalse opcode. + PolicyOpcode* MakeOpAlwaysFalse(uint32_t options); + + // Creates an OpAlwaysFalse opcode. + PolicyOpcode* MakeOpAlwaysTrue(uint32_t options); + + // Creates an OpAction opcode. + // action: The action to return when Evaluate() is called. + PolicyOpcode* MakeOpAction(EvalResult action, uint32_t options); + + // Creates an OpNumberMatch opcode. + // selected_param: index of the input argument. It must be a uint32_t or the + // evaluation result will generate a EVAL_ERROR. + // match: the number to compare against the selected_param. + PolicyOpcode* MakeOpNumberMatch(int16_t selected_param, + uint32_t match, + uint32_t options); + + // Creates an OpNumberMatch opcode (void pointers are cast to numbers). + // selected_param: index of the input argument. It must be an void* or the + // evaluation result will generate a EVAL_ERROR. + // match: the pointer numeric value to compare against selected_param. + PolicyOpcode* MakeOpVoidPtrMatch(int16_t selected_param, + const void* match, + uint32_t options); + + // Creates an OpNumberMatchRange opcode using the memory passed in the ctor. + // selected_param: index of the input argument. It must be a uint32_t or the + // evaluation result will generate a EVAL_ERROR. + // lower_bound, upper_bound: the range to compare against selected_param. + PolicyOpcode* MakeOpNumberMatchRange(int16_t selected_param, + uint32_t lower_bound, + uint32_t upper_bound, + uint32_t options); + + // Creates an OpWStringMatch opcode using the raw memory passed in the ctor. + // selected_param: index of the input argument. It must be a wide string + // pointer or the evaluation result will generate a EVAL_ERROR. + // match_str: string to compare against selected_param. + // start_position: when its value is from 0 to < 0x7fff it indicates an + // offset from the selected_param string where to perform the comparison. If + // the value is SeekForward then a substring search is performed. If the + // value is SeekToEnd the comparison is performed against the last part of + // the selected_param string. + // Note that the range in the position (0 to 0x7fff) is dictated by the + // current implementation. + // match_opts: Indicates additional matching flags. Currently CaseInsensitive + // is supported. + PolicyOpcode* MakeOpWStringMatch(int16_t selected_param, + const wchar_t* match_str, + int start_position, + StringMatchOptions match_opts, + uint32_t options); + + // Creates an OpNumberAndMatch opcode using the raw memory passed in the ctor. + // selected_param: index of the input argument. It must be uint32_t or the + // evaluation result will generate a EVAL_ERROR. + // match: the value to bitwise AND against selected_param. + PolicyOpcode* MakeOpNumberAndMatch(int16_t selected_param, + uint32_t match, + uint32_t options); + + private: + // Constructs the common part of every opcode. selected_param is the index + // of the input param to use when evaluating the opcode. Pass -1 in + // selected_param to indicate that no input parameter is required. + PolicyOpcode* MakeBase(OpcodeID opcode_id, + uint32_t options, + int16_t selected_param); + + // Allocates (and copies) a string (of size length) inside the buffer and + // returns the displacement with respect to start. + ptrdiff_t AllocRelative(void* start, const wchar_t* str, size_t length); + + // Points to the lowest currently available address of the memory + // used to make the opcodes. This pointer increments as opcodes are made. + char* memory_top_; + + // Points to the highest currently available address of the memory + // used to make the opcodes. This pointer decrements as opcode strings are + // allocated. + char* memory_bottom_; + + DISALLOW_COPY_AND_ASSIGN(OpcodeFactory); +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_POLICY_ENGINE_OPCODES_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_engine_params.h b/security/sandbox/chromium/sandbox/win/src/policy_engine_params.h new file mode 100644 index 0000000000..07fd7eac82 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_engine_params.h @@ -0,0 +1,190 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_SRC_POLICY_ENGINE_PARAMS_H__ +#define SANDBOX_SRC_POLICY_ENGINE_PARAMS_H__ + +#include <stdint.h> + +#include "sandbox/win/src/internal_types.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +// This header defines the classes that allow the low level policy to select +// the input parameters. In order to better make sense of this header is +// recommended that you check policy_engine_opcodes.h first. + +namespace sandbox { + +// Models the set of interesting parameters of an intercepted system call +// normally you don't create objects of this class directly, instead you +// use the POLPARAMS_XXX macros. +// For example, if an intercepted function has the following signature: +// +// NTSTATUS NtOpenFileFunction (PHANDLE FileHandle, +// ACCESS_MASK DesiredAccess, +// POBJECT_ATTRIBUTES ObjectAttributes, +// PIO_STATUS_BLOCK IoStatusBlock, +// ULONG ShareAccess, +// ULONG OpenOptions); +// +// You could say that the following parameters are of interest to policy: +// +// POLPARAMS_BEGIN(open_params) +// POLPARAM(DESIRED_ACCESS) +// POLPARAM(OBJECT_NAME) +// POLPARAM(SECURITY_DESCRIPTOR) +// POLPARAM(IO_STATUS) +// POLPARAM(OPEN_OPTIONS) +// POLPARAMS_END; +// +// and the actual code will use this for defining the parameters: +// +// CountedParameterSet<open_params> p; +// p[open_params::DESIRED_ACCESS] = ParamPickerMake(DesiredAccess); +// p[open_params::OBJECT_NAME] = +// ParamPickerMake(ObjectAttributes->ObjectName); +// p[open_params::SECURITY_DESCRIPTOR] = +// ParamPickerMake(ObjectAttributes->SecurityDescriptor); +// p[open_params::IO_STATUS] = ParamPickerMake(IoStatusBlock); +// p[open_params::OPEN_OPTIONS] = ParamPickerMake(OpenOptions); +// +// These will create an stack-allocated array of ParameterSet objects which +// have each 1) the address of the parameter 2) a numeric id that encodes the +// original C++ type. This allows the policy to treat any set of supported +// argument types uniformily and with some type safety. +// +// TODO(cpu): support not fully implemented yet for unicode string and will +// probably add other types as well. +class ParameterSet { + public: + ParameterSet() : real_type_(INVALID_TYPE), address_(nullptr) {} + + // Retrieve the stored parameter. If the type does not match ulong fail. + bool Get(uint32_t* destination) const { + if (real_type_ != UINT32_TYPE) { + return false; + } + *destination = Void2TypePointerCopy<uint32_t>(); + return true; + } + + // Retrieve the stored parameter. If the type does not match void* fail. + bool Get(const void** destination) const { + if (real_type_ != VOIDPTR_TYPE) { + return false; + } + *destination = Void2TypePointerCopy<void*>(); + return true; + } + + // Retrieve the stored parameter. If the type does not match wchar_t* fail. + bool Get(const wchar_t** destination) const { + if (real_type_ != WCHAR_TYPE) { + return false; + } + *destination = Void2TypePointerCopy<const wchar_t*>(); + return true; + } + + // False if the parameter is not properly initialized. + bool IsValid() const { return real_type_ != INVALID_TYPE; } + + protected: + // The constructor can only be called by derived types, which should + // safely provide the real_type and the address of the argument. + ParameterSet(ArgType real_type, const void* address) + : real_type_(real_type), address_(address) {} + + private: + // This template provides the same functionality as bits_cast but + // it works with pointer while the former works only with references. + template <typename T> + T Void2TypePointerCopy() const { + return *(reinterpret_cast<const T*>(address_)); + } + + ArgType real_type_; + const void* address_; +}; + +// To safely infer the type, we use a set of template specializations +// in ParameterSetEx with a template function ParamPickerMake to do the +// parameter type deduction. + +// Base template class. Not implemented so using unsupported types should +// fail to compile. +template <typename T> +class ParameterSetEx : public ParameterSet { + public: + ParameterSetEx(const void* address); +}; + +template <> +class ParameterSetEx<void const*> : public ParameterSet { + public: + ParameterSetEx(const void* address) : ParameterSet(VOIDPTR_TYPE, address) {} +}; + +template <> +class ParameterSetEx<void*> : public ParameterSet { + public: + ParameterSetEx(const void* address) : ParameterSet(VOIDPTR_TYPE, address) {} +}; + +template <> +class ParameterSetEx<wchar_t*> : public ParameterSet { + public: + ParameterSetEx(const void* address) : ParameterSet(WCHAR_TYPE, address) {} +}; + +template <> +class ParameterSetEx<wchar_t const*> : public ParameterSet { + public: + ParameterSetEx(const void* address) : ParameterSet(WCHAR_TYPE, address) {} +}; + +template <> +class ParameterSetEx<uint32_t> : public ParameterSet { + public: + ParameterSetEx(const void* address) : ParameterSet(UINT32_TYPE, address) {} +}; + +template <> +class ParameterSetEx<UNICODE_STRING> : public ParameterSet { + public: + ParameterSetEx(const void* address) : ParameterSet(UNISTR_TYPE, address) {} +}; + +template <typename T> +ParameterSet ParamPickerMake(T& parameter) { + return ParameterSetEx<T>(¶meter); +} + +struct CountedParameterSetBase { + size_t count; + ParameterSet parameters[1]; +}; + +// This template defines the actual list of policy parameters for a given +// interception. +// Warning: This template stores the address to the actual variables, in +// other words, the values are not copied. +template <typename T> +struct CountedParameterSet { + CountedParameterSet() : count(T::PolParamLast) {} + + ParameterSet& operator[](typename T::Args n) { return parameters[n]; } + + CountedParameterSetBase* GetBase() { + return reinterpret_cast<CountedParameterSetBase*>(this); + } + + size_t count; + ParameterSet parameters[T::PolParamLast]; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_ENGINE_PARAMS_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_engine_processor.cc b/security/sandbox/chromium/sandbox/win/src/policy_engine_processor.cc new file mode 100644 index 0000000000..07969f31a8 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_engine_processor.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2006-2008 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/win/src/policy_engine_processor.h" + +#include <stddef.h> +#include <stdint.h> + +namespace sandbox { + +void PolicyProcessor::SetInternalState(size_t index, EvalResult result) { + state_.current_index_ = index; + state_.current_result_ = result; +} + +EvalResult PolicyProcessor::GetAction() const { + return state_.current_result_; +} + +// Decides if an opcode can be skipped (not evaluated) or not. The function +// takes as inputs the opcode and the current evaluation context and returns +// true if the opcode should be skipped or not and also can set keep_skipping +// to false to signal that the current instruction should be skipped but not +// the next after the current one. +bool SkipOpcode(const PolicyOpcode& opcode, + MatchContext* context, + bool* keep_skipping) { + if (opcode.IsAction()) { + uint32_t options = context->options; + context->Clear(); + *keep_skipping = false; + return (kPolUseOREval != options); + } + *keep_skipping = true; + return true; +} + +PolicyResult PolicyProcessor::Evaluate(uint32_t options, + ParameterSet* parameters, + size_t param_count) { + if (!policy_) + return NO_POLICY_MATCH; + if (0 == policy_->opcode_count) + return NO_POLICY_MATCH; + if (!(kShortEval & options)) + return POLICY_ERROR; + + MatchContext context; + bool evaluation = false; + bool skip_group = false; + SetInternalState(0, EVAL_FALSE); + size_t count = policy_->opcode_count; + + // Loop over all the opcodes Evaluating in sequence. Since we only support + // short circuit evaluation, we stop as soon as we find an 'action' opcode + // and the current evaluation is true. + // + // Skipping opcodes can happen when we are in AND mode (!kPolUseOREval) and + // have got EVAL_FALSE or when we are in OR mode (kPolUseOREval) and got + // EVAL_TRUE. Skipping will stop at the next action opcode or at the opcode + // after the action depending on kPolUseOREval. + + for (size_t ix = 0; ix != count; ++ix) { + PolicyOpcode& opcode = policy_->opcodes[ix]; + // Skipping block. + if (skip_group) { + if (SkipOpcode(opcode, &context, &skip_group)) + continue; + } + // Evaluation block. + EvalResult result = opcode.Evaluate(parameters, param_count, &context); + switch (result) { + case EVAL_FALSE: + evaluation = false; + if (kPolUseOREval != context.options) + skip_group = true; + break; + case EVAL_ERROR: + if (kStopOnErrors & options) + return POLICY_ERROR; + break; + case EVAL_TRUE: + evaluation = true; + if (kPolUseOREval == context.options) + skip_group = true; + break; + default: + // We have evaluated an action. + SetInternalState(ix, result); + return POLICY_MATCH; + } + } + + if (evaluation) { + // Reaching the end of the policy with a positive evaluation is probably + // an error: we did not find a final action opcode? + return POLICY_ERROR; + } + return NO_POLICY_MATCH; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_engine_processor.h b/security/sandbox/chromium/sandbox/win/src/policy_engine_processor.h new file mode 100644 index 0000000000..b62973b14a --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_engine_processor.h @@ -0,0 +1,143 @@ +// Copyright (c) 2010 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. + +#ifndef SANDBOX_SRC_POLICY_ENGINE_PROCESSOR_H__ +#define SANDBOX_SRC_POLICY_ENGINE_PROCESSOR_H__ + +#include <stddef.h> +#include <stdint.h> + +#include "base/macros.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_engine_params.h" + +namespace sandbox { + +// This header contains the core policy evaluator. In its simplest form +// it evaluates a stream of opcodes assuming that they are laid out in +// memory as opcode groups. +// +// An opcode group has N comparison opcodes plus 1 action opcode. For +// example here we have 3 opcode groups (A, B,C): +// +// [comparison 1] <-- group A start +// [comparison 2] +// [comparison 3] +// [action A ] +// [comparison 1] <-- group B start +// [action B ] +// [comparison 1] <-- group C start +// [comparison 2] +// [action C ] +// +// The opcode evaluator proceeds from the top, evaluating each opcode in +// sequence. An opcode group is evaluated until the first comparison that +// returns false. At that point the rest of the group is skipped and evaluation +// resumes with the first comparison of the next group. When all the comparisons +// in a group have evaluated to true and the action is reached. The group is +// considered a matching group. +// +// In the 'ShortEval' mode evaluation stops when it reaches the end or the first +// matching group. The action opcode from this group is the resulting policy +// action. +// +// In the 'RankedEval' mode evaluation stops only when it reaches the end of the +// the opcode stream. In the process all matching groups are saved and at the +// end the 'best' group is selected (what makes the best is TBD) and the action +// from this group is the resulting policy action. +// +// As explained above, the policy evaluation of a group is a logical AND of +// the evaluation of each opcode. However an opcode can request kPolUseOREval +// which makes the evaluation to use logical OR. Given that each opcode can +// request its evaluation result to be negated with kPolNegateEval you can +// achieve the negation of the total group evaluation. This means that if you +// need to express: +// if (!(c1 && c2 && c3)) +// You can do it by: +// if ((!c1) || (!c2) || (!c3)) +// + +// Possible outcomes of policy evaluation. +enum PolicyResult { NO_POLICY_MATCH, POLICY_MATCH, POLICY_ERROR }; + +// Policy evaluation flags +// TODO(cpu): implement the options kStopOnErrors & kRankedEval. +// +// Stop evaluating as soon as an error is encountered. +const uint32_t kStopOnErrors = 1; +// Ignore all non fatal opcode evaluation errors. +const uint32_t kIgnoreErrors = 2; +// Short-circuit evaluation: Only evaluate until opcode group that +// evaluated to true has been found. +const uint32_t kShortEval = 4; +// Discussed briefly at the policy design meeting. It will evaluate +// all rules and then return the 'best' rule that evaluated true. +const uint32_t kRankedEval = 8; + +// This class evaluates a policy-opcode stream given the memory where the +// opcodes are and an input 'parameter set'. +// +// This class is designed to be callable from interception points +// as low as the NtXXXX service level (it is not currently safe, but +// it is designed to be made safe). +// +// Its usage in an interception is: +// +// POLPARAMS_BEGIN(eval_params) +// POLPARAM(param1) +// POLPARAM(param2) +// POLPARAM(param3) +// POLPARAM(param4) +// POLPARAM(param5) +// POLPARAMS_END; +// +// PolicyProcessor pol_evaluator(policy_memory); +// PolicyResult pr = pol_evaluator.Evaluate(ShortEval, eval_params, +// _countof(eval_params)); +// if (NO_POLICY_MATCH == pr) { +// EvalResult policy_action = pol_evaluator.GetAction(); +// // apply policy here... +// } +// +// Where the POLPARAM() arguments are derived from the intercepted function +// arguments, and represent all the 'interesting' policy inputs, and +// policy_memory is a memory buffer containing the opcode stream that is the +// relevant policy for this intercept. +class PolicyProcessor { + public: + // policy_buffer contains opcodes made with OpcodeFactory. They are usually + // created in the broker process and evaluated in the target process. + + // This constructor is just a variant of the previous constructor. + explicit PolicyProcessor(PolicyBuffer* policy) : policy_(policy) { + SetInternalState(0, EVAL_FALSE); + } + + // Evaluates a policy-opcode stream. See the comments at the top of this + // class for more info. Returns POLICY_MATCH if a rule set was found that + // matches an active policy. + PolicyResult Evaluate(uint32_t options, + ParameterSet* parameters, + size_t parameter_count); + + // If the result of Evaluate() was POLICY_MATCH, calling this function returns + // the recommended policy action. + EvalResult GetAction() const; + + private: + struct { + size_t current_index_; + EvalResult current_result_; + } state_; + + // Sets the currently matching action result. + void SetInternalState(size_t index, EvalResult result); + + PolicyBuffer* policy_; + DISALLOW_COPY_AND_ASSIGN(PolicyProcessor); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_ENGINE_PROCESSOR_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_engine_unittest.cc b/security/sandbox/chromium/sandbox/win/src/policy_engine_unittest.cc new file mode 100644 index 0000000000..b8b2002108 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_engine_unittest.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2006-2008 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 <stddef.h> +#include <stdint.h> + +#include "sandbox/win/src/policy_engine_params.h" +#include "sandbox/win/src/policy_engine_processor.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define POLPARAMS_BEGIN(x) sandbox::ParameterSet x[] = { +#define POLPARAM(p) sandbox::ParamPickerMake(p), +#define POLPARAMS_END } + +namespace sandbox { + +bool SetupNtdllImports(); + +TEST(PolicyEngineTest, Rules1) { + SetupNtdllImports(); + + // Construct two policy rules that say: + // + // #1 + // If the path is c:\\documents and settings\\* AND + // If the creation mode is 'open existing' AND + // If the security descriptor is null THEN + // Ask the broker. + // + // #2 + // If the security descriptor is null AND + // If the path ends with *.txt AND + // If the creation mode is not 'create new' THEN + // return Access Denied. + + enum FileCreateArgs { + FileNameArg, + CreationDispositionArg, + FlagsAndAttributesArg, + SecurityAttributes + }; + + const size_t policy_sz = 1024; + PolicyBuffer* policy = reinterpret_cast<PolicyBuffer*>(new char[policy_sz]); + OpcodeFactory opcode_maker(policy, policy_sz - 0x40); + + // Add rule set #1 + opcode_maker.MakeOpWStringMatch(FileNameArg, L"c:\\documents and settings\\", + 0, CASE_INSENSITIVE, kPolNone); + opcode_maker.MakeOpNumberMatch(CreationDispositionArg, OPEN_EXISTING, + kPolNone); + opcode_maker.MakeOpVoidPtrMatch(SecurityAttributes, nullptr, kPolNone); + opcode_maker.MakeOpAction(ASK_BROKER, kPolNone); + + // Add rule set #2 + opcode_maker.MakeOpWStringMatch(FileNameArg, L".TXT", kSeekToEnd, + CASE_INSENSITIVE, kPolNone); + opcode_maker.MakeOpNumberMatch(CreationDispositionArg, CREATE_NEW, + kPolNegateEval); + opcode_maker.MakeOpAction(FAKE_ACCESS_DENIED, kPolNone); + policy->opcode_count = 7; + + const wchar_t* filename = L"c:\\Documents and Settings\\Microsoft\\BLAH.txt"; + uint32_t creation_mode = OPEN_EXISTING; + uint32_t flags = FILE_ATTRIBUTE_NORMAL; + void* security_descriptor = nullptr; + + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) + POLPARAM(creation_mode) + POLPARAM(flags) + POLPARAM(security_descriptor) + POLPARAMS_END; + + PolicyResult pr; + PolicyProcessor pol_ev(policy); + + // Test should match the first rule set. + pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, pr); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + // Test should still match the first rule set. + pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, pr); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + // Changing creation_mode such that evaluation should not match any rule. + creation_mode = CREATE_NEW; + pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, pr); + + // Changing creation_mode such that evaluation should match rule #2. + creation_mode = OPEN_ALWAYS; + pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, pr); + EXPECT_EQ(FAKE_ACCESS_DENIED, pol_ev.GetAction()); + + delete[] reinterpret_cast<char*>(policy); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_low_level.cc b/security/sandbox/chromium/sandbox/win/src/policy_low_level.cc new file mode 100644 index 0000000000..d987211c8b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_low_level.cc @@ -0,0 +1,355 @@ +// Copyright (c) 2006-2008 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/win/src/policy_low_level.h" + +#include <stddef.h> +#include <stdint.h> + +#include <map> +#include <string> + +namespace { + +// A single rule can use at most this amount of memory. +const size_t kRuleBufferSize = 1024 * 4; + +// The possible states of the string matching opcode generator. +enum { + PENDING_NONE, + PENDING_ASTERISK, // Have seen an '*' but have not generated an opcode. + PENDING_QMARK, // Have seen an '?' but have not generated an opcode. +}; + +// The category of the last character seen by the string matching opcode +// generator. +const uint32_t kLastCharIsNone = 0; +const uint32_t kLastCharIsAlpha = 1; +const uint32_t kLastCharIsWild = 2; +const uint32_t kLastCharIsAsterisk = kLastCharIsWild + 4; +const uint32_t kLastCharIsQuestionM = kLastCharIsWild + 8; + +} // namespace + +namespace sandbox { + +LowLevelPolicy::LowLevelPolicy(PolicyGlobal* policy_store) + : policy_store_(policy_store) {} + +// Adding a rule is nothing more than pushing it into an stl container. Done() +// is called for the rule in case the code that made the rule in the first +// place has not done it. +bool LowLevelPolicy::AddRule(IpcTag service, PolicyRule* rule) { + if (!rule->Done()) { + return false; + } + + PolicyRule* local_rule = new PolicyRule(*rule); + RuleNode node = {local_rule, service}; + rules_.push_back(node); + return true; +} + +LowLevelPolicy::~LowLevelPolicy() { + // Delete all the rules. + typedef std::list<RuleNode> RuleNodes; + for (RuleNodes::iterator it = rules_.begin(); it != rules_.end(); ++it) { + delete it->rule; + } +} + +// Here is where the heavy byte shuffling is done. We take all the rules and +// 'compile' them into a single memory region. Now, the rules are in random +// order so the first step is to reorganize them into a stl map that is keyed +// by the service id and as a value contains a list with all the rules that +// belong to that service. Then we enter the big for-loop where we carve a +// memory zone for the opcodes and the data and call RebindCopy on each rule +// so they all end up nicely packed in the policy_store_. +bool LowLevelPolicy::Done() { + typedef std::list<RuleNode> RuleNodes; + typedef std::list<const PolicyRule*> RuleList; + typedef std::map<IpcTag, RuleList> Mmap; + Mmap mmap; + + for (RuleNodes::iterator it = rules_.begin(); it != rules_.end(); ++it) { + mmap[it->service].push_back(it->rule); + } + + PolicyBuffer* current_buffer = &policy_store_->data[0]; + char* buffer_end = + reinterpret_cast<char*>(current_buffer) + policy_store_->data_size; + size_t avail_size = policy_store_->data_size; + + for (Mmap::iterator it = mmap.begin(); it != mmap.end(); ++it) { + IpcTag service = (*it).first; + if (static_cast<size_t>(service) >= kMaxServiceCount) { + return false; + } + policy_store_->entry[static_cast<size_t>(service)] = current_buffer; + + RuleList::iterator rules_it = (*it).second.begin(); + RuleList::iterator rules_it_end = (*it).second.end(); + + size_t svc_opcode_count = 0; + + for (; rules_it != rules_it_end; ++rules_it) { + const PolicyRule* rule = (*rules_it); + size_t op_count = rule->GetOpcodeCount(); + + size_t opcodes_size = op_count * sizeof(PolicyOpcode); + if (avail_size < opcodes_size) { + return false; + } + size_t data_size = avail_size - opcodes_size; + PolicyOpcode* opcodes_start = ¤t_buffer->opcodes[svc_opcode_count]; + if (!rule->RebindCopy(opcodes_start, opcodes_size, buffer_end, + &data_size)) { + return false; + } + size_t used = avail_size - data_size; + buffer_end -= used; + avail_size -= used; + svc_opcode_count += op_count; + } + + current_buffer->opcode_count = svc_opcode_count; + size_t policy_buffers_occupied = + (svc_opcode_count * sizeof(PolicyOpcode)) / sizeof(current_buffer[0]); + current_buffer = ¤t_buffer[policy_buffers_occupied + 1]; + } + + return true; +} + +PolicyRule::PolicyRule(EvalResult action) : action_(action), done_(false) { + char* memory = new char[sizeof(PolicyBuffer) + kRuleBufferSize]; + buffer_ = reinterpret_cast<PolicyBuffer*>(memory); + buffer_->opcode_count = 0; + opcode_factory_ = + new OpcodeFactory(buffer_, kRuleBufferSize + sizeof(PolicyOpcode)); +} + +PolicyRule::PolicyRule(const PolicyRule& other) { + if (this == &other) + return; + action_ = other.action_; + done_ = other.done_; + size_t buffer_size = sizeof(PolicyBuffer) + kRuleBufferSize; + char* memory = new char[buffer_size]; + buffer_ = reinterpret_cast<PolicyBuffer*>(memory); + memcpy(buffer_, other.buffer_, buffer_size); + + char* opcode_buffer = reinterpret_cast<char*>(&buffer_->opcodes[0]); + char* next_opcode = &opcode_buffer[GetOpcodeCount() * sizeof(PolicyOpcode)]; + opcode_factory_ = + new OpcodeFactory(next_opcode, other.opcode_factory_->memory_size()); +} + +// This function get called from a simple state machine implemented in +// AddStringMatch() which passes the current state (in state) and it passes +// true in last_call if AddStringMatch() has finished processing the input +// pattern string and this would be the last call to generate any pending +// opcode. The skip_count is the currently accumulated number of '?' seen so +// far and once the associated opcode is generated this function sets it back +// to zero. +bool PolicyRule::GenStringOpcode(RuleType rule_type, + StringMatchOptions match_opts, + uint16_t parameter, + int state, + bool last_call, + int* skip_count, + std::wstring* fragment) { + // The last opcode must: + // 1) Always clear the context. + // 2) Preserve the negation. + // 3) Remove the 'OR' mode flag. + uint32_t options = kPolNone; + if (last_call) { + if (IF_NOT == rule_type) { + options = kPolClearContext | kPolNegateEval; + } else { + options = kPolClearContext; + } + } else if (IF_NOT == rule_type) { + options = kPolUseOREval | kPolNegateEval; + } + + PolicyOpcode* op = nullptr; + + // The fragment string contains the accumulated characters to match with, it + // never contains wildcards (unless they have been escaped) and while there + // is no fragment there is no new string match opcode to generate. + if (fragment->empty()) { + // There is no new opcode to generate but in the last call we have to fix + // the previous opcode because it was really the last but we did not know + // it at that time. + if (last_call && (buffer_->opcode_count > 0)) { + op = &buffer_->opcodes[buffer_->opcode_count - 1]; + op->SetOptions(options); + } + return true; + } + + if (PENDING_ASTERISK == state) { + if (last_call) { + op = opcode_factory_->MakeOpWStringMatch(parameter, fragment->c_str(), + kSeekToEnd, match_opts, options); + } else { + op = opcode_factory_->MakeOpWStringMatch( + parameter, fragment->c_str(), kSeekForward, match_opts, options); + } + + } else if (PENDING_QMARK == state) { + op = opcode_factory_->MakeOpWStringMatch(parameter, fragment->c_str(), + *skip_count, match_opts, options); + *skip_count = 0; + } else { + if (last_call) { + match_opts = static_cast<StringMatchOptions>(EXACT_LENGTH | match_opts); + } + op = opcode_factory_->MakeOpWStringMatch(parameter, fragment->c_str(), 0, + match_opts, options); + } + if (!op) + return false; + ++buffer_->opcode_count; + fragment->clear(); + return true; +} + +bool PolicyRule::AddStringMatch(RuleType rule_type, + int16_t parameter, + const wchar_t* string, + StringMatchOptions match_opts) { + if (done_) { + // Do not allow to add more rules after generating the action opcode. + return false; + } + + const wchar_t* current_char = string; + uint32_t last_char = kLastCharIsNone; + int state = PENDING_NONE; + int skip_count = 0; // counts how many '?' we have seen in a row. + std::wstring fragment; // accumulates the non-wildcard part. + + while (L'\0' != *current_char) { + switch (*current_char) { + case L'*': + if (kLastCharIsWild & last_char) { + // '**' and '&*' is an error. + return false; + } + if (!GenStringOpcode(rule_type, match_opts, parameter, state, false, + &skip_count, &fragment)) { + return false; + } + last_char = kLastCharIsAsterisk; + state = PENDING_ASTERISK; + break; + case L'?': + if (kLastCharIsAsterisk == last_char) { + // '*?' is an error. + return false; + } + if (!GenStringOpcode(rule_type, match_opts, parameter, state, false, + &skip_count, &fragment)) { + return false; + } + ++skip_count; + last_char = kLastCharIsQuestionM; + state = PENDING_QMARK; + break; + case L'/': + // Note: "/?" is an escaped '?'. Eat the slash and fall through. + if (L'?' == current_char[1]) { + ++current_char; + } + FALLTHROUGH; + default: + fragment += *current_char; + last_char = kLastCharIsAlpha; + } + ++current_char; + } + + if (!GenStringOpcode(rule_type, match_opts, parameter, state, true, + &skip_count, &fragment)) { + return false; + } + return true; +} + +bool PolicyRule::AddNumberMatch(RuleType rule_type, + int16_t parameter, + uint32_t number, + RuleOp comparison_op) { + if (done_) { + // Do not allow to add more rules after generating the action opcode. + return false; + } + uint32_t opts = (rule_type == IF_NOT) ? kPolNegateEval : kPolNone; + + if (EQUAL == comparison_op) { + if (!opcode_factory_->MakeOpNumberMatch(parameter, number, opts)) + return false; + } else if (AND == comparison_op) { + if (!opcode_factory_->MakeOpNumberAndMatch(parameter, number, opts)) + return false; + } + ++buffer_->opcode_count; + return true; +} + +bool PolicyRule::Done() { + if (done_) { + return true; + } + if (!opcode_factory_->MakeOpAction(action_, kPolNone)) + return false; + ++buffer_->opcode_count; + done_ = true; + return true; +} + +bool PolicyRule::RebindCopy(PolicyOpcode* opcode_start, + size_t opcode_size, + char* data_start, + size_t* data_size) const { + size_t count = buffer_->opcode_count; + for (size_t ix = 0; ix != count; ++ix) { + if (opcode_size < sizeof(PolicyOpcode)) { + return false; + } + PolicyOpcode& opcode = buffer_->opcodes[ix]; + *opcode_start = opcode; + if (OP_WSTRING_MATCH == opcode.GetID()) { + // For this opcode argument 0 is a delta to the string and argument 1 + // is the length (in chars) of the string. + const wchar_t* str = opcode.GetRelativeString(0); + size_t str_len; + opcode.GetArgument(1, &str_len); + str_len = str_len * sizeof(wchar_t); + if ((*data_size) < str_len) { + return false; + } + *data_size -= str_len; + data_start -= str_len; + memcpy(data_start, str, str_len); + // Recompute the string displacement + ptrdiff_t delta = data_start - reinterpret_cast<char*>(opcode_start); + opcode_start->SetArgument(0, delta); + } + ++opcode_start; + opcode_size -= sizeof(PolicyOpcode); + } + + return true; +} + +PolicyRule::~PolicyRule() { + delete[] reinterpret_cast<char*>(buffer_); + delete opcode_factory_; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_low_level.h b/security/sandbox/chromium/sandbox/win/src/policy_low_level.h new file mode 100644 index 0000000000..1586f96af9 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_low_level.h @@ -0,0 +1,189 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_SRC_POLICY_LOW_LEVEL_H__ +#define SANDBOX_SRC_POLICY_LOW_LEVEL_H__ + +#include <stddef.h> +#include <stdint.h> + +#include <list> + +#include <string> + +#include "base/macros.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_engine_params.h" + +// Low level policy classes. +// Built on top of the PolicyOpcode and OpcodeFactory, the low level policy +// provides a way to define rules on strings and numbers but it is unaware +// of Windows specific details or how the Interceptions must be set up. +// To use these classes you construct one or more rules and add them to the +// LowLevelPolicy object like this: +// +// PolicyRule rule1(ASK_BROKER); +// rule1.AddStringMatch(IF, 0, L"\\\\/?/?\\c:\\*Microsoft*\\*.exe", true); +// rule1.AddNumberMatch(IF_NOT, 1, CREATE_ALWAYS, EQUAL); +// rule1.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL); +// +// PolicyRule rule2(FAKE_SUCCESS); +// rule2.AddStringMatch(IF, 0, L"\\\\/?/?\\Pipe\\Chrome.*", false)); +// rule2.AddNumberMatch(IF, 1, OPEN_EXISTING, EQUAL)); +// +// LowLevelPolicy policyGen(*policy_memory); +// policyGen.AddRule(kNtCreateFileSvc, &rule1); +// policyGen.AddRule(kNtCreateFileSvc, &rule2); +// policyGen.Done(); +// +// At this point (error checking omitted) the policy_memory can be copied +// to the target process where it can be evaluated. + +namespace sandbox { + +// Defines the memory layout of the policy. This memory is filled by +// LowLevelPolicy object. +// For example: +// +// [Service 0] --points to---\ +// [Service 1] --------------|-----\ +// ...... | | +// [Service N] | | +// [data_size] | | +// [Policy Buffer 0] <-------/ | +// [opcodes of] | +// ....... | +// [Policy Buffer 1] <-------------/ +// [opcodes] +// ....... +// ....... +// [Policy Buffer N] +// [opcodes] +// ....... +// <possibly unused space here> +// ....... +// [opcode string ] +// [opcode string ] +// ....... +// [opcode string ] +struct PolicyGlobal { + PolicyBuffer* entry[kMaxServiceCount]; + size_t data_size; + PolicyBuffer data[1]; +}; + +class PolicyRule; + +// Provides the means to collect rules into a policy store (memory) +class LowLevelPolicy { + public: + // policy_store: must contain allocated memory and the internal + // size fields set to correct values. + explicit LowLevelPolicy(PolicyGlobal* policy_store); + + // Destroys all the policy rules. + ~LowLevelPolicy(); + + // Adds a rule to be generated when Done() is called. + // service: The id of the service that this rule is associated with, + // for example the 'Open Thread' service or the "Create File" service. + // returns false on error. + bool AddRule(IpcTag service, PolicyRule* rule); + + // Generates all the rules added with AddRule() into the memory area + // passed on the constructor. Returns false on error. + bool Done(); + + private: + struct RuleNode { + const PolicyRule* rule; + IpcTag service; + }; + std::list<RuleNode> rules_; + PolicyGlobal* policy_store_; + DISALLOW_IMPLICIT_CONSTRUCTORS(LowLevelPolicy); +}; + +// There are 'if' rules and 'if not' comparisons +enum RuleType { + IF = 0, + IF_NOT = 1, +}; + +// Possible comparisons for numbers +enum RuleOp { + EQUAL, + AND, + RANGE // TODO(cpu): Implement this option. +}; + +// Provides the means to collect a set of comparisons into a single +// rule and its associated action. +class PolicyRule { + friend class LowLevelPolicy; + + public: + explicit PolicyRule(EvalResult action); + PolicyRule(const PolicyRule& other); + ~PolicyRule(); + + // Adds a string comparison to the rule. + // rule_type: possible values are IF and IF_NOT. + // parameter: the expected index of the argument for this rule. For example + // in a 'create file' service the file name argument can be at index 0. + // string: is the desired matching pattern. + // match_opts: if the pattern matching is case sensitive or not. + bool AddStringMatch(RuleType rule_type, + int16_t parameter, + const wchar_t* string, + StringMatchOptions match_opts); + + // Adds a number match comparison to the rule. + // rule_type: possible values are IF and IF_NOT. + // parameter: the expected index of the argument for this rule. + // number: the value to compare the input to. + // comparison_op: the comparison kind (equal, logical and, etc). + bool AddNumberMatch(RuleType rule_type, + int16_t parameter, + uint32_t number, + RuleOp comparison_op); + + // Returns the number of opcodes generated so far. + size_t GetOpcodeCount() const { return buffer_->opcode_count; } + + // Called when there is no more comparisons to add. Internally it generates + // the last opcode (the action opcode). Returns false if this operation fails. + bool Done(); + + private: + void operator=(const PolicyRule&); + // Called in a loop from AddStringMatch to generate the required string + // match opcodes. rule_type, match_opts and parameter are the same as + // in AddStringMatch. + bool GenStringOpcode(RuleType rule_type, + StringMatchOptions match_opts, + uint16_t parameter, + int state, + bool last_call, + int* skip_count, + std::wstring* fragment); + + // Loop over all generated opcodes and copy them to increasing memory + // addresses from opcode_start and copy the extra data (strings usually) into + // decreasing addresses from data_start. Extra data is only present in the + // string evaluation opcodes. + bool RebindCopy(PolicyOpcode* opcode_start, + size_t opcode_size, + char* data_start, + size_t* data_size) const; + PolicyBuffer* buffer_; + OpcodeFactory* opcode_factory_; + EvalResult action_; + bool done_; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_LOW_LEVEL_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_low_level_unittest.cc b/security/sandbox/chromium/sandbox/win/src/policy_low_level_unittest.cc new file mode 100644 index 0000000000..8484660096 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_low_level_unittest.cc @@ -0,0 +1,684 @@ +// Copyright (c) 2006-2008 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/win/src/policy_low_level.h" + +#include <stddef.h> +#include <stdint.h> + +#include "sandbox/win/src/policy_engine_params.h" +#include "sandbox/win/src/policy_engine_processor.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define POLPARAMS_BEGIN(x) sandbox::ParameterSet x[] = { +#define POLPARAM(p) sandbox::ParamPickerMake(p), +#define POLPARAMS_END } + +namespace sandbox { + +bool SetupNtdllImports(); + +// Testing that we allow opcode generation on valid string patterns. +TEST(PolicyEngineTest, StringPatternsOK) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE(pr.AddStringMatch(IF, 0, L"c:\\adobe\\ver??\\", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddStringMatch(IF, 0, L"*.tmp", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddStringMatch(IF, 0, L"c:\\*.doc", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddStringMatch(IF, 0, L"c:\\windows\\*", CASE_SENSITIVE)); + EXPECT_TRUE( + pr.AddStringMatch(IF, 0, L"d:\\adobe\\acrobat.exe", CASE_SENSITIVE)); +} + +// Testing that we signal invalid string patterns. +TEST(PolicyEngineTest, StringPatternsBAD) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_FALSE(pr.AddStringMatch(IF, 0, L"one**two", CASE_SENSITIVE)); + EXPECT_FALSE(pr.AddStringMatch(IF, 0, L"**three", CASE_SENSITIVE)); + EXPECT_FALSE(pr.AddStringMatch(IF, 0, L"five?six*?seven", CASE_SENSITIVE)); + EXPECT_FALSE(pr.AddStringMatch(IF, 0, L"eight?*nine", CASE_SENSITIVE)); +} + +// Helper function to allocate space (on the heap) for policy. +PolicyGlobal* MakePolicyMemory() { + const size_t kTotalPolicySz = 4096 * 8; + char* mem = new char[kTotalPolicySz]; + memset(mem, 0, kTotalPolicySz); + PolicyGlobal* policy = reinterpret_cast<PolicyGlobal*>(mem); + policy->data_size = kTotalPolicySz - sizeof(PolicyGlobal); + return policy; +} + +// The simplest test using LowLevelPolicy it should test a single opcode which +// does a exact string comparison. +TEST(PolicyEngineTest, SimpleStrMatch) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE( + pr.AddStringMatch(IF, 0, L"z:\\Directory\\domo.txt", CASE_INSENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + const IpcTag kFakeService = IpcTag::PING2; + + LowLevelPolicy policyGen(policy); + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = L"Z:\\Directory\\domo.txt"; + + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[static_cast<size_t>(kFakeService)]); + + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"Z:\\Directory\\domo.txt.tmp"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + delete[] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, SimpleIfNotStrMatch) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE(pr.AddStringMatch(IF_NOT, 0, L"c:\\Microsoft\\", CASE_SENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + const IpcTag kFakeService = IpcTag::PING2; + LowLevelPolicy policyGen(policy); + + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = nullptr; + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[static_cast<size_t>(kFakeService)]); + + filename = L"c:\\Microsoft\\"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\MicroNerd\\"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Microsoft\\domo.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + delete[] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, SimpleIfNotStrMatchWild1) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE( + pr.AddStringMatch(IF_NOT, 0, L"c:\\Microsoft\\*", CASE_SENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + const IpcTag kFakeService = IpcTag::NTCREATEFILE; + LowLevelPolicy policyGen(policy); + + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = nullptr; + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[static_cast<size_t>(kFakeService)]); + + filename = L"c:\\Microsoft\\domo.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\MicroNerd\\domo.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + delete[] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, SimpleIfNotStrMatchWild2) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE( + pr.AddStringMatch(IF_NOT, 0, L"c:\\Microsoft\\*.txt", CASE_SENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + const IpcTag kFakeService = IpcTag::NTCREATEFILE; + LowLevelPolicy policyGen(policy); + + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = nullptr; + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[static_cast<size_t>(kFakeService)]); + + filename = L"c:\\Microsoft\\domo.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\MicroNerd\\domo.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Microsoft\\domo.bmp"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + delete[] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, IfNotStrMatchTwoRulesWild1) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE( + pr.AddStringMatch(IF_NOT, 0, L"c:\\Microsoft\\*", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddNumberMatch(IF, 1, 24, EQUAL)); + + PolicyGlobal* policy = MakePolicyMemory(); + const IpcTag kFakeService = IpcTag::NTCREATEFILE; + LowLevelPolicy policyGen(policy); + + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = nullptr; + uint32_t access = 0; + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAM(access) // Argument 1 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[static_cast<size_t>(kFakeService)]); + + filename = L"c:\\Microsoft\\domo.txt"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\Microsoft\\domo.txt"; + access = 42; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\MicroNerd\\domo.txt"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Micronesia\\domo.txt"; + access = 42; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + delete[] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, IfNotStrMatchTwoRulesWild2) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE(pr.AddNumberMatch(IF, 1, 24, EQUAL)); + EXPECT_TRUE( + pr.AddStringMatch(IF_NOT, 0, L"c:\\GoogleV?\\*.txt", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddNumberMatch(IF, 2, 66, EQUAL)); + + PolicyGlobal* policy = MakePolicyMemory(); + const IpcTag kFakeService = IpcTag::NTCREATEFILE; + LowLevelPolicy policyGen(policy); + + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = nullptr; + uint32_t access = 0; + uint32_t sharing = 66; + + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAM(access) // Argument 1 + POLPARAM(sharing) // Argument 2 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[static_cast<size_t>(kFakeService)]); + + filename = L"c:\\GoogleV2\\domo.txt"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\GoogleV2\\domo.bmp"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\GoogleV23\\domo.txt"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\GoogleV2\\domo.txt"; + access = 42; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\Google\\domo.txt"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Micronesia\\domo.txt"; + access = 42; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\GoogleV2\\domo.bmp"; + access = 24; + sharing = 0; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + delete[] reinterpret_cast<char*>(policy); +} + +// Testing one single rule in one single service. The service is made to +// resemble NtCreateFile. +TEST(PolicyEngineTest, OneRuleTest) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE( + pr.AddStringMatch(IF, 0, L"c:\\*Microsoft*\\*.txt", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddNumberMatch(IF_NOT, 1, CREATE_ALWAYS, EQUAL)); + EXPECT_TRUE(pr.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL)); + + PolicyGlobal* policy = MakePolicyMemory(); + + const IpcTag kNtFakeCreateFile = IpcTag::NTCREATEFILE; + + LowLevelPolicy policyGen(policy); + EXPECT_TRUE(policyGen.AddRule(kNtFakeCreateFile, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = L"c:\\Documents and Settings\\Microsoft\\BLAH.txt"; + uint32_t creation_mode = OPEN_EXISTING; + uint32_t flags = FILE_ATTRIBUTE_NORMAL; + void* security_descriptor = nullptr; + + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAM(creation_mode) // Argument 1 + POLPARAM(flags) // Argument 2 + POLPARAM(security_descriptor) + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[static_cast<size_t>(kNtFakeCreateFile)]); + + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + creation_mode = CREATE_ALWAYS; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + creation_mode = OPEN_EXISTING; + filename = L"c:\\Other\\Path\\Microsoft\\Another file.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Other\\Path\\Microsoft\\Another file.txt.tmp"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + flags = FILE_ATTRIBUTE_DEVICE; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\Other\\Macrosoft\\Another file.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\Microsoft\\1.txt"; + flags = FILE_ATTRIBUTE_NORMAL; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Microsoft\\1.ttt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + delete[] reinterpret_cast<char*>(policy); +} + +// Testing 3 rules in 3 services. Two of the services resemble File services. +TEST(PolicyEngineTest, ThreeRulesTest) { + SetupNtdllImports(); + PolicyRule pr_pipe(FAKE_SUCCESS); + EXPECT_TRUE(pr_pipe.AddStringMatch(IF, 0, L"\\\\/?/?\\Pipe\\Chrome.*", + CASE_INSENSITIVE)); + EXPECT_TRUE(pr_pipe.AddNumberMatch(IF, 1, OPEN_EXISTING, EQUAL)); + EXPECT_TRUE(pr_pipe.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL)); + + size_t opc1 = pr_pipe.GetOpcodeCount(); + EXPECT_EQ(3u, opc1); + + PolicyRule pr_dump(ASK_BROKER); + EXPECT_TRUE(pr_dump.AddStringMatch(IF, 0, L"\\\\/?/?\\*\\Crash Reports\\*", + CASE_INSENSITIVE)); + EXPECT_TRUE(pr_dump.AddNumberMatch(IF, 1, CREATE_ALWAYS, EQUAL)); + EXPECT_TRUE(pr_dump.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL)); + + size_t opc2 = pr_dump.GetOpcodeCount(); + EXPECT_EQ(4u, opc2); + + PolicyRule pr_winexe(SIGNAL_ALARM); + EXPECT_TRUE(pr_winexe.AddStringMatch(IF, 0, L"\\\\/?/?\\C:\\Windows\\*.exe", + CASE_INSENSITIVE)); + EXPECT_TRUE(pr_winexe.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL)); + + size_t opc3 = pr_winexe.GetOpcodeCount(); + EXPECT_EQ(3u, opc3); + + PolicyRule pr_adobe(GIVE_CACHED); + EXPECT_TRUE( + pr_adobe.AddStringMatch(IF, 0, L"c:\\adobe\\ver?.?\\", CASE_SENSITIVE)); + EXPECT_TRUE(pr_adobe.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL)); + + size_t opc4 = pr_adobe.GetOpcodeCount(); + EXPECT_EQ(4u, opc4); + + PolicyRule pr_none(GIVE_FIRST); + EXPECT_TRUE(pr_none.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_READONLY, AND)); + EXPECT_TRUE(pr_none.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_SYSTEM, AND)); + + size_t opc5 = pr_none.GetOpcodeCount(); + EXPECT_EQ(2u, opc5); + + PolicyGlobal* policy = MakePolicyMemory(); + + // These do not match the real tag values. + const IpcTag kNtFakeNone = static_cast<IpcTag>(4); + const IpcTag kNtFakeCreateFile = static_cast<IpcTag>(5); + const IpcTag kNtFakeOpenFile = static_cast<IpcTag>(6); + + LowLevelPolicy policyGen(policy); + EXPECT_TRUE(policyGen.AddRule(kNtFakeCreateFile, &pr_pipe)); + EXPECT_TRUE(policyGen.AddRule(kNtFakeCreateFile, &pr_dump)); + EXPECT_TRUE(policyGen.AddRule(kNtFakeCreateFile, &pr_winexe)); + + EXPECT_TRUE(policyGen.AddRule(kNtFakeOpenFile, &pr_adobe)); + EXPECT_TRUE(policyGen.AddRule(kNtFakeOpenFile, &pr_pipe)); + + EXPECT_TRUE(policyGen.AddRule(kNtFakeNone, &pr_none)); + + EXPECT_TRUE(policyGen.Done()); + + // Inspect the policy structure manually. + EXPECT_FALSE(policy->entry[0]); + EXPECT_FALSE(policy->entry[1]); + EXPECT_FALSE(policy->entry[2]); + EXPECT_FALSE(policy->entry[3]); + EXPECT_TRUE(policy->entry[4]); // kNtFakeNone. + EXPECT_TRUE(policy->entry[5]); // kNtFakeCreateFile. + EXPECT_TRUE(policy->entry[6]); // kNtFakeOpenFile. + EXPECT_FALSE(policy->entry[7]); + + // The total per service opcode counts now must take in account one + // extra opcode (action opcode) per rule. + ++opc1; + ++opc2; + ++opc3; + ++opc4; + ++opc5; + + size_t tc1 = policy->entry[static_cast<size_t>(kNtFakeNone)]->opcode_count; + size_t tc2 = + policy->entry[static_cast<size_t>(kNtFakeCreateFile)]->opcode_count; + size_t tc3 = + policy->entry[static_cast<size_t>(kNtFakeOpenFile)]->opcode_count; + + EXPECT_EQ(opc5, tc1); + EXPECT_EQ((opc1 + opc2 + opc3), tc2); + EXPECT_EQ((opc1 + opc4), tc3); + + // Check the type of the first and last opcode of each service. + + EXPECT_EQ( + OP_NUMBER_AND_MATCH, + policy->entry[static_cast<size_t>(kNtFakeNone)]->opcodes[0].GetID()); + EXPECT_EQ(OP_ACTION, policy->entry[static_cast<size_t>(kNtFakeNone)] + ->opcodes[tc1 - 1] + .GetID()); + EXPECT_EQ(OP_WSTRING_MATCH, + policy->entry[static_cast<size_t>(kNtFakeCreateFile)] + ->opcodes[0] + .GetID()); + EXPECT_EQ(OP_ACTION, policy->entry[static_cast<size_t>(kNtFakeCreateFile)] + ->opcodes[tc2 - 1] + .GetID()); + EXPECT_EQ( + OP_WSTRING_MATCH, + policy->entry[static_cast<size_t>(kNtFakeOpenFile)]->opcodes[0].GetID()); + EXPECT_EQ(OP_ACTION, policy->entry[static_cast<size_t>(kNtFakeOpenFile)] + ->opcodes[tc3 - 1] + .GetID()); + + // Test the policy evaluation. + + const wchar_t* filename = L""; + uint32_t creation_mode = OPEN_EXISTING; + uint32_t flags = FILE_ATTRIBUTE_NORMAL; + void* security_descriptor = nullptr; + + POLPARAMS_BEGIN(params) + POLPARAM(filename) // Argument 0 + POLPARAM(creation_mode) // Argument 1 + POLPARAM(flags) // Argument 2 + POLPARAM(security_descriptor) + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor eval_CreateFile( + policy->entry[static_cast<size_t>(kNtFakeCreateFile)]); + PolicyProcessor eval_OpenFile( + policy->entry[static_cast<size_t>(kNtFakeOpenFile)]); + PolicyProcessor eval_None(policy->entry[static_cast<size_t>(kNtFakeNone)]); + + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"\\\\??\\c:\\Windows\\System32\\calc.exe"; + flags = FILE_ATTRIBUTE_SYSTEM; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + flags += FILE_ATTRIBUTE_READONLY; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(GIVE_FIRST, eval_None.GetAction()); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + flags = FILE_ATTRIBUTE_NORMAL; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(SIGNAL_ALARM, eval_CreateFile.GetAction()); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\adobe\\ver3.2\\temp"; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(GIVE_CACHED, eval_OpenFile.GetAction()); + + filename = L"c:\\adobe\\ver3.22\\temp"; + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"\\\\??\\c:\\some path\\other path\\crash reports\\some path"; + creation_mode = CREATE_ALWAYS; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, eval_CreateFile.GetAction()); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"\\\\??\\Pipe\\Chrome.12345"; + creation_mode = OPEN_EXISTING; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(FAKE_SUCCESS, eval_CreateFile.GetAction()); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(FAKE_SUCCESS, eval_OpenFile.GetAction()); + + delete[] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, PolicyRuleCopyConstructorTwoStrings) { + SetupNtdllImports(); + // Both pr_orig and pr_copy should allow hello.* but not *.txt files. + PolicyRule pr_orig(ASK_BROKER); + EXPECT_TRUE(pr_orig.AddStringMatch(IF, 0, L"hello.*", CASE_SENSITIVE)); + + PolicyRule pr_copy(pr_orig); + EXPECT_TRUE(pr_orig.AddStringMatch(IF_NOT, 0, L"*.txt", CASE_SENSITIVE)); + EXPECT_TRUE(pr_copy.AddStringMatch(IF_NOT, 0, L"*.txt", CASE_SENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + LowLevelPolicy policyGen(policy); + EXPECT_TRUE(policyGen.AddRule(IpcTag::PING1, &pr_orig)); + EXPECT_TRUE(policyGen.AddRule(IpcTag::PING2, &pr_copy)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* name = nullptr; + POLPARAMS_BEGIN(eval_params) + POLPARAM(name) + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev_orig(policy->entry[1]); + name = L"domo.txt"; + result = pol_ev_orig.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + name = L"hello.bmp"; + result = pol_ev_orig.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev_orig.GetAction()); + + PolicyProcessor pol_ev_copy(policy->entry[2]); + name = L"domo.txt"; + result = pol_ev_copy.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + name = L"hello.bmp"; + result = pol_ev_copy.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev_copy.GetAction()); +} + +TEST(PolicyEngineTest, PolicyGenDoneCalledTwice) { + SetupNtdllImports(); + // The specific rules here are not important. + PolicyRule pr_orig(ASK_BROKER); + EXPECT_TRUE(pr_orig.AddStringMatch(IF, 0, L"hello.*", CASE_SENSITIVE)); + + PolicyRule pr_copy(pr_orig); + EXPECT_TRUE(pr_orig.AddStringMatch(IF_NOT, 0, L"*.txt", CASE_SENSITIVE)); + EXPECT_TRUE(pr_copy.AddStringMatch(IF_NOT, 0, L"*.txt", CASE_SENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + LowLevelPolicy policyGen(policy); + const IpcTag tag1 = IpcTag::PING1; + const IpcTag tag2 = IpcTag::PING2; + EXPECT_TRUE(policyGen.AddRule(tag1, &pr_orig)); + EXPECT_TRUE(policyGen.AddRule(tag2, &pr_copy)); + EXPECT_TRUE(policyGen.Done()); + + // Obtain opcode counts. + size_t tc1 = policy->entry[static_cast<size_t>(IpcTag::PING1)]->opcode_count; + size_t tc2 = policy->entry[static_cast<size_t>(IpcTag::PING2)]->opcode_count; + + // Call Done() again. + EXPECT_TRUE(policyGen.Done()); + + // Expect same opcode counts. + EXPECT_EQ(tc1, + policy->entry[static_cast<size_t>(IpcTag::PING1)]->opcode_count); + EXPECT_EQ(tc2, + policy->entry[static_cast<size_t>(IpcTag::PING2)]->opcode_count); + + // Confirm the rules work as before. + const wchar_t* name = nullptr; + POLPARAMS_BEGIN(eval_params) + POLPARAM(name) + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev_orig(policy->entry[1]); + name = L"domo.txt"; + result = pol_ev_orig.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + name = L"hello.bmp"; + result = pol_ev_orig.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev_orig.GetAction()); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_opcodes_unittest.cc b/security/sandbox/chromium/sandbox/win/src/policy_opcodes_unittest.cc new file mode 100644 index 0000000000..c83efcba18 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_opcodes_unittest.cc @@ -0,0 +1,364 @@ +// Copyright (c) 2006-2008 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/win/src/policy_engine_opcodes.h" + +#include <stddef.h> +#include <stdint.h> + +#include "sandbox/win/src/policy_engine_params.h" +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_types.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define INIT_GLOBAL_RTL(member) \ + g_nt.member = \ + reinterpret_cast<member##Function>(::GetProcAddress(ntdll, #member)); \ + if (!g_nt.member) \ + return false + +namespace sandbox { + +const size_t kOpcodeMemory = 1024; + +SANDBOX_INTERCEPT NtExports g_nt; + +bool SetupNtdllImports() { + HMODULE ntdll = ::GetModuleHandle(kNtdllName); + + INIT_GLOBAL_RTL(RtlAllocateHeap); + INIT_GLOBAL_RTL(RtlAnsiStringToUnicodeString); + INIT_GLOBAL_RTL(RtlCompareUnicodeString); + INIT_GLOBAL_RTL(RtlCreateHeap); + INIT_GLOBAL_RTL(RtlDestroyHeap); + INIT_GLOBAL_RTL(RtlFreeHeap); + INIT_GLOBAL_RTL(_strnicmp); + INIT_GLOBAL_RTL(strlen); + INIT_GLOBAL_RTL(wcslen); + + return true; +} + +TEST(PolicyEngineTest, ParameterSetTest) { + void* pv1 = reinterpret_cast<void*>(0x477EAA5); + const void* pv2 = reinterpret_cast<void*>(0x987654); + ParameterSet pset1 = ParamPickerMake(pv1); + ParameterSet pset2 = ParamPickerMake(pv2); + + // Test that we can store and retrieve a void pointer: + const void* result1 = 0; + uint32_t result2 = 0; + EXPECT_TRUE(pset1.Get(&result1)); + EXPECT_TRUE(pv1 == result1); + EXPECT_FALSE(pset1.Get(&result2)); + EXPECT_TRUE(pset2.Get(&result1)); + EXPECT_TRUE(pv2 == result1); + EXPECT_FALSE(pset2.Get(&result2)); + + // Test that we can store and retrieve a uint32_t: + uint32_t number = 12747; + ParameterSet pset3 = ParamPickerMake(number); + EXPECT_FALSE(pset3.Get(&result1)); + EXPECT_TRUE(pset3.Get(&result2)); + EXPECT_EQ(number, result2); + + // Test that we can store and retrieve a string: + const wchar_t* txt = L"S231L"; + ParameterSet pset4 = ParamPickerMake(txt); + const wchar_t* result3 = nullptr; + EXPECT_TRUE(pset4.Get(&result3)); + EXPECT_EQ(0, wcscmp(txt, result3)); +} + +TEST(PolicyEngineTest, OpcodeConstraints) { + // Test that PolicyOpcode has no virtual functions + // because these objects are copied over to other processes + // so they cannot have vtables. + EXPECT_FALSE(__is_polymorphic(PolicyOpcode)); + // Keep developers from adding smarts to the opcodes which should + // be pretty much a bag of bytes with a OO interface. + EXPECT_TRUE(__has_trivial_destructor(PolicyOpcode)); + EXPECT_TRUE(__has_trivial_constructor(PolicyOpcode)); + EXPECT_TRUE(__has_trivial_copy(PolicyOpcode)); +} + +TEST(PolicyEngineTest, TrueFalseOpcodes) { + void* dummy = nullptr; + ParameterSet ppb1 = ParamPickerMake(dummy); + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + + // This opcode always evaluates to true. + PolicyOpcode* op1 = opcode_maker.MakeOpAlwaysFalse(kPolNone); + ASSERT_NE(nullptr, op1); + EXPECT_EQ(EVAL_FALSE, op1->Evaluate(&ppb1, 1, nullptr)); + EXPECT_FALSE(op1->IsAction()); + + // This opcode always evaluates to false. + PolicyOpcode* op2 = opcode_maker.MakeOpAlwaysTrue(kPolNone); + ASSERT_NE(nullptr, op2); + EXPECT_EQ(EVAL_TRUE, op2->Evaluate(&ppb1, 1, nullptr)); + + // Nulls not allowed on the params. + EXPECT_EQ(EVAL_ERROR, op2->Evaluate(nullptr, 0, nullptr)); + EXPECT_EQ(EVAL_ERROR, op2->Evaluate(nullptr, 1, nullptr)); + + // True and False opcodes do not 'require' a number of parameters + EXPECT_EQ(EVAL_TRUE, op2->Evaluate(&ppb1, 0, nullptr)); + EXPECT_EQ(EVAL_TRUE, op2->Evaluate(&ppb1, 1, nullptr)); + + // Test Inverting the logic. Note that inversion is done outside + // any particular opcode evaluation so no need to repeat for all + // opcodes. + PolicyOpcode* op3 = opcode_maker.MakeOpAlwaysFalse(kPolNegateEval); + ASSERT_NE(nullptr, op3); + EXPECT_EQ(EVAL_TRUE, op3->Evaluate(&ppb1, 1, nullptr)); + PolicyOpcode* op4 = opcode_maker.MakeOpAlwaysTrue(kPolNegateEval); + ASSERT_NE(nullptr, op4); + EXPECT_EQ(EVAL_FALSE, op4->Evaluate(&ppb1, 1, nullptr)); + + // Test that we clear the match context + PolicyOpcode* op5 = opcode_maker.MakeOpAlwaysTrue(kPolClearContext); + ASSERT_NE(nullptr, op5); + MatchContext context; + context.position = 1; + context.options = kPolUseOREval; + EXPECT_EQ(EVAL_TRUE, op5->Evaluate(&ppb1, 1, &context)); + EXPECT_EQ(0u, context.position); + MatchContext context2; + EXPECT_EQ(context2.options, context.options); +} + +TEST(PolicyEngineTest, OpcodeMakerCase1) { + // Testing that the opcode maker does not overrun the + // supplied buffer. It should only be able to make 'count' opcodes. + void* dummy = nullptr; + ParameterSet ppb1 = ParamPickerMake(dummy); + + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + size_t count = sizeof(memory) / sizeof(PolicyOpcode); + + for (size_t ix = 0; ix != count; ++ix) { + PolicyOpcode* op = opcode_maker.MakeOpAlwaysFalse(kPolNone); + ASSERT_NE(nullptr, op); + EXPECT_EQ(EVAL_FALSE, op->Evaluate(&ppb1, 1, nullptr)); + } + // There should be no room more another opcode: + PolicyOpcode* op1 = opcode_maker.MakeOpAlwaysFalse(kPolNone); + ASSERT_EQ(nullptr, op1); +} + +TEST(PolicyEngineTest, OpcodeMakerCase2) { + SetupNtdllImports(); + // Testing that the opcode maker does not overrun the + // supplied buffer. It should only be able to make 'count' opcodes. + // The difference with the previous test is that this opcodes allocate + // the string 'txt2' inside the same buffer. + const wchar_t* txt1 = L"1234"; + const wchar_t txt2[] = L"123"; + + ParameterSet ppb1 = ParamPickerMake(txt1); + MatchContext mc1; + + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + size_t count = sizeof(memory) / (sizeof(PolicyOpcode) + sizeof(txt2)); + + // Test that it does not overrun the buffer. + for (size_t ix = 0; ix != count; ++ix) { + PolicyOpcode* op = opcode_maker.MakeOpWStringMatch( + 0, txt2, 0, CASE_SENSITIVE, kPolClearContext); + ASSERT_NE(nullptr, op); + EXPECT_EQ(EVAL_TRUE, op->Evaluate(&ppb1, 1, &mc1)); + } + + // There should be no room more another opcode: + PolicyOpcode* op1 = + opcode_maker.MakeOpWStringMatch(0, txt2, 0, CASE_SENSITIVE, kPolNone); + ASSERT_EQ(nullptr, op1); +} + +TEST(PolicyEngineTest, IntegerOpcodes) { + const wchar_t* txt = L"abcdef"; + uint32_t num1 = 42; + uint32_t num2 = 113377; + + ParameterSet pp_wrong1 = ParamPickerMake(txt); + ParameterSet pp_num1 = ParamPickerMake(num1); + ParameterSet pp_num2 = ParamPickerMake(num2); + + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + + // Test basic match for uint32s 42 == 42 and 42 != 113377. + PolicyOpcode* op_m42 = opcode_maker.MakeOpNumberMatch(0, 42UL, kPolNone); + ASSERT_NE(nullptr, op_m42); + EXPECT_EQ(EVAL_TRUE, op_m42->Evaluate(&pp_num1, 1, nullptr)); + EXPECT_EQ(EVAL_FALSE, op_m42->Evaluate(&pp_num2, 1, nullptr)); + EXPECT_EQ(EVAL_ERROR, op_m42->Evaluate(&pp_wrong1, 1, nullptr)); + + // Test basic match for void pointers. + const void* vp = nullptr; + ParameterSet pp_num3 = ParamPickerMake(vp); + PolicyOpcode* op_vp_null = + opcode_maker.MakeOpVoidPtrMatch(0, nullptr, kPolNone); + ASSERT_NE(nullptr, op_vp_null); + EXPECT_EQ(EVAL_TRUE, op_vp_null->Evaluate(&pp_num3, 1, nullptr)); + EXPECT_EQ(EVAL_FALSE, op_vp_null->Evaluate(&pp_num1, 1, nullptr)); + EXPECT_EQ(EVAL_ERROR, op_vp_null->Evaluate(&pp_wrong1, 1, nullptr)); + + // Basic range test [41 43] (inclusive). + PolicyOpcode* op_range1 = + opcode_maker.MakeOpNumberMatchRange(0, 41, 43, kPolNone); + ASSERT_NE(nullptr, op_range1); + EXPECT_EQ(EVAL_TRUE, op_range1->Evaluate(&pp_num1, 1, nullptr)); + EXPECT_EQ(EVAL_FALSE, op_range1->Evaluate(&pp_num2, 1, nullptr)); + EXPECT_EQ(EVAL_ERROR, op_range1->Evaluate(&pp_wrong1, 1, nullptr)); +} + +TEST(PolicyEngineTest, LogicalOpcodes) { + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + + uint32_t num1 = 0x10100702; + ParameterSet pp_num1 = ParamPickerMake(num1); + + PolicyOpcode* op_and1 = + opcode_maker.MakeOpNumberAndMatch(0, 0x00100000, kPolNone); + ASSERT_NE(nullptr, op_and1); + EXPECT_EQ(EVAL_TRUE, op_and1->Evaluate(&pp_num1, 1, nullptr)); + PolicyOpcode* op_and2 = + opcode_maker.MakeOpNumberAndMatch(0, 0x00000001, kPolNone); + ASSERT_NE(nullptr, op_and2); + EXPECT_EQ(EVAL_FALSE, op_and2->Evaluate(&pp_num1, 1, nullptr)); +} + +TEST(PolicyEngineTest, WCharOpcodes1) { + SetupNtdllImports(); + + const wchar_t* txt1 = L"the quick fox jumps over the lazy dog"; + const wchar_t txt2[] = L"the quick"; + const wchar_t txt3[] = L" fox jumps"; + const wchar_t txt4[] = L"the lazy dog"; + const wchar_t txt5[] = L"jumps over"; + const wchar_t txt6[] = L"g"; + + ParameterSet pp_tc1 = ParamPickerMake(txt1); + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + + PolicyOpcode* op1 = + opcode_maker.MakeOpWStringMatch(0, txt2, 0, CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op1); + + // Simplest substring match from pos 0. It should be a successful match + // and the match context should be updated. + MatchContext mc1; + EXPECT_EQ(EVAL_TRUE, op1->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_TRUE(_countof(txt2) == mc1.position + 1); + + // Matching again should fail and the context should be unmodified. + EXPECT_EQ(EVAL_FALSE, op1->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_TRUE(_countof(txt2) == mc1.position + 1); + + // Using the same match context we should continue where we left + // in the previous successful match, + PolicyOpcode* op3 = + opcode_maker.MakeOpWStringMatch(0, txt3, 0, CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op3); + EXPECT_EQ(EVAL_TRUE, op3->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_TRUE(_countof(txt3) + _countof(txt2) == mc1.position + 2); + + // We now keep on matching but now we skip 6 characters which means + // we skip the string ' over '. And we zero the match context. This is + // the primitive that we use to build '??'. + PolicyOpcode* op4 = opcode_maker.MakeOpWStringMatch( + 0, txt4, 6, CASE_SENSITIVE, kPolClearContext); + ASSERT_NE(nullptr, op4); + EXPECT_EQ(EVAL_TRUE, op4->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(0u, mc1.position); + + // Test that we can properly match the last part of the string + PolicyOpcode* op4b = opcode_maker.MakeOpWStringMatch( + 0, txt4, kSeekToEnd, CASE_SENSITIVE, kPolClearContext); + ASSERT_NE(nullptr, op4b); + EXPECT_EQ(EVAL_TRUE, op4b->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(0u, mc1.position); + + // Test matching 'jumps over' over the entire string. This is the + // primitive we build '*' from. + PolicyOpcode* op5 = opcode_maker.MakeOpWStringMatch(0, txt5, kSeekForward, + CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op5); + EXPECT_EQ(EVAL_TRUE, op5->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(24u, mc1.position); + + // Test that we don't match because it is not at the end of the string + PolicyOpcode* op5b = opcode_maker.MakeOpWStringMatch( + 0, txt5, kSeekToEnd, CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op5b); + EXPECT_EQ(EVAL_FALSE, op5b->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(24u, mc1.position); + + // Test that we function if the string does not fit. In this case we + // try to match 'the lazy dog' against 'he lazy dog'. + PolicyOpcode* op6 = + opcode_maker.MakeOpWStringMatch(0, txt4, 2, CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op6); + EXPECT_EQ(EVAL_FALSE, op6->Evaluate(&pp_tc1, 1, &mc1)); + + // Testing matching against 'g' which should be the last char. + MatchContext mc2; + PolicyOpcode* op7 = opcode_maker.MakeOpWStringMatch(0, txt6, kSeekForward, + CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op7); + EXPECT_EQ(EVAL_TRUE, op7->Evaluate(&pp_tc1, 1, &mc2)); + EXPECT_EQ(37u, mc2.position); + + // Trying to match again should fail since we are in the last char. + // This also covers a couple of boundary conditions. + EXPECT_EQ(EVAL_FALSE, op7->Evaluate(&pp_tc1, 1, &mc2)); + EXPECT_EQ(37u, mc2.position); +} + +TEST(PolicyEngineTest, WCharOpcodes2) { + SetupNtdllImports(); + + const wchar_t* path1 = L"c:\\documents and settings\\Microsoft\\BLAH.txt"; + const wchar_t txt1[] = L"Settings\\microsoft"; + ParameterSet pp_tc1 = ParamPickerMake(path1); + + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + MatchContext mc1; + + // Testing case-insensitive does not buy us much since it this option + // is just passed to the Microsoft API that we use normally, but just for + // coverage, here it is: + PolicyOpcode* op1s = opcode_maker.MakeOpWStringMatch( + 0, txt1, kSeekForward, CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op1s); + PolicyOpcode* op1i = opcode_maker.MakeOpWStringMatch( + 0, txt1, kSeekForward, CASE_INSENSITIVE, kPolNone); + ASSERT_NE(nullptr, op1i); + EXPECT_EQ(EVAL_FALSE, op1s->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(EVAL_TRUE, op1i->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(35u, mc1.position); +} + +TEST(PolicyEngineTest, ActionOpcodes) { + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + MatchContext mc1; + void* dummy = nullptr; + ParameterSet ppb1 = ParamPickerMake(dummy); + + PolicyOpcode* op1 = opcode_maker.MakeOpAction(ASK_BROKER, kPolNone); + ASSERT_NE(nullptr, op1); + EXPECT_TRUE(op1->IsAction()); + EXPECT_EQ(ASK_BROKER, op1->Evaluate(&ppb1, 1, &mc1)); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_params.h b/security/sandbox/chromium/sandbox/win/src/policy_params.h new file mode 100644 index 0000000000..aeec6d7edc --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_params.h @@ -0,0 +1,70 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_SRC_POLICY_PARAMS_H__ +#define SANDBOX_SRC_POLICY_PARAMS_H__ + +#include "sandbox/win/src/policy_engine_params.h" + +namespace sandbox { + +class ParameterSet; + +// Warning: The following macros store the address to the actual variables, in +// other words, the values are not copied. +#define POLPARAMS_BEGIN(type) class type { public: enum Args { +#define POLPARAM(arg) arg, +#define POLPARAMS_END(type) PolParamLast }; }; \ + typedef sandbox::ParameterSet type##Array [type::PolParamLast]; + +// Policy parameters for file open / create. +POLPARAMS_BEGIN(OpenFile) + POLPARAM(NAME) + POLPARAM(BROKER) // true if called from the broker. + POLPARAM(ACCESS) + POLPARAM(DISPOSITION) + POLPARAM(OPTIONS) +POLPARAMS_END(OpenFile) + +// Policy parameter for name-based policies. +POLPARAMS_BEGIN(FileName) + POLPARAM(NAME) + POLPARAM(BROKER) // true if called from the broker. +POLPARAMS_END(FileName) + +static_assert(OpenFile::NAME == static_cast<int>(FileName::NAME), + "to simplify fs policies"); +static_assert(OpenFile::BROKER == static_cast<int>(FileName::BROKER), + "to simplify fs policies"); + +// Policy parameter for name-based policies. +POLPARAMS_BEGIN(NameBased) + POLPARAM(NAME) +POLPARAMS_END(NameBased) + +// Policy parameters for open event. +POLPARAMS_BEGIN(OpenEventParams) + POLPARAM(NAME) + POLPARAM(ACCESS) +POLPARAMS_END(OpenEventParams) + +// Policy Parameters for reg open / create. +POLPARAMS_BEGIN(OpenKey) + POLPARAM(NAME) + POLPARAM(ACCESS) +POLPARAMS_END(OpenKey) + +// Policy parameter for name-based policies. +POLPARAMS_BEGIN(HandleTarget) + POLPARAM(NAME) + POLPARAM(TARGET) +POLPARAMS_END(HandleTarget) + +// Policy parameters where no parameter based checks are done. +POLPARAMS_BEGIN(EmptyParams) +POLPARAMS_END(EmptyParams) + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_PARAMS_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_target.cc b/security/sandbox/chromium/sandbox/win/src/policy_target.cc new file mode 100644 index 0000000000..1727d622f8 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_target.cc @@ -0,0 +1,138 @@ +// Copyright (c) 2006-2008 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/win/src/policy_target.h" + +#include <stddef.h> + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_processor.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +// Handle for our private heap. +extern void* g_heap; + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt; + +// Policy data. +extern void* volatile g_shared_policy_memory; +SANDBOX_INTERCEPT size_t g_shared_policy_size; + +bool QueryBroker(IpcTag ipc_id, CountedParameterSetBase* params) { + DCHECK_NT(static_cast<size_t>(ipc_id) < kMaxServiceCount); + DCHECK_NT(g_shared_policy_memory); + DCHECK_NT(g_shared_policy_size > 0); + + if (static_cast<size_t>(ipc_id) >= kMaxServiceCount) + return false; + + PolicyGlobal* global_policy = + reinterpret_cast<PolicyGlobal*>(g_shared_policy_memory); + + if (!global_policy->entry[static_cast<size_t>(ipc_id)]) + return false; + + PolicyBuffer* policy = reinterpret_cast<PolicyBuffer*>( + reinterpret_cast<char*>(g_shared_policy_memory) + + reinterpret_cast<size_t>( + global_policy->entry[static_cast<size_t>(ipc_id)])); + + if ((reinterpret_cast<size_t>( + global_policy->entry[static_cast<size_t>(ipc_id)]) > + global_policy->data_size) || + (g_shared_policy_size < global_policy->data_size)) { + NOTREACHED_NT(); + return false; + } + + for (size_t i = 0; i < params->count; i++) { + if (!params->parameters[i].IsValid()) { + NOTREACHED_NT(); + return false; + } + } + + PolicyProcessor processor(policy); + PolicyResult result = + processor.Evaluate(kShortEval, params->parameters, params->count); + DCHECK_NT(POLICY_ERROR != result); + + return POLICY_MATCH == result && ASK_BROKER == processor.GetAction(); +} + +// ----------------------------------------------------------------------- + +// Hooks NtSetInformationThread to block RevertToSelf from being +// called before the actual call to LowerToken. +NTSTATUS WINAPI TargetNtSetInformationThread( + NtSetInformationThreadFunction orig_SetInformationThread, + HANDLE thread, + NT_THREAD_INFORMATION_CLASS thread_info_class, + PVOID thread_information, + ULONG thread_information_bytes) { + do { + if (SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) + break; + if (ThreadImpersonationToken != thread_info_class) + break; + if (!thread_information) + break; + HANDLE token; + if (sizeof(token) > thread_information_bytes) + break; + + NTSTATUS ret = CopyData(&token, thread_information, sizeof(token)); + if (!NT_SUCCESS(ret) || NULL != token) + break; + + // This is a revert to self. + return STATUS_SUCCESS; + } while (false); + + return orig_SetInformationThread( + thread, thread_info_class, thread_information, thread_information_bytes); +} + +// Hooks NtOpenThreadToken to force the open_as_self parameter to be set to +// false if we are still running with the impersonation token. open_as_self set +// to true means that the token will be open using the process token instead of +// the impersonation token. This is bad because the process token does not have +// access to open the thread token. +NTSTATUS WINAPI +TargetNtOpenThreadToken(NtOpenThreadTokenFunction orig_OpenThreadToken, + HANDLE thread, + ACCESS_MASK desired_access, + BOOLEAN open_as_self, + PHANDLE token) { + if (!SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) + open_as_self = false; + + return orig_OpenThreadToken(thread, desired_access, open_as_self, token); +} + +// See comment for TargetNtOpenThreadToken +NTSTATUS WINAPI +TargetNtOpenThreadTokenEx(NtOpenThreadTokenExFunction orig_OpenThreadTokenEx, + HANDLE thread, + ACCESS_MASK desired_access, + BOOLEAN open_as_self, + ULONG handle_attributes, + PHANDLE token) { + if (!SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) + open_as_self = false; + + return orig_OpenThreadTokenEx(thread, desired_access, open_as_self, + handle_attributes, token); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_target.h b/security/sandbox/chromium/sandbox/win/src/policy_target.h new file mode 100644 index 0000000000..62686aaab2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_target.h @@ -0,0 +1,46 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_WIN_SRC_POLICY_TARGET_H_ +#define SANDBOX_WIN_SRC_POLICY_TARGET_H_ + +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +struct CountedParameterSetBase; + +// Performs a policy lookup and returns true if the request should be passed to +// the broker process. +bool QueryBroker(IpcTag ipc_id, CountedParameterSetBase* params); + +extern "C" { + +// Interception of NtSetInformationThread on the child process. +// It should never be called directly. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtSetInformationThread( + NtSetInformationThreadFunction orig_SetInformationThread, HANDLE thread, + NT_THREAD_INFORMATION_CLASS thread_info_class, PVOID thread_information, + ULONG thread_information_bytes); + +// Interception of NtOpenThreadToken on the child process. +// It should never be called directly +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenThreadToken( + NtOpenThreadTokenFunction orig_OpenThreadToken, HANDLE thread, + ACCESS_MASK desired_access, BOOLEAN open_as_self, PHANDLE token); + +// Interception of NtOpenThreadTokenEx on the child process. +// It should never be called directly +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenThreadTokenEx( + NtOpenThreadTokenExFunction orig_OpenThreadTokenEx, HANDLE thread, + ACCESS_MASK desired_access, BOOLEAN open_as_self, ULONG handle_attributes, + PHANDLE token); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_POLICY_TARGET_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_target_test.cc b/security/sandbox/chromium/sandbox/win/src/policy_target_test.cc new file mode 100644 index 0000000000..4ee03e19f6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_target_test.cc @@ -0,0 +1,486 @@ +// 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 "base/memory/read_only_shared_memory_region.h" +#include "base/memory/writable_shared_memory_region.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "base/win/scoped_process_information.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/target_services.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include "base/win/win_util.h" +#endif + +namespace sandbox { + +#define BINDNTDLL(name) \ + name##Function name = reinterpret_cast<name##Function>( \ + ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), #name)) + +// Reverts to self and verify that SetInformationToken was faked. Returns +// SBOX_TEST_SUCCEEDED if faked and SBOX_TEST_FAILED if not faked. +SBOX_TESTS_COMMAND int PolicyTargetTest_token(int argc, wchar_t** argv) { + HANDLE thread_token; + // Get the thread token, using impersonation. + if (!::OpenThreadToken(GetCurrentThread(), + TOKEN_IMPERSONATE | TOKEN_DUPLICATE, false, + &thread_token)) + return ::GetLastError(); + + ::RevertToSelf(); + ::CloseHandle(thread_token); + + int ret = SBOX_TEST_FAILED; + if (::OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | TOKEN_DUPLICATE, + false, &thread_token)) { + ret = SBOX_TEST_SUCCEEDED; + ::CloseHandle(thread_token); + } + return ret; +} + +// Stores the high privilege token on a static variable, change impersonation +// again to that one and verify that we are not interfering anymore with +// RevertToSelf. +SBOX_TESTS_COMMAND int PolicyTargetTest_steal(int argc, wchar_t** argv) { + static HANDLE thread_token; + if (!SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) { + if (!::OpenThreadToken(GetCurrentThread(), + TOKEN_IMPERSONATE | TOKEN_DUPLICATE, false, + &thread_token)) + return ::GetLastError(); + } else { + if (!::SetThreadToken(nullptr, thread_token)) + return ::GetLastError(); + + // See if we fake the call again. + int ret = PolicyTargetTest_token(argc, argv); + ::CloseHandle(thread_token); + return ret; + } + return 0; +} + +// Opens the thread token with and without impersonation. +SBOX_TESTS_COMMAND int PolicyTargetTest_token2(int argc, wchar_t** argv) { + HANDLE thread_token; + // Get the thread token, using impersonation. + if (!::OpenThreadToken(GetCurrentThread(), + TOKEN_IMPERSONATE | TOKEN_DUPLICATE, false, + &thread_token)) + return ::GetLastError(); + ::CloseHandle(thread_token); + + // Get the thread token, without impersonation. + if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | TOKEN_DUPLICATE, + true, &thread_token)) + return ::GetLastError(); + ::CloseHandle(thread_token); + return SBOX_TEST_SUCCEEDED; +} + +// Opens the thread token with and without impersonation, using +// NtOpenThreadTokenEX. +SBOX_TESTS_COMMAND int PolicyTargetTest_token3(int argc, wchar_t** argv) { + BINDNTDLL(NtOpenThreadTokenEx); + if (!NtOpenThreadTokenEx) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + HANDLE thread_token; + // Get the thread token, using impersonation. + NTSTATUS status = NtOpenThreadTokenEx(GetCurrentThread(), + TOKEN_IMPERSONATE | TOKEN_DUPLICATE, + false, 0, &thread_token); + if (status == STATUS_NO_TOKEN) + return ERROR_NO_TOKEN; + if (!NT_SUCCESS(status)) + return SBOX_TEST_FAILED; + + ::CloseHandle(thread_token); + + // Get the thread token, without impersonation. + status = NtOpenThreadTokenEx(GetCurrentThread(), + TOKEN_IMPERSONATE | TOKEN_DUPLICATE, true, 0, + &thread_token); + if (!NT_SUCCESS(status)) + return SBOX_TEST_FAILED; + + ::CloseHandle(thread_token); + return SBOX_TEST_SUCCEEDED; +} + +// Tests that we can open the current thread. +SBOX_TESTS_COMMAND int PolicyTargetTest_thread(int argc, wchar_t** argv) { + DWORD thread_id = ::GetCurrentThreadId(); + HANDLE thread = ::OpenThread(SYNCHRONIZE, false, thread_id); + if (!thread) + return ::GetLastError(); + if (!::CloseHandle(thread)) + return ::GetLastError(); + + return SBOX_TEST_SUCCEEDED; +} + +// New thread entry point: do nothing. +DWORD WINAPI PolicyTargetTest_thread_main(void* param) { + ::Sleep(INFINITE); + return 0; +} + +// Tests that we can create a new thread, and open it. +SBOX_TESTS_COMMAND int PolicyTargetTest_thread2(int argc, wchar_t** argv) { + // Use default values to create a new thread. + DWORD thread_id; + HANDLE thread = ::CreateThread(nullptr, 0, &PolicyTargetTest_thread_main, 0, + 0, &thread_id); + if (!thread) + return ::GetLastError(); + if (!::CloseHandle(thread)) + return ::GetLastError(); + + thread = ::OpenThread(SYNCHRONIZE, false, thread_id); + if (!thread) + return ::GetLastError(); + + if (!::CloseHandle(thread)) + return ::GetLastError(); + + return SBOX_TEST_SUCCEEDED; +} + +// Tests that we can call CreateProcess. +SBOX_TESTS_COMMAND int PolicyTargetTest_process(int argc, wchar_t** argv) { + // Use default values to create a new process. + STARTUPINFO startup_info = {0}; + startup_info.cb = sizeof(startup_info); + PROCESS_INFORMATION temp_process_info = {}; + // Note: CreateProcessW() can write to its lpCommandLine, don't pass a + // raw string literal. + std::wstring writable_cmdline_str(L"foo.exe"); + if (!::CreateProcessW(L"foo.exe", &writable_cmdline_str[0], nullptr, nullptr, + false, 0, nullptr, nullptr, &startup_info, + &temp_process_info)) + return SBOX_TEST_SUCCEEDED; + base::win::ScopedProcessInformation process_info(temp_process_info); + return SBOX_TEST_FAILED; +} + +TEST(PolicyTargetTest, SetInformationThread) { + TestRunner runner; + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token")); + + runner.SetTestState(AFTER_REVERT); + EXPECT_EQ(ERROR_NO_TOKEN, runner.RunTest(L"PolicyTargetTest_token")); + + runner.SetTestState(EVERY_STATE); + EXPECT_EQ(SBOX_TEST_FAILED, runner.RunTest(L"PolicyTargetTest_steal")); +} + +TEST(PolicyTargetTest, OpenThreadToken) { + TestRunner runner; + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token2")); + + runner.SetTestState(AFTER_REVERT); + EXPECT_EQ(ERROR_NO_TOKEN, runner.RunTest(L"PolicyTargetTest_token2")); +} + +TEST(PolicyTargetTest, OpenThreadTokenEx) { + TestRunner runner; + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token3")); + + runner.SetTestState(AFTER_REVERT); + EXPECT_EQ(ERROR_NO_TOKEN, runner.RunTest(L"PolicyTargetTest_token3")); +} + +TEST(PolicyTargetTest, OpenThread) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_thread")) + << "Opens the current thread"; + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_thread2")) + << "Creates a new thread and opens it"; +} + +TEST(PolicyTargetTest, OpenProcess) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_process")) + << "Opens a process"; +} + +TEST(PolicyTargetTest, PolicyBaseNoJobLifetime) { + TestRunner runner(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); + runner.SetReleasePolicyInRun(true); + // TargetPolicy and its SharedMemIPCServer should continue to exist until + // the child process dies. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_thread")) + << "Opens the current thread"; +} + +// Launches the app in the sandbox and ask it to wait in an +// infinite loop. Waits for 2 seconds and then check if the +// desktop associated with the app thread is not the same as the +// current desktop. +TEST(PolicyTargetTest, DesktopPolicy) { + BrokerServices* broker = GetBroker(); + + // Precreate the desktop. + scoped_refptr<TargetPolicy> temp_policy = broker->CreatePolicy(); + temp_policy->CreateAlternateDesktop(false); + temp_policy = nullptr; + + ASSERT_TRUE(broker); + + // Get the path to the sandboxed app. + wchar_t prog_name[MAX_PATH]; + GetModuleFileNameW(nullptr, prog_name, MAX_PATH); + + std::wstring arguments(L"\""); + arguments += prog_name; + arguments += L"\" -child 0 wait"; // Don't care about the "state" argument. + + // Launch the app. + ResultCode result = SBOX_ALL_OK; + ResultCode warning_result = SBOX_ALL_OK; + DWORD last_error = ERROR_SUCCESS; + base::win::ScopedProcessInformation target; + + scoped_refptr<TargetPolicy> policy = broker->CreatePolicy(); + policy->SetAlternateDesktop(false); + policy->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN); + PROCESS_INFORMATION temp_process_info = {}; + result = + broker->SpawnTarget(prog_name, arguments.c_str(), policy, &warning_result, + &last_error, &temp_process_info); + std::wstring desktop_name = policy->GetAlternateDesktop(); + policy = nullptr; + + EXPECT_EQ(SBOX_ALL_OK, result); + if (result == SBOX_ALL_OK) + target.Set(temp_process_info); + + EXPECT_EQ(1u, ::ResumeThread(target.thread_handle())); + + EXPECT_EQ(static_cast<DWORD>(WAIT_TIMEOUT), + ::WaitForSingleObject(target.process_handle(), 2000)); + + EXPECT_NE(::GetThreadDesktop(target.thread_id()), + ::GetThreadDesktop(::GetCurrentThreadId())); + + HDESK desk = ::OpenDesktop(desktop_name.c_str(), 0, false, DESKTOP_ENUMERATE); + EXPECT_TRUE(desk); + EXPECT_TRUE(::CloseDesktop(desk)); + EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0)); + + ::WaitForSingleObject(target.process_handle(), INFINITE); + + // Close the desktop handle. + temp_policy = broker->CreatePolicy(); + temp_policy->DestroyAlternateDesktop(); + temp_policy = nullptr; + + // Make sure the desktop does not exist anymore. + desk = ::OpenDesktop(desktop_name.c_str(), 0, false, DESKTOP_ENUMERATE); + EXPECT_FALSE(desk); +} + +// Launches the app in the sandbox and ask it to wait in an +// infinite loop. Waits for 2 seconds and then check if the +// winstation associated with the app thread is not the same as the +// current desktop. +TEST(PolicyTargetTest, WinstaPolicy) { + BrokerServices* broker = GetBroker(); + + // Precreate the desktop. + scoped_refptr<TargetPolicy> temp_policy = broker->CreatePolicy(); + temp_policy->CreateAlternateDesktop(true); + temp_policy = nullptr; + + ASSERT_TRUE(broker); + + // Get the path to the sandboxed app. + wchar_t prog_name[MAX_PATH]; + GetModuleFileNameW(nullptr, prog_name, MAX_PATH); + + std::wstring arguments(L"\""); + arguments += prog_name; + arguments += L"\" -child 0 wait"; // Don't care about the "state" argument. + + // Launch the app. + ResultCode result = SBOX_ALL_OK; + ResultCode warning_result = SBOX_ALL_OK; + base::win::ScopedProcessInformation target; + + scoped_refptr<TargetPolicy> policy = broker->CreatePolicy(); + policy->SetAlternateDesktop(true); + policy->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN); + PROCESS_INFORMATION temp_process_info = {}; + DWORD last_error = ERROR_SUCCESS; + result = + broker->SpawnTarget(prog_name, arguments.c_str(), policy, &warning_result, + &last_error, &temp_process_info); + std::wstring desktop_name = policy->GetAlternateDesktop(); + policy = nullptr; + + EXPECT_EQ(SBOX_ALL_OK, result); + if (result == SBOX_ALL_OK) + target.Set(temp_process_info); + + EXPECT_EQ(1u, ::ResumeThread(target.thread_handle())); + + EXPECT_EQ(static_cast<DWORD>(WAIT_TIMEOUT), + ::WaitForSingleObject(target.process_handle(), 2000)); + + EXPECT_NE(::GetThreadDesktop(target.thread_id()), + ::GetThreadDesktop(::GetCurrentThreadId())); + + ASSERT_FALSE(desktop_name.empty()); + + // Make sure there is a backslash, for the window station name. + EXPECT_NE(desktop_name.find_first_of(L'\\'), std::wstring::npos); + + // Isolate the desktop name. + desktop_name = desktop_name.substr(desktop_name.find_first_of(L'\\') + 1); + + HDESK desk = ::OpenDesktop(desktop_name.c_str(), 0, false, DESKTOP_ENUMERATE); + // This should fail if the desktop is really on another window station. + EXPECT_FALSE(desk); + EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0)); + + ::WaitForSingleObject(target.process_handle(), INFINITE); + + // Close the desktop handle. + temp_policy = broker->CreatePolicy(); + temp_policy->DestroyAlternateDesktop(); + temp_policy = nullptr; +} + +// Creates multiple policies, with alternate desktops on both local and +// alternate winstations. +TEST(PolicyTargetTest, BothLocalAndAlternateWinstationDesktop) { + BrokerServices* broker = GetBroker(); + + scoped_refptr<TargetPolicy> policy1 = broker->CreatePolicy(); + scoped_refptr<TargetPolicy> policy2 = broker->CreatePolicy(); + scoped_refptr<TargetPolicy> policy3 = broker->CreatePolicy(); + + ResultCode result; + result = policy1->SetAlternateDesktop(false); + EXPECT_EQ(SBOX_ALL_OK, result); + result = policy2->SetAlternateDesktop(true); + EXPECT_EQ(SBOX_ALL_OK, result); + result = policy3->SetAlternateDesktop(false); + EXPECT_EQ(SBOX_ALL_OK, result); + + std::wstring policy1_desktop_name = policy1->GetAlternateDesktop(); + std::wstring policy2_desktop_name = policy2->GetAlternateDesktop(); + + // Extract only the "desktop name" portion of + // "{winstation name}\\{desktop name}" + EXPECT_NE(policy1_desktop_name.substr( + policy1_desktop_name.find_first_of(L'\\') + 1), + policy2_desktop_name.substr( + policy2_desktop_name.find_first_of(L'\\') + 1)); + + policy1->DestroyAlternateDesktop(); + policy2->DestroyAlternateDesktop(); + policy3->DestroyAlternateDesktop(); +} + +// Launches the app in the sandbox and share a handle with it. The app should +// be able to use the handle. +TEST(PolicyTargetTest, ShareHandleTest) { + BrokerServices* broker = GetBroker(); + ASSERT_TRUE(broker); + + base::StringPiece contents = "Hello World"; + base::WritableSharedMemoryRegion writable_region = + base::WritableSharedMemoryRegion::Create(contents.size()); + ASSERT_TRUE(writable_region.IsValid()); + base::WritableSharedMemoryMapping writable_mapping = writable_region.Map(); + ASSERT_TRUE(writable_mapping.IsValid()); + memcpy(writable_mapping.memory(), contents.data(), contents.size()); + + // Get the path to the sandboxed app. + wchar_t prog_name[MAX_PATH]; + GetModuleFileNameW(nullptr, prog_name, MAX_PATH); + + base::ReadOnlySharedMemoryRegion read_only_region = + base::WritableSharedMemoryRegion::ConvertToReadOnly( + std::move(writable_region)); + ASSERT_TRUE(read_only_region.IsValid()); + + scoped_refptr<TargetPolicy> policy = broker->CreatePolicy(); + policy->AddHandleToShare(read_only_region.GetPlatformHandle()); + + std::wstring arguments(L"\""); + arguments += prog_name; + arguments += L"\" -child 0 shared_memory_handle "; + arguments += base::AsWString(base::NumberToString16( + base::win::HandleToUint32(read_only_region.GetPlatformHandle()))); + + // Launch the app. + ResultCode result = SBOX_ALL_OK; + ResultCode warning_result = SBOX_ALL_OK; + base::win::ScopedProcessInformation target; + + policy->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN); + PROCESS_INFORMATION temp_process_info = {}; + DWORD last_error = ERROR_SUCCESS; + result = + broker->SpawnTarget(prog_name, arguments.c_str(), policy, &warning_result, + &last_error, &temp_process_info); + policy = nullptr; + + EXPECT_EQ(SBOX_ALL_OK, result); + if (result == SBOX_ALL_OK) + target.Set(temp_process_info); + + EXPECT_EQ(1u, ::ResumeThread(target.thread_handle())); + + EXPECT_EQ(static_cast<DWORD>(WAIT_TIMEOUT), + ::WaitForSingleObject(target.process_handle(), 2000)); + + EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0)); + + ::WaitForSingleObject(target.process_handle(), INFINITE); +} + +// Dummy target that just reports that's it spawned correctly. +SBOX_TESTS_COMMAND int PolicyTargetTest_SetEffectiveToken(int argc, + wchar_t** argv) { + return SBOX_TEST_SUCCEEDED; +} + +// Test whether after using SetEffectiveToken spawning a target works as +// expected. +TEST(PolicyTargetTest, SetEffectiveToken) { + TestRunner runner; + HANDLE token; + + // Get current process token. + EXPECT_TRUE( + ::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)); + + // Setup token guard. + base::win::ScopedHandle token_guard(token); + + // Set token and run target. + runner.GetPolicy()->SetEffectiveToken(token_guard.Get()); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"PolicyTargetTest_SetEffectiveToken")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations.cc b/security/sandbox/chromium/sandbox/win/src/process_mitigations.cc new file mode 100644 index 0000000000..557296c93f --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations.cc @@ -0,0 +1,622 @@ +// 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 "sandbox/win/src/process_mitigations.h" + +#include <stddef.h> +#include <windows.h> +#include <wow64apiset.h> + +#include <algorithm> + +#include "base/win/windows_version.h" +#include "build/build_config.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/sandbox_rand.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +// API defined in libloaderapi.h >= Win8. +using SetDefaultDllDirectoriesFunction = decltype(&SetDefaultDllDirectories); + +// APIs defined in processthreadsapi.h >= Win8. +using SetProcessMitigationPolicyFunction = + decltype(&SetProcessMitigationPolicy); +using GetProcessMitigationPolicyFunction = + decltype(&GetProcessMitigationPolicy); +using SetThreadInformationFunction = decltype(&SetThreadInformation); + +// Returns a two-element array of mitigation flags supported on this machine. +// - This function is only useful on >= base::win::Version::WIN8. +const ULONG64* GetSupportedMitigations() { + static ULONG64 mitigations[2] = {}; + + // This static variable will only be initialized once. + if (!mitigations[0] && !mitigations[1]) { + GetProcessMitigationPolicyFunction get_process_mitigation_policy = + reinterpret_cast<GetProcessMitigationPolicyFunction>(::GetProcAddress( + ::GetModuleHandleA("kernel32.dll"), "GetProcessMitigationPolicy")); + if (get_process_mitigation_policy) { + // NOTE: the two-element-sized input array is only supported on >= Win10 + // RS2. + // If an earlier version, the second element will be left 0. + size_t mits_size = + (base::win::GetVersion() >= base::win::Version::WIN10_RS2) + ? (sizeof(mitigations[0]) * 2) + : sizeof(mitigations[0]); + if (!get_process_mitigation_policy(::GetCurrentProcess(), + ProcessMitigationOptionsMask, + &mitigations, mits_size)) { + NOTREACHED(); + } + } + } + + return &mitigations[0]; +} + +// Returns true if this is 32-bit Chrome running on ARM64 with emulation. +// Needed because ACG does not work with emulated code. +// See +// https://docs.microsoft.com/en-us/windows/uwp/porting/apps-on-arm-troubleshooting-x86. +// See https://crbug.com/977723. +// TODO(wfh): Move this code into base. See https://crbug.com/978257. +bool IsRunning32bitEmulatedOnArm64() { +#if defined(ARCH_CPU_X86) + using IsWow64Process2Function = decltype(&IsWow64Process2); + + IsWow64Process2Function is_wow64_process2 = + reinterpret_cast<IsWow64Process2Function>(::GetProcAddress( + ::GetModuleHandleA("kernel32.dll"), "IsWow64Process2")); + if (!is_wow64_process2) + return false; + USHORT process_machine; + USHORT native_machine; + bool retval = is_wow64_process2(::GetCurrentProcess(), &process_machine, + &native_machine); + if (!retval) + return false; + if (native_machine == IMAGE_FILE_MACHINE_ARM64) + return true; +#endif // defined(ARCH_CPU_X86) + return false; +} + +// Returns true if user-mode Hardware-enforced Stack Protection is available for +// the Win32 environment. +bool IsUserCetWin32Available() { + static bool cetAvailable = []() -> bool { + using IsUserCetAvailableInEnvironmentFunction = + decltype(&IsUserCetAvailableInEnvironment); + + IsUserCetAvailableInEnvironmentFunction is_user_cet_available = + reinterpret_cast<IsUserCetAvailableInEnvironmentFunction>( + ::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), + "IsUserCetAvailableInEnvironment")); + if (!is_user_cet_available) { + return false; + } + + return is_user_cet_available(USER_CET_ENVIRONMENT_WIN32_PROCESS); + }(); + + return cetAvailable; +} + +} // namespace + +namespace sandbox { + +bool ApplyProcessMitigationsToCurrentProcess(MitigationFlags flags) { + if (!CanSetProcessMitigationsPostStartup(flags)) + return false; + + base::win::Version version = base::win::GetVersion(); + HMODULE module = ::GetModuleHandleA("kernel32.dll"); + + if (flags & MITIGATION_DLL_SEARCH_ORDER) { + SetDefaultDllDirectoriesFunction set_default_dll_directories = + reinterpret_cast<SetDefaultDllDirectoriesFunction>( + ::GetProcAddress(module, "SetDefaultDllDirectories")); + + // Check for SetDefaultDllDirectories since it requires KB2533623. + if (set_default_dll_directories) { + if (!set_default_dll_directories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + } + + // Set the heap to terminate on corruption + if (flags & MITIGATION_HEAP_TERMINATE) { + if (!::HeapSetInformation(nullptr, HeapEnableTerminationOnCorruption, + nullptr, 0) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + if (flags & MITIGATION_HARDEN_TOKEN_IL_POLICY) { + DWORD error = HardenProcessIntegrityLevelPolicy(); + if ((error != ERROR_SUCCESS) && (error != ERROR_ACCESS_DENIED)) + return false; + } + +#if !defined(_WIN64) // DEP is always enabled on 64-bit. + if (flags & MITIGATION_DEP) { + DWORD dep_flags = PROCESS_DEP_ENABLE; + + if (flags & MITIGATION_DEP_NO_ATL_THUNK) + dep_flags |= PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION; + + if (!::SetProcessDEPPolicy(dep_flags) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } +#endif + + // This is all we can do in Win7 and below. + if (version < base::win::Version::WIN8) + return true; + + SetProcessMitigationPolicyFunction set_process_mitigation_policy = + reinterpret_cast<SetProcessMitigationPolicyFunction>( + ::GetProcAddress(module, "SetProcessMitigationPolicy")); + if (!set_process_mitigation_policy) + return false; + + // Enable ASLR policies. + if (flags & MITIGATION_RELOCATE_IMAGE) { + PROCESS_MITIGATION_ASLR_POLICY policy = {}; + policy.EnableForceRelocateImages = true; + policy.DisallowStrippedImages = + (flags & MITIGATION_RELOCATE_IMAGE_REQUIRED) == + MITIGATION_RELOCATE_IMAGE_REQUIRED; + + if (!set_process_mitigation_policy(ProcessASLRPolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + // Enable strict handle policies. + if (flags & MITIGATION_STRICT_HANDLE_CHECKS) { + PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY policy = {}; + policy.HandleExceptionsPermanentlyEnabled = + policy.RaiseExceptionOnInvalidHandleReference = true; + + if (!set_process_mitigation_policy(ProcessStrictHandleCheckPolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + // Enable system call policies. + if (flags & MITIGATION_WIN32K_DISABLE) { + PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY policy = {}; + policy.DisallowWin32kSystemCalls = true; + + if (!set_process_mitigation_policy(ProcessSystemCallDisablePolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + // Enable extension point policies. + if (flags & MITIGATION_EXTENSION_POINT_DISABLE) { + PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY policy = {}; + policy.DisableExtensionPoints = true; + + if (!set_process_mitigation_policy(ProcessExtensionPointDisablePolicy, + &policy, sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + if (version < base::win::Version::WIN8_1) + return true; + + // Enable dynamic code policies. + if (!IsRunning32bitEmulatedOnArm64() && + (flags & MITIGATION_DYNAMIC_CODE_DISABLE)) { + // Verify caller is not accidentally setting both mutually exclusive + // policies. + DCHECK(!(flags & MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT)); + PROCESS_MITIGATION_DYNAMIC_CODE_POLICY policy = {}; + policy.ProhibitDynamicCode = true; + + if (!set_process_mitigation_policy(ProcessDynamicCodePolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + if (version < base::win::Version::WIN10) + return true; + + // Enable font policies. + if (flags & MITIGATION_NONSYSTEM_FONT_DISABLE) { + PROCESS_MITIGATION_FONT_DISABLE_POLICY policy = {}; + policy.DisableNonSystemFonts = true; + + if (!set_process_mitigation_policy(ProcessFontDisablePolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + if (version < base::win::Version::WIN10_TH2) + return true; + + // Enable binary signing policies. + if (flags & MITIGATION_FORCE_MS_SIGNED_BINS) { + PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY policy = {}; + // Allow only MS signed binaries. + policy.MicrosoftSignedOnly = true; + // NOTE: there are two other flags available to allow + // 1) Only Windows Store signed. + // 2) MS-signed, Win Store signed, and WHQL signed binaries. + // Support not added at the moment. + if (!set_process_mitigation_policy(ProcessSignaturePolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + // Enable image load policies. + if (flags & MITIGATION_IMAGE_LOAD_NO_REMOTE || + flags & MITIGATION_IMAGE_LOAD_NO_LOW_LABEL || + flags & MITIGATION_IMAGE_LOAD_PREFER_SYS32) { + PROCESS_MITIGATION_IMAGE_LOAD_POLICY policy = {}; + if (flags & MITIGATION_IMAGE_LOAD_NO_REMOTE) + policy.NoRemoteImages = true; + if (flags & MITIGATION_IMAGE_LOAD_NO_LOW_LABEL) + policy.NoLowMandatoryLabelImages = true; + // PreferSystem32 is only supported on >= Anniversary. + if (version >= base::win::Version::WIN10_RS1 && + flags & MITIGATION_IMAGE_LOAD_PREFER_SYS32) { + policy.PreferSystem32Images = true; + } + + if (!set_process_mitigation_policy(ProcessImageLoadPolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + if (version < base::win::Version::WIN10_RS1) + return true; + + // Enable dynamic code policies. + // Per-thread opt-out is only supported on >= Anniversary (RS1). + if (!IsRunning32bitEmulatedOnArm64() && + (flags & MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT)) { + // Verify caller is not accidentally setting both mutually exclusive + // policies. + DCHECK(!(flags & MITIGATION_DYNAMIC_CODE_DISABLE)); + PROCESS_MITIGATION_DYNAMIC_CODE_POLICY policy = {}; + policy.ProhibitDynamicCode = true; + policy.AllowThreadOptOut = true; + + if (!set_process_mitigation_policy(ProcessDynamicCodePolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + return true; +} + +bool ApplyMitigationsToCurrentThread(MitigationFlags flags) { + if (!CanSetMitigationsPerThread(flags)) + return false; + + base::win::Version version = base::win::GetVersion(); + + if (version < base::win::Version::WIN10_RS1) + return true; + + // Enable dynamic code per-thread policies. + if (flags & MITIGATION_DYNAMIC_CODE_OPT_OUT_THIS_THREAD) { + DWORD thread_policy = THREAD_DYNAMIC_CODE_ALLOW; + + // NOTE: SetThreadInformation API only exists on >= Win8. Dynamically + // get function handle. + SetThreadInformationFunction set_thread_info_function = + reinterpret_cast<SetThreadInformationFunction>(::GetProcAddress( + ::GetModuleHandleA("kernel32.dll"), "SetThreadInformation")); + if (!set_thread_info_function) + return false; + + // NOTE: Must use the pseudo-handle here, a thread HANDLE won't work. + if (!set_thread_info_function(::GetCurrentThread(), ThreadDynamicCodePolicy, + &thread_policy, sizeof(thread_policy))) { + return false; + } + } + + return true; +} + +void ConvertProcessMitigationsToPolicy(MitigationFlags flags, + DWORD64* policy_flags, + size_t* size) { + base::win::Version version = base::win::GetVersion(); + + // |policy_flags| is a two-element array of DWORD64s. Ensure mitigation flags + // from PROCESS_CREATION_MITIGATION_POLICY2_* go into the second value. If + // any flags are set in value 2, update |size| to include both elements. + DWORD64* policy_value_1 = &policy_flags[0]; + DWORD64* policy_value_2 = &policy_flags[1]; + *policy_value_1 = 0; + *policy_value_2 = 0; + +#if defined(_WIN64) + *size = sizeof(*policy_flags); +#elif defined(_M_IX86) + // A 64-bit flags attribute is illegal on 32-bit Win 7. + if (version < base::win::Version::WIN8) + *size = sizeof(DWORD); + else + *size = sizeof(*policy_flags); +#else +#error This platform is not supported. +#endif + +// DEP and SEHOP are not valid for 64-bit Windows +#if !defined(_WIN64) + if (flags & MITIGATION_DEP) { + *policy_value_1 |= PROCESS_CREATION_MITIGATION_POLICY_DEP_ENABLE; + if (!(flags & MITIGATION_DEP_NO_ATL_THUNK)) + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_DEP_ATL_THUNK_ENABLE; + } + + if (flags & MITIGATION_SEHOP) + *policy_value_1 |= PROCESS_CREATION_MITIGATION_POLICY_SEHOP_ENABLE; +#endif + + // Win 7 + if (version < base::win::Version::WIN8) + return; + + // Everything >= Win8, do not return before the end of the function where + // the final policy bitmap is sanity checked against what is supported on this + // machine. The API required to do so is only available since Win8. + + // Mitigations >= Win8: + //---------------------------------------------------------------------------- + if (version >= base::win::Version::WIN8) { + if (flags & MITIGATION_RELOCATE_IMAGE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON; + if (flags & MITIGATION_RELOCATE_IMAGE_REQUIRED) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON_REQ_RELOCS; + } + } + + if (flags & MITIGATION_HEAP_TERMINATE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_HEAP_TERMINATE_ALWAYS_ON; + } + + if (flags & MITIGATION_BOTTOM_UP_ASLR) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_BOTTOM_UP_ASLR_ALWAYS_ON; + } + + if (flags & MITIGATION_HIGH_ENTROPY_ASLR) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_HIGH_ENTROPY_ASLR_ALWAYS_ON; + } + + if (flags & MITIGATION_STRICT_HANDLE_CHECKS) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_STRICT_HANDLE_CHECKS_ALWAYS_ON; + } + + if (flags & MITIGATION_WIN32K_DISABLE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON; + } + + if (flags & MITIGATION_EXTENSION_POINT_DISABLE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_EXTENSION_POINT_DISABLE_ALWAYS_ON; + } + } + + // Mitigations >= Win8.1: + //---------------------------------------------------------------------------- + if (version >= base::win::Version::WIN8_1) { + if (flags & MITIGATION_DYNAMIC_CODE_DISABLE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON; + } + + if (flags & MITIGATION_CONTROL_FLOW_GUARD_DISABLE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF; + } + } + + // Mitigations >= Win10: + //---------------------------------------------------------------------------- + if (version >= base::win::Version::WIN10) { + if (flags & MITIGATION_NONSYSTEM_FONT_DISABLE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_FONT_DISABLE_ALWAYS_ON; + } + } + + // Mitigations >= Win10 TH2: + //---------------------------------------------------------------------------- + if (version >= base::win::Version::WIN10_TH2) { + if (flags & MITIGATION_FORCE_MS_SIGNED_BINS) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON; + } + + if (flags & MITIGATION_IMAGE_LOAD_NO_REMOTE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_NO_REMOTE_ALWAYS_ON; + } + + if (flags & MITIGATION_IMAGE_LOAD_NO_LOW_LABEL) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_NO_LOW_LABEL_ALWAYS_ON; + } + } + + // Mitigations >= Win10 RS1 ("Anniversary"): + //---------------------------------------------------------------------------- + if (version >= base::win::Version::WIN10_RS1) { + if (flags & MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON_ALLOW_OPT_OUT; + } + + if (flags & MITIGATION_IMAGE_LOAD_PREFER_SYS32) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON; + } + } + + // Mitigations >= Win10 RS3 ("Fall Creator's"): + //---------------------------------------------------------------------------- + if (version >= base::win::Version::WIN10_RS3) { + // Note: This mitigation requires not only Win10 1709, but also the January + // 2018 security updates and any applicable firmware updates from the + // OEM device manufacturer. + // Note: Applying this mitigation attribute on creation will succeed, even + // if + // the underlying hardware does not support the implementation. + // Windows just does its best under the hood for the given hardware. + if (flags & MITIGATION_RESTRICT_INDIRECT_BRANCH_PREDICTION) { + *policy_value_2 |= + PROCESS_CREATION_MITIGATION_POLICY2_RESTRICT_INDIRECT_BRANCH_PREDICTION_ALWAYS_ON; + } + } + + // Mitigations >= Win10 20H1 + //---------------------------------------------------------------------------- + if (version >= base::win::Version::WIN10_20H1) { + if (flags & MITIGATION_CET_COMPAT_MODE && IsUserCetWin32Available()) { + *policy_value_2 |= + PROCESS_CREATION_MITIGATION_POLICY2_CET_USER_SHADOW_STACKS_ALWAYS_ON; + } + } + + // When done setting policy flags, sanity check supported policies on this + // machine, and then update |size|. + + const ULONG64* supported = GetSupportedMitigations(); + + *policy_value_1 = *policy_value_1 & supported[0]; + *policy_value_2 = *policy_value_2 & supported[1]; + + // Only include the second element in |size| if it is non-zero. Else, + // UpdateProcThreadAttribute() will return a failure when setting policies. + if (*policy_value_2 && version >= base::win::Version::WIN10_RS2) { + *size = sizeof(*policy_flags) * 2; + } + + return; +} + +MitigationFlags FilterPostStartupProcessMitigations(MitigationFlags flags) { + base::win::Version version = base::win::GetVersion(); + + // Windows 7. + if (version < base::win::Version::WIN8) { + return flags & (MITIGATION_BOTTOM_UP_ASLR | MITIGATION_DLL_SEARCH_ORDER | + MITIGATION_HEAP_TERMINATE); + } + + // Windows 8 and above. + return flags & (MITIGATION_BOTTOM_UP_ASLR | MITIGATION_DLL_SEARCH_ORDER); +} + +bool ApplyProcessMitigationsToSuspendedProcess(HANDLE process, + MitigationFlags flags) { +// This is a hack to fake a weak bottom-up ASLR on 32-bit Windows. +#if !defined(_WIN64) + if (flags & MITIGATION_BOTTOM_UP_ASLR) { + unsigned int limit; + GetRandom(&limit); + char* ptr = 0; + const size_t kMask64k = 0xFFFF; + // Random range (512k-16.5mb) in 64k steps. + const char* end = ptr + ((((limit % 16384) + 512) * 1024) & ~kMask64k); + while (ptr < end) { + MEMORY_BASIC_INFORMATION memory_info; + if (!::VirtualQueryEx(process, ptr, &memory_info, sizeof(memory_info))) + break; + size_t size = std::min((memory_info.RegionSize + kMask64k) & ~kMask64k, + static_cast<SIZE_T>(end - ptr)); + if (ptr && memory_info.State == MEM_FREE) + ::VirtualAllocEx(process, ptr, size, MEM_RESERVE, PAGE_NOACCESS); + ptr += size; + } + } +#endif + + return true; +} + +MitigationFlags GetAllowedPostStartupProcessMitigations() { + return MITIGATION_HEAP_TERMINATE | + MITIGATION_DEP | + MITIGATION_DEP_NO_ATL_THUNK | + MITIGATION_RELOCATE_IMAGE | + MITIGATION_RELOCATE_IMAGE_REQUIRED | + MITIGATION_BOTTOM_UP_ASLR | + MITIGATION_STRICT_HANDLE_CHECKS | + MITIGATION_EXTENSION_POINT_DISABLE | + MITIGATION_DLL_SEARCH_ORDER | + MITIGATION_HARDEN_TOKEN_IL_POLICY | + MITIGATION_WIN32K_DISABLE | + MITIGATION_DYNAMIC_CODE_DISABLE | + MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT | + MITIGATION_FORCE_MS_SIGNED_BINS | + MITIGATION_NONSYSTEM_FONT_DISABLE | + MITIGATION_IMAGE_LOAD_NO_REMOTE | + MITIGATION_IMAGE_LOAD_NO_LOW_LABEL | + MITIGATION_IMAGE_LOAD_PREFER_SYS32; +} + +bool CanSetProcessMitigationsPostStartup(MitigationFlags flags) { + // All of these mitigations can be enabled after startup. + return !(flags & ~GetAllowedPostStartupProcessMitigations()); +} + +bool CanSetProcessMitigationsPreStartup(MitigationFlags flags) { + // These mitigations cannot be enabled prior to startup. + return !(flags & + (MITIGATION_STRICT_HANDLE_CHECKS | MITIGATION_DLL_SEARCH_ORDER)); +} + +bool CanSetMitigationsPerThread(MitigationFlags flags) { + // If any flags EXCEPT these are set, fail. + if (flags & ~(MITIGATION_DYNAMIC_CODE_OPT_OUT_THIS_THREAD)) + return false; + + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations.h b/security/sandbox/chromium/sandbox/win/src/process_mitigations.h new file mode 100644 index 0000000000..26dc42e358 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations.h @@ -0,0 +1,56 @@ +// 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. + +#ifndef SANDBOX_SRC_WIN_PROCESS_MITIGATIONS_H_ +#define SANDBOX_SRC_WIN_PROCESS_MITIGATIONS_H_ + +#include <windows.h> + +#include <stddef.h> + +#include "sandbox/win/src/security_level.h" + +namespace sandbox { + +// Sets the mitigation policy for the current process, ignoring any settings +// that are invalid for the current version of Windows. +bool ApplyProcessMitigationsToCurrentProcess(MitigationFlags flags); + +// Sets the mitigation policy for the current thread, ignoring any settings +// that are invalid for the current version of Windows. +bool ApplyMitigationsToCurrentThread(MitigationFlags flags); + +// Returns the flags that must be enforced after startup for the current OS +// version. +MitigationFlags FilterPostStartupProcessMitigations(MitigationFlags flags); + +// Converts sandbox flags to the PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES +// policy flags used by UpdateProcThreadAttribute(). +// - |policy_flags| must be a two-element DWORD64 array. +// - |size| is a size_t so that it can be passed directly into +// UpdateProcThreadAttribute(). +void ConvertProcessMitigationsToPolicy(MitigationFlags flags, + DWORD64* policy_flags, + size_t* size); + +// Adds mitigations that need to be performed on the suspended target process +// before execution begins. +bool ApplyProcessMitigationsToSuspendedProcess(HANDLE process, + MitigationFlags flags); + +// Returns the list of process mitigations which can be enabled post startup. +MitigationFlags GetAllowedPostStartupProcessMitigations(); + +// Returns true if all the supplied flags can be set after a process starts. +bool CanSetProcessMitigationsPostStartup(MitigationFlags flags); + +// Returns true if all the supplied flags can be set before a process starts. +bool CanSetProcessMitigationsPreStartup(MitigationFlags flags); + +// Returns true if all the supplied flags can be set on the current thread. +bool CanSetMitigationsPerThread(MitigationFlags flags); + +} // namespace sandbox + +#endif // SANDBOX_SRC_WIN_PROCESS_MITIGATIONS_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.cc new file mode 100644 index 0000000000..4d2d94b865 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.cc @@ -0,0 +1,592 @@ +// Copyright 2014 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/win/src/process_mitigations_win32k_dispatcher.h" + +#include <algorithm> +#include <string> + +#include "base/memory/platform_shared_memory_region.h" +#include "base/memory/unsafe_shared_memory_region.h" +#include "base/unguessable_token.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/process_mitigations_win32k_interception.h" +#include "sandbox/win/src/process_mitigations_win32k_policy.h" + +namespace sandbox { + +namespace { + +base::UnsafeSharedMemoryRegion GetSharedMemoryRegion( + const ClientInfo& client_info, + HANDLE handle, + size_t size) { + HANDLE dup_handle = nullptr; + intptr_t handle_int = reinterpret_cast<intptr_t>(handle); + if (handle_int <= 0 || + !::DuplicateHandle(client_info.process, handle, ::GetCurrentProcess(), + &dup_handle, 0, false, DUPLICATE_SAME_ACCESS)) { + return {}; + } + // The raw handle returned from ::DuplicateHandle() must be wrapped in a + // base::PlatformSharedMemoryRegion. + base::subtle::PlatformSharedMemoryRegion platform_region = + base::subtle::PlatformSharedMemoryRegion::Take( + base::win::ScopedHandle(dup_handle), + base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe, size, + base::UnguessableToken::Create()); + // |platform_region| can now be wrapped in a base::UnsafeSharedMemoryRegion. + return base::UnsafeSharedMemoryRegion::Deserialize( + std::move(platform_region)); +} + +} // namespace + +ProtectedVideoOutput::~ProtectedVideoOutput() { + ProcessMitigationsWin32KLockdownPolicy::DestroyOPMProtectedOutputAction( + handle_); +} + +scoped_refptr<ProtectedVideoOutput> +ProcessMitigationsWin32KDispatcher::GetProtectedVideoOutput( + HANDLE handle, + bool destroy_output) { + base::AutoLock lock(protected_outputs_lock_); + scoped_refptr<ProtectedVideoOutput> result; + auto it = protected_outputs_.find(handle); + if (it != protected_outputs_.end()) { + result = it->second; + if (destroy_output) + protected_outputs_.erase(it); + } + return result; +} + +ProcessMitigationsWin32KDispatcher::ProcessMitigationsWin32KDispatcher( + PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall enum_display_monitors_params = { + {IpcTag::USER_ENUMDISPLAYMONITORS, {INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::EnumDisplayMonitors)}; + static const IPCCall get_monitor_info_params = { + {IpcTag::USER_GETMONITORINFO, {VOIDPTR_TYPE, INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::GetMonitorInfo)}; + static const IPCCall get_suggested_output_size_params = { + {IpcTag::GDI_GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE, {WCHAR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher:: + GetSuggestedOPMProtectedOutputArraySize)}; + static const IPCCall create_protected_outputs_params = { + {IpcTag::GDI_CREATEOPMPROTECTEDOUTPUTS, {WCHAR_TYPE, INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::CreateOPMProtectedOutputs)}; + static const IPCCall get_cert_size_params = { + {IpcTag::GDI_GETCERTIFICATESIZE, {WCHAR_TYPE, VOIDPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::GetCertificateSize)}; + static const IPCCall get_cert_params = { + {IpcTag::GDI_GETCERTIFICATE, + {WCHAR_TYPE, VOIDPTR_TYPE, VOIDPTR_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::GetCertificate)}; + static const IPCCall destroy_protected_output_params = { + {IpcTag::GDI_DESTROYOPMPROTECTEDOUTPUT, {VOIDPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::DestroyOPMProtectedOutput)}; + static const IPCCall get_random_number_params = { + {IpcTag::GDI_GETOPMRANDOMNUMBER, {VOIDPTR_TYPE, INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::GetOPMRandomNumber)}; + static const IPCCall set_signing_key_params = { + {IpcTag::GDI_SETOPMSIGNINGKEYANDSEQUENCENUMBERS, + {VOIDPTR_TYPE, INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher:: + SetOPMSigningKeyAndSequenceNumbers)}; + static const IPCCall configure_protected_output_params = { + {IpcTag::GDI_CONFIGUREOPMPROTECTEDOUTPUT, {VOIDPTR_TYPE, VOIDPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::ConfigureOPMProtectedOutput)}; + static const IPCCall get_information_params = { + {IpcTag::GDI_GETOPMINFORMATION, {VOIDPTR_TYPE, VOIDPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::GetOPMInformation)}; + + ipc_calls_.push_back(enum_display_monitors_params); + ipc_calls_.push_back(get_monitor_info_params); + ipc_calls_.push_back(get_suggested_output_size_params); + ipc_calls_.push_back(create_protected_outputs_params); + ipc_calls_.push_back(get_cert_size_params); + ipc_calls_.push_back(get_cert_params); + ipc_calls_.push_back(destroy_protected_output_params); + ipc_calls_.push_back(get_random_number_params); + ipc_calls_.push_back(set_signing_key_params); + ipc_calls_.push_back(configure_protected_output_params); + ipc_calls_.push_back(get_information_params); +} + +ProcessMitigationsWin32KDispatcher::~ProcessMitigationsWin32KDispatcher() {} + +bool ProcessMitigationsWin32KDispatcher::SetupService( + InterceptionManager* manager, + IpcTag service) { + if (!(policy_base_->GetProcessMitigations() & + sandbox::MITIGATION_WIN32K_DISABLE)) { + return false; + } + + switch (service) { + case IpcTag::GDI_GDIDLLINITIALIZE: { + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GdiDllInitialize, + GDIINITIALIZE_ID, 12)) { + return false; + } + return true; + } + + case IpcTag::GDI_GETSTOCKOBJECT: { + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetStockObject, + GETSTOCKOBJECT_ID, 8)) { + return false; + } + return true; + } + + case IpcTag::USER_REGISTERCLASSW: { + if (!INTERCEPT_EAT(manager, L"user32.dll", RegisterClassW, + REGISTERCLASSW_ID, 8)) { + return false; + } + return true; + } + + case IpcTag::USER_ENUMDISPLAYMONITORS: { + if (!INTERCEPT_EAT(manager, L"user32.dll", EnumDisplayMonitors, + ENUMDISPLAYMONITORS_ID, 20)) { + return false; + } + return true; + } + + case IpcTag::USER_ENUMDISPLAYDEVICES: { + if (!INTERCEPT_EAT(manager, L"user32.dll", EnumDisplayDevicesA, + ENUMDISPLAYDEVICESA_ID, 20)) { + return false; + } + return true; + } + + case IpcTag::USER_GETMONITORINFO: { + if (!INTERCEPT_EAT(manager, L"user32.dll", GetMonitorInfoA, + GETMONITORINFOA_ID, 12)) { + return false; + } + + if (!INTERCEPT_EAT(manager, L"user32.dll", GetMonitorInfoW, + GETMONITORINFOW_ID, 12)) { + return false; + } + return true; + } + + case IpcTag::GDI_CREATEOPMPROTECTEDOUTPUTS: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", CreateOPMProtectedOutputs, + CREATEOPMPROTECTEDOUTPUTS_ID, 24)) { + return false; + } + return true; + + case IpcTag::GDI_GETCERTIFICATE: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetCertificate, + GETCERTIFICATE_ID, 20)) { + return false; + } + if (base::win::GetVersion() < base::win::Version::WIN10_TH2) + return true; + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetCertificateByHandle, + GETCERTIFICATEBYHANDLE_ID, 20)) { + return false; + } + return true; + + case IpcTag::GDI_GETCERTIFICATESIZE: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetCertificateSize, + GETCERTIFICATESIZE_ID, 16)) { + return false; + } + if (base::win::GetVersion() < base::win::Version::WIN10_TH2) + return true; + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetCertificateSizeByHandle, + GETCERTIFICATESIZEBYHANDLE_ID, 16)) { + return false; + } + return true; + + case IpcTag::GDI_DESTROYOPMPROTECTEDOUTPUT: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", DestroyOPMProtectedOutput, + DESTROYOPMPROTECTEDOUTPUT_ID, 8)) { + return false; + } + return true; + + case IpcTag::GDI_CONFIGUREOPMPROTECTEDOUTPUT: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", ConfigureOPMProtectedOutput, + CONFIGUREOPMPROTECTEDOUTPUT_ID, 20)) { + return false; + } + return true; + + case IpcTag::GDI_GETOPMINFORMATION: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetOPMInformation, + GETOPMINFORMATION_ID, 16)) { + return false; + } + return true; + + case IpcTag::GDI_GETOPMRANDOMNUMBER: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetOPMRandomNumber, + GETOPMRANDOMNUMBER_ID, 12)) { + return false; + } + return true; + + case IpcTag::GDI_GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", + GetSuggestedOPMProtectedOutputArraySize, + GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE_ID, 12)) { + return false; + } + return true; + + case IpcTag::GDI_SETOPMSIGNINGKEYANDSEQUENCENUMBERS: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", + SetOPMSigningKeyAndSequenceNumbers, + SETOPMSIGNINGKEYANDSEQUENCENUMBERS_ID, 12)) { + return false; + } + return true; + + default: + break; + } + return false; +} + +bool ProcessMitigationsWin32KDispatcher::EnumDisplayMonitors( + IPCInfo* ipc, + CountedBuffer* buffer) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.win32_result = ERROR_ACCESS_DENIED; + return true; + } + + if (buffer->Size() != sizeof(EnumMonitorsResult)) { + ipc->return_info.win32_result = ERROR_INVALID_PARAMETER; + return true; + } + HMONITOR monitor_list[kMaxEnumMonitors] = {}; + + uint32_t monitor_list_count = + ProcessMitigationsWin32KLockdownPolicy::EnumDisplayMonitorsAction( + *ipc->client_info, monitor_list, kMaxEnumMonitors); + DCHECK(monitor_list_count <= kMaxEnumMonitors); + + EnumMonitorsResult* result = + static_cast<EnumMonitorsResult*>(buffer->Buffer()); + for (uint32_t monitor_pos = 0; monitor_pos < monitor_list_count; + ++monitor_pos) { + result->monitors[monitor_pos] = monitor_list[monitor_pos]; + } + result->monitor_count = monitor_list_count; + ipc->return_info.win32_result = 0; + + return true; +} + +bool ProcessMitigationsWin32KDispatcher::GetMonitorInfo(IPCInfo* ipc, + void* monitor, + CountedBuffer* buffer) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.win32_result = ERROR_ACCESS_DENIED; + return true; + } + if (buffer->Size() != sizeof(MONITORINFOEXW)) { + ipc->return_info.win32_result = ERROR_INVALID_PARAMETER; + return true; + } + MONITORINFO* monitor_info = static_cast<MONITORINFO*>(buffer->Buffer()); + // Ensure size is valid and represents what we've been passed. + monitor_info->cbSize = buffer->Size(); + HMONITOR monitor_handle = static_cast<HMONITOR>(monitor); + bool success = ProcessMitigationsWin32KLockdownPolicy::GetMonitorInfoAction( + *ipc->client_info, monitor_handle, monitor_info); + ipc->return_info.win32_result = + success ? ERROR_SUCCESS : ERROR_INVALID_PARAMETER; + return true; +} + +bool ProcessMitigationsWin32KDispatcher:: + GetSuggestedOPMProtectedOutputArraySize(IPCInfo* ipc, + std::wstring* device_name) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + NTSTATUS status = ProcessMitigationsWin32KLockdownPolicy:: + GetSuggestedOPMProtectedOutputArraySizeAction( + *ipc->client_info, *device_name, + &ipc->return_info.extended[0].unsigned_int); + if (!status) { + ipc->return_info.extended_count = 1; + } + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::CreateOPMProtectedOutputs( + IPCInfo* ipc, + std::wstring* device_name, + CountedBuffer* protected_outputs) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + uint32_t output_array_size = 0; + uint32_t input_array_size = protected_outputs->Size() / sizeof(HANDLE); + HANDLE* handles = static_cast<HANDLE*>(protected_outputs->Buffer()); + NTSTATUS status = + ProcessMitigationsWin32KLockdownPolicy::CreateOPMProtectedOutputsAction( + *ipc->client_info, *device_name, handles, input_array_size, + &output_array_size); + if (!status && (output_array_size <= input_array_size)) { + base::AutoLock lock(protected_outputs_lock_); + ipc->return_info.extended_count = 1; + ipc->return_info.extended[0].unsigned_int = output_array_size; + for (uint32_t handle_pos = 0; handle_pos < output_array_size; + handle_pos++) { + HANDLE handle = handles[handle_pos]; + protected_outputs_[handle] = new ProtectedVideoOutput(handle); + } + } + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::GetCertificateSize( + IPCInfo* ipc, + std::wstring* device_name, + void* protected_output) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + NTSTATUS status = STATUS_INVALID_PARAMETER; + if (device_name->size() > 0) { + status = ProcessMitigationsWin32KLockdownPolicy::GetCertificateSizeAction( + *ipc->client_info, *device_name, + &ipc->return_info.extended[0].unsigned_int); + } else { + scoped_refptr<ProtectedVideoOutput> output = + GetProtectedVideoOutput(protected_output, false); + if (output) { + status = ProcessMitigationsWin32KLockdownPolicy:: + GetCertificateSizeByHandleAction( + *ipc->client_info, output.get()->handle(), + &ipc->return_info.extended[0].unsigned_int); + } + } + if (!status) { + ipc->return_info.extended_count = 1; + } + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::GetCertificate( + IPCInfo* ipc, + std::wstring* device_name, + void* protected_output, + void* shared_buffer_handle, + uint32_t shared_buffer_size) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + // Don't let caller map an arbitrarily large buffer into memory. + if (shared_buffer_size > kProtectedVideoOutputSectionSize) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + base::UnsafeSharedMemoryRegion region = GetSharedMemoryRegion( + *ipc->client_info, shared_buffer_handle, shared_buffer_size); + if (!region.IsValid()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + base::WritableSharedMemoryMapping cert_data = region.Map(); + if (!cert_data.IsValid()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + NTSTATUS status = STATUS_INVALID_PARAMETER; + if (device_name->size() > 0) { + status = ProcessMitigationsWin32KLockdownPolicy::GetCertificateAction( + *ipc->client_info, *device_name, + cert_data.GetMemoryAsSpan<BYTE>().data(), shared_buffer_size); + } else { + scoped_refptr<ProtectedVideoOutput> output = + GetProtectedVideoOutput(protected_output, false); + if (output) { + status = + ProcessMitigationsWin32KLockdownPolicy::GetCertificateByHandleAction( + *ipc->client_info, output.get()->handle(), + cert_data.GetMemoryAsSpan<BYTE>().data(), shared_buffer_size); + } + } + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::DestroyOPMProtectedOutput( + IPCInfo* ipc, + void* protected_output) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + scoped_refptr<ProtectedVideoOutput> output = + GetProtectedVideoOutput(protected_output, true); + NTSTATUS status = STATUS_INVALID_HANDLE; + if (output) + status = STATUS_SUCCESS; + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::GetOPMRandomNumber( + IPCInfo* ipc, + void* protected_output, + CountedBuffer* random_number) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + scoped_refptr<ProtectedVideoOutput> output = + GetProtectedVideoOutput(protected_output, false); + NTSTATUS status = STATUS_INVALID_PARAMETER; + if (!output || random_number->Size() != sizeof(DXGKMDT_OPM_RANDOM_NUMBER)) { + status = STATUS_INVALID_PARAMETER; + } else { + status = ProcessMitigationsWin32KLockdownPolicy::GetOPMRandomNumberAction( + *ipc->client_info, output.get()->handle(), random_number->Buffer()); + } + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::SetOPMSigningKeyAndSequenceNumbers( + IPCInfo* ipc, + void* protected_output, + CountedBuffer* parameters) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + scoped_refptr<ProtectedVideoOutput> output = + GetProtectedVideoOutput(protected_output, false); + NTSTATUS status = STATUS_INVALID_PARAMETER; + if (!output || + parameters->Size() != sizeof(DXGKMDT_OPM_ENCRYPTED_PARAMETERS)) { + status = STATUS_INVALID_PARAMETER; + } else { + status = ProcessMitigationsWin32KLockdownPolicy:: + SetOPMSigningKeyAndSequenceNumbersAction( + *ipc->client_info, output.get()->handle(), parameters->Buffer()); + } + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::ConfigureOPMProtectedOutput( + IPCInfo* ipc, + void* protected_output, + void* shared_buffer_handle) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + scoped_refptr<ProtectedVideoOutput> output = + GetProtectedVideoOutput(protected_output, false); + if (!output) { + ipc->return_info.nt_status = STATUS_INVALID_HANDLE; + return true; + }; + base::UnsafeSharedMemoryRegion region = + GetSharedMemoryRegion(*ipc->client_info, shared_buffer_handle, + sizeof(DXGKMDT_OPM_CONFIGURE_PARAMETERS)); + if (!region.IsValid()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + base::WritableSharedMemoryMapping buffer = region.Map(); + if (!buffer.IsValid()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + NTSTATUS status = + ProcessMitigationsWin32KLockdownPolicy::ConfigureOPMProtectedOutputAction( + *ipc->client_info, output.get()->handle(), buffer.memory()); + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::GetOPMInformation( + IPCInfo* ipc, + void* protected_output, + void* shared_buffer_handle) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + scoped_refptr<ProtectedVideoOutput> output = + GetProtectedVideoOutput(protected_output, false); + if (!output) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + size_t shared_buffer_size = + std::max(sizeof(DXGKMDT_OPM_GET_INFO_PARAMETERS), + sizeof(DXGKMDT_OPM_REQUESTED_INFORMATION)); + + base::UnsafeSharedMemoryRegion region = GetSharedMemoryRegion( + *ipc->client_info, shared_buffer_handle, shared_buffer_size); + if (!region.IsValid()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + base::WritableSharedMemoryMapping buffer = region.Map(); + if (!buffer.IsValid()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + DXGKMDT_OPM_REQUESTED_INFORMATION requested_info = {}; + NTSTATUS status = + ProcessMitigationsWin32KLockdownPolicy::GetOPMInformationAction( + *ipc->client_info, output.get()->handle(), buffer.memory(), + &requested_info); + if (!status) { + memcpy(buffer.memory(), &requested_info, + sizeof(DXGKMDT_OPM_REQUESTED_INFORMATION)); + } + ipc->return_info.nt_status = status; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.h new file mode 100644 index 0000000000..0714191fcf --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.h @@ -0,0 +1,89 @@ +// Copyright 2014 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. + +#ifndef SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_DISPATCHER_H_ +#define SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_DISPATCHER_H_ + +#include <map> +#include <string> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// Class to maintain a reference to a OPM protected output handle. +class ProtectedVideoOutput + : public base::RefCountedThreadSafe<ProtectedVideoOutput> { + public: + ProtectedVideoOutput(HANDLE handle) : handle_(handle) {} + HANDLE handle() { return handle_; } + + private: + friend class base::RefCountedThreadSafe<ProtectedVideoOutput>; + ~ProtectedVideoOutput(); + + HANDLE handle_; + + DISALLOW_COPY_AND_ASSIGN(ProtectedVideoOutput); +}; + +// This class sets up intercepts for the Win32K lockdown policy which is set +// on Windows 8 and beyond. +class ProcessMitigationsWin32KDispatcher : public Dispatcher { + public: + explicit ProcessMitigationsWin32KDispatcher(PolicyBase* policy_base); + ~ProcessMitigationsWin32KDispatcher() override; + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + bool EnumDisplayMonitors(IPCInfo* ipc, CountedBuffer* buffer); + bool GetMonitorInfo(IPCInfo* ipc, void* monitor, CountedBuffer* buffer); + bool GetSuggestedOPMProtectedOutputArraySize(IPCInfo* ipc, + std::wstring* device_name); + bool CreateOPMProtectedOutputs(IPCInfo* ipc, + std::wstring* device_name, + CountedBuffer* protected_outputs); + bool GetCertificateSize(IPCInfo* ipc, + std::wstring* device_name, + void* protected_output); + bool GetCertificate(IPCInfo* ipc, + std::wstring* device_name, + void* protected_output, + void* shared_buffer_handle, + uint32_t shared_buffer_size); + bool DestroyOPMProtectedOutput(IPCInfo* ipc, void* protected_output); + bool GetOPMRandomNumber(IPCInfo* ipc, + void* protected_output, + CountedBuffer* random_number); + bool SetOPMSigningKeyAndSequenceNumbers(IPCInfo* ipc, + void* protected_output, + CountedBuffer* parameters); + bool ConfigureOPMProtectedOutput(IPCInfo* ipc, + void* protected_output, + void* shared_buffer_handle); + bool GetOPMInformation(IPCInfo* ipc, + void* protected_output, + void* shared_buffer_handle); + + private: + scoped_refptr<ProtectedVideoOutput> GetProtectedVideoOutput( + HANDLE handle, + bool destroy_output); + + PolicyBase* policy_base_; + std::map<HANDLE, scoped_refptr<ProtectedVideoOutput>> protected_outputs_; + base::Lock protected_outputs_lock_; + + DISALLOW_COPY_AND_ASSIGN(ProcessMitigationsWin32KDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_DISPATCHER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_interception.cc b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_interception.cc new file mode 100644 index 0000000000..1a16d85adf --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_interception.cc @@ -0,0 +1,523 @@ +// Copyright 2014 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/win/src/process_mitigations_win32k_interception.h" + +#include <algorithm> + +#include "base/numerics/safe_conversions.h" +#include "base/numerics/safe_math.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +namespace { + +// Implement a simple shared memory class as we can't use the base one. +class ScopedSharedMemory { + public: + ScopedSharedMemory(uint32_t size) : memory_(nullptr) { + handle_.Set(::CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, + PAGE_READWRITE | SEC_COMMIT, 0, size, + nullptr)); + if (handle_.IsValid()) { + memory_ = ::MapViewOfFile(handle_.Get(), FILE_MAP_READ | FILE_MAP_WRITE, + 0, 0, size); + } + } + ~ScopedSharedMemory() { + if (memory_) + ::UnmapViewOfFile(memory_); + } + + void* handle() { return handle_.Get(); } + void* memory() { return memory_; } + bool IsValid() { return handle_.IsValid() && memory_; } + + private: + base::win::ScopedHandle handle_; + void* memory_; +}; + +void UnicodeStringToString(PUNICODE_STRING unicode_string, + std::wstring* result) { + *result = std::wstring( + unicode_string->Buffer, + unicode_string->Buffer + + (unicode_string->Length / sizeof(unicode_string->Buffer[0]))); +} + +bool CallMonitorInfo(HMONITOR monitor, MONITORINFOEXW* monitor_info_ptr) { + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return false; + + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return false; + + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + InOutCountedBuffer buffer(monitor_info_ptr, sizeof(*monitor_info_ptr)); + ResultCode code = CrossCall(ipc, IpcTag::USER_GETMONITORINFO, + static_cast<void*>(monitor), buffer, &answer); + + if (code != SBOX_ALL_OK) + return false; + + if (answer.win32_result != ERROR_SUCCESS) + return false; + + return true; +} + +} // namespace + +BOOL WINAPI +TargetGdiDllInitialize(GdiDllInitializeFunction orig_gdi_dll_initialize, + HANDLE dll, + DWORD reason) { + return true; +} + +HGDIOBJ WINAPI +TargetGetStockObject(GetStockObjectFunction orig_get_stock_object, int object) { + return nullptr; +} + +ATOM WINAPI +TargetRegisterClassW(RegisterClassWFunction orig_register_class_function, + const WNDCLASS* wnd_class) { + return true; +} + +BOOL WINAPI TargetEnumDisplayMonitors(EnumDisplayMonitorsFunction, + HDC hdc, + LPCRECT lprcClip, + MONITORENUMPROC lpfnEnum, + LPARAM dwData) { + if (!lpfnEnum || hdc || lprcClip) { + return false; + } + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return false; + + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return false; + + CrossCallReturn answer = {0}; + answer.nt_status = 0; + EnumMonitorsResult result = {}; + InOutCountedBuffer result_buffer(&result, sizeof(result)); + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = + CrossCall(ipc, IpcTag::USER_ENUMDISPLAYMONITORS, result_buffer, &answer); + + if (code != SBOX_ALL_OK) + return false; + + if (answer.win32_result) + return false; + + if (result.monitor_count > kMaxEnumMonitors) + return false; + + for (uint32_t monitor_pos = 0; monitor_pos < result.monitor_count; + ++monitor_pos) { + bool continue_enum = + lpfnEnum(result.monitors[monitor_pos], nullptr, nullptr, dwData); + if (!continue_enum) + return false; + } + + return true; +} + +BOOL WINAPI TargetEnumDisplayDevicesA(EnumDisplayDevicesAFunction, + LPCSTR lpDevice, + DWORD iDevNum, + PDISPLAY_DEVICEA lpDisplayDevice, + DWORD dwFlags) { + return false; +} + +BOOL WINAPI TargetGetMonitorInfoA(GetMonitorInfoAFunction, + HMONITOR monitor, + MONITORINFO* monitor_info_ptr) { + if (!monitor_info_ptr) + return false; + DWORD size = monitor_info_ptr->cbSize; + if (size != sizeof(MONITORINFO) && size != sizeof(MONITORINFOEXA)) + return false; + MONITORINFOEXW monitor_info_tmp = {}; + monitor_info_tmp.cbSize = sizeof(monitor_info_tmp); + bool success = CallMonitorInfo(monitor, &monitor_info_tmp); + if (!success) + return false; + memcpy(monitor_info_ptr, &monitor_info_tmp, sizeof(*monitor_info_ptr)); + if (size == sizeof(MONITORINFOEXA)) { + MONITORINFOEXA* monitor_info_exa = + reinterpret_cast<MONITORINFOEXA*>(monitor_info_ptr); + if (!::WideCharToMultiByte(CP_ACP, 0, monitor_info_tmp.szDevice, -1, + monitor_info_exa->szDevice, + sizeof(monitor_info_exa->szDevice), nullptr, + nullptr)) { + return false; + } + } + return true; +} + +BOOL WINAPI TargetGetMonitorInfoW(GetMonitorInfoWFunction, + HMONITOR monitor, + LPMONITORINFO monitor_info_ptr) { + if (!monitor_info_ptr) + return false; + DWORD size = monitor_info_ptr->cbSize; + if (size != sizeof(MONITORINFO) && size != sizeof(MONITORINFOEXW)) + return false; + MONITORINFOEXW monitor_info_tmp = {}; + monitor_info_tmp.cbSize = sizeof(monitor_info_tmp); + if (!CallMonitorInfo(monitor, &monitor_info_tmp)) + return false; + memcpy(monitor_info_ptr, &monitor_info_tmp, size); + return true; +} + +static NTSTATUS GetCertificateCommon( + PUNICODE_STRING device_name, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_size) { + // Don't support arbitrarily large certificate buffers. + if (certificate_size > kProtectedVideoOutputSectionSize) + return STATUS_INVALID_PARAMETER; + if (certificate_type != DXGKMDT_OPM_CERTIFICATE) + return STATUS_INVALID_PARAMETER; + if (device_name && device_name->Length == 0) + return STATUS_INVALID_PARAMETER; + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + ScopedSharedMemory buffer(certificate_size); + if (!buffer.IsValid()) + return STATUS_INVALID_PARAMETER; + std::wstring device_name_str; + void* protected_output_handle = nullptr; + if (device_name) { + if (device_name->Length == 0) + return STATUS_INVALID_PARAMETER; + UnicodeStringToString(device_name, &device_name_str); + } else { + protected_output_handle = protected_output; + } + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = + CrossCall(ipc, IpcTag::GDI_GETCERTIFICATE, device_name_str.c_str(), + protected_output_handle, buffer.handle(), + static_cast<uint32_t>(certificate_size), &answer); + + if (code != SBOX_ALL_OK) { + return STATUS_ACCESS_DENIED; + } + + if (!answer.nt_status) + memcpy(certificate, buffer.memory(), certificate_size); + + return answer.nt_status; +} + +NTSTATUS WINAPI TargetGetCertificate(GetCertificateFunction, + PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_size) { + return GetCertificateCommon(device_name, nullptr, certificate_type, + certificate, certificate_size); +} + +static NTSTATUS GetCertificateSizeCommon( + PUNICODE_STRING device_name, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length) { + if (certificate_type != DXGKMDT_OPM_CERTIFICATE) + return STATUS_INVALID_PARAMETER; + if (device_name && device_name->Length == 0) + return STATUS_INVALID_PARAMETER; + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + std::wstring device_name_str; + void* protected_output_handle = nullptr; + if (device_name) { + UnicodeStringToString(device_name, &device_name_str); + } else { + protected_output_handle = protected_output; + } + ResultCode code = + CrossCall(ipc, IpcTag::GDI_GETCERTIFICATESIZE, device_name_str.c_str(), + protected_output_handle, &answer); + + if (code != SBOX_ALL_OK) { + return STATUS_ACCESS_DENIED; + } + + if (!answer.nt_status) + *certificate_length = answer.extended[0].unsigned_int; + + return answer.nt_status; +} + +NTSTATUS WINAPI +TargetGetCertificateSize(GetCertificateSizeFunction, + PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length) { + return GetCertificateSizeCommon(device_name, nullptr, certificate_type, + certificate_length); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetCertificateByHandle( + GetCertificateByHandleFunction orig_get_certificate_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length) { + return GetCertificateCommon(nullptr, protected_output, certificate_type, + certificate, certificate_length); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetCertificateSizeByHandle( + GetCertificateSizeByHandleFunction orig_get_certificate_size_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length) { + return GetCertificateSizeCommon(nullptr, protected_output, certificate_type, + certificate_length); +} + +NTSTATUS WINAPI +TargetDestroyOPMProtectedOutput(DestroyOPMProtectedOutputFunction, + OPM_PROTECTED_OUTPUT_HANDLE protected_output) { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = CrossCall(ipc, IpcTag::GDI_DESTROYOPMPROTECTEDOUTPUT, + static_cast<void*>(protected_output), &answer); + + if (code != SBOX_ALL_OK) + return STATUS_ACCESS_DENIED; + + return answer.nt_status; +} + +NTSTATUS WINAPI TargetConfigureOPMProtectedOutput( + ConfigureOPMProtectedOutputFunction, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_CONFIGURE_PARAMETERS* parameters, + ULONG additional_parameters_size, + const BYTE* additional_parameters) { + // Don't support additional parameters. + if (additional_parameters_size > 0) + return STATUS_INVALID_PARAMETER; + + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + ScopedSharedMemory buffer(sizeof(*parameters)); + if (!buffer.IsValid()) + return STATUS_INVALID_PARAMETER; + memcpy(buffer.memory(), parameters, sizeof(*parameters)); + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = + CrossCall(ipc, IpcTag::GDI_CONFIGUREOPMPROTECTEDOUTPUT, + static_cast<void*>(protected_output), buffer.handle(), &answer); + + if (code != SBOX_ALL_OK) { + return STATUS_ACCESS_DENIED; + } + + return answer.nt_status; +} + +NTSTATUS WINAPI TargetGetOPMInformation( + GetOPMInformationFunction, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_GET_INFO_PARAMETERS* parameters, + DXGKMDT_OPM_REQUESTED_INFORMATION* requested_information) { + size_t max_size = + std::max(sizeof(*parameters), sizeof(*requested_information)); + + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + ScopedSharedMemory buffer(base::checked_cast<uint32_t>(max_size)); + if (!buffer.IsValid()) + return STATUS_INVALID_PARAMETER; + memcpy(buffer.memory(), parameters, sizeof(*parameters)); + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = + CrossCall(ipc, IpcTag::GDI_GETOPMINFORMATION, + static_cast<void*>(protected_output), buffer.handle(), &answer); + + if (code != SBOX_ALL_OK) + return STATUS_ACCESS_DENIED; + + if (!answer.nt_status) { + memcpy(requested_information, buffer.memory(), + sizeof(*requested_information)); + } + + return answer.nt_status; +} + +NTSTATUS WINAPI +TargetGetOPMRandomNumber(GetOPMRandomNumberFunction, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_OPM_RANDOM_NUMBER* random_number) { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + InOutCountedBuffer buffer(random_number, sizeof(*random_number)); + ResultCode code = + CrossCall(ipc, IpcTag::GDI_GETOPMRANDOMNUMBER, + static_cast<void*>(protected_output), buffer, &answer); + + if (code != SBOX_ALL_OK) + return STATUS_ACCESS_DENIED; + + return answer.nt_status; +} + +NTSTATUS WINAPI TargetGetSuggestedOPMProtectedOutputArraySize( + GetSuggestedOPMProtectedOutputArraySizeFunction, + PUNICODE_STRING device_name, + DWORD* suggested_output_size) { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + std::wstring device_name_str; + UnicodeStringToString(device_name, &device_name_str); + ResultCode code = + CrossCall(ipc, IpcTag::GDI_GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE, + device_name_str.c_str(), &answer); + + if (code != SBOX_ALL_OK) + return STATUS_ACCESS_DENIED; + + if (!answer.nt_status) + *suggested_output_size = answer.extended[0].unsigned_int; + + return answer.nt_status; +} + +NTSTATUS WINAPI TargetSetOPMSigningKeyAndSequenceNumbers( + SetOPMSigningKeyAndSequenceNumbersFunction, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_ENCRYPTED_PARAMETERS* parameters) { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + DXGKMDT_OPM_ENCRYPTED_PARAMETERS temp_parameters = *parameters; + + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + InOutCountedBuffer buffer(&temp_parameters, sizeof(temp_parameters)); + ResultCode code = + CrossCall(ipc, IpcTag::GDI_SETOPMSIGNINGKEYANDSEQUENCENUMBERS, + static_cast<void*>(protected_output), buffer, &answer); + + if (code != SBOX_ALL_OK) + return STATUS_ACCESS_DENIED; + + return answer.nt_status; +} + +NTSTATUS WINAPI +TargetCreateOPMProtectedOutputs(CreateOPMProtectedOutputsFunction, + PUNICODE_STRING device_name, + DXGKMDT_OPM_VIDEO_OUTPUT_SEMANTICS vos, + DWORD outputs_array_size, + DWORD* output_size, + OPM_PROTECTED_OUTPUT_HANDLE* outputs_array) { + if (vos != DXGKMDT_OPM_VOS_OPM_SEMANTICS) + return STATUS_INVALID_PARAMETER; + + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + base::CheckedNumeric<uint32_t> array_size = outputs_array_size; + array_size *= sizeof(HANDLE); + if (!array_size.IsValid()) + return STATUS_INVALID_PARAMETER; + + InOutCountedBuffer buffer(outputs_array, array_size.ValueOrDie()); + std::wstring device_name_str; + UnicodeStringToString(device_name, &device_name_str); + ResultCode code = CrossCall(ipc, IpcTag::GDI_CREATEOPMPROTECTEDOUTPUTS, + device_name_str.c_str(), buffer, &answer); + + if (code != SBOX_ALL_OK) + return STATUS_ACCESS_DENIED; + + if (!answer.nt_status) + *output_size = answer.extended[0].unsigned_int; + + return answer.nt_status; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_interception.h b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_interception.h new file mode 100644 index 0000000000..befcda2767 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_interception.h @@ -0,0 +1,151 @@ +// Copyright 2014 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. + +#ifndef SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_INTERCEPTION_H_ +#define SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_INTERCEPTION_H_ + +#include <windows.h> + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +const DWORD kProtectedVideoOutputSectionSize = 16 * 1024; +const DWORD kMaxEnumMonitors = 32; + +struct EnumMonitorsResult { + DWORD monitor_count; + HMONITOR monitors[kMaxEnumMonitors]; +}; + +typedef BOOL(WINAPI* GdiDllInitializeFunction)(HANDLE dll, + DWORD reason, + LPVOID reserved); + +using GetStockObjectFunction = decltype(&::GetStockObject); + +using RegisterClassWFunction = decltype(&::RegisterClassW); + +using EnumDisplayMonitorsFunction = decltype(&::EnumDisplayMonitors); + +using EnumDisplayDevicesAFunction = decltype(&::EnumDisplayDevicesA); + +using GetMonitorInfoWFunction = decltype(&::GetMonitorInfoW); +using GetMonitorInfoAFunction = decltype(&::GetMonitorInfoA); + +extern "C" { + +// Interceptor for the GdiDllInitialize function. +SANDBOX_INTERCEPT BOOL WINAPI +TargetGdiDllInitialize(GdiDllInitializeFunction orig_gdi_dll_initialize, + HANDLE dll, + DWORD reason); + +// Interceptor for the GetStockObject function. +SANDBOX_INTERCEPT HGDIOBJ WINAPI +TargetGetStockObject(GetStockObjectFunction orig_get_stock_object, int object); + +// Interceptor for the RegisterClassW function. +SANDBOX_INTERCEPT ATOM WINAPI +TargetRegisterClassW(RegisterClassWFunction orig_register_class_function, + const WNDCLASS* wnd_class); + +SANDBOX_INTERCEPT BOOL WINAPI TargetEnumDisplayMonitors( + EnumDisplayMonitorsFunction orig_enum_display_monitors_function, + HDC hdc, + LPCRECT clip_rect, + MONITORENUMPROC enum_function, + LPARAM data); + +SANDBOX_INTERCEPT BOOL WINAPI TargetEnumDisplayDevicesA( + EnumDisplayDevicesAFunction orig_enum_display_devices_function, + LPCSTR device, + DWORD device_number, + PDISPLAY_DEVICEA display_device, + DWORD flags); + +SANDBOX_INTERCEPT BOOL WINAPI +TargetGetMonitorInfoA(GetMonitorInfoAFunction orig_get_monitor_info_function, + HMONITOR monitor, + LPMONITORINFO monitor_info); + +SANDBOX_INTERCEPT BOOL WINAPI +TargetGetMonitorInfoW(GetMonitorInfoWFunction orig_get_monitor_info_function, + HMONITOR monitor, + LPMONITORINFO monitor_info); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetCreateOPMProtectedOutputs( + CreateOPMProtectedOutputsFunction orig_create_proceted_outputs_function, + PUNICODE_STRING device_name, + DXGKMDT_OPM_VIDEO_OUTPUT_SEMANTICS vos, + DWORD protected_output_array_size, + DWORD* output_array_size, + OPM_PROTECTED_OUTPUT_HANDLE* protected_outputs); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificate(GetCertificateFunction orig_get_certificate_function, + PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetCertificateSize( + GetCertificateSizeFunction orig_get_certificate_size_function, + PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetCertificateByHandle( + GetCertificateByHandleFunction orig_get_certificate_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetCertificateSizeByHandle( + GetCertificateSizeByHandleFunction orig_get_certificate_size_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetDestroyOPMProtectedOutput( + DestroyOPMProtectedOutputFunction orig_destroy_protected_output_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetConfigureOPMProtectedOutput( + ConfigureOPMProtectedOutputFunction + origin_configure_protected_output_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_CONFIGURE_PARAMETERS* parameters, + ULONG additional_parameters_size, + const BYTE* additional_parameters); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetOPMInformation( + GetOPMInformationFunction origin_get_information_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_GET_INFO_PARAMETERS* parameters, + DXGKMDT_OPM_REQUESTED_INFORMATION* requested_information); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetOPMRandomNumber( + GetOPMRandomNumberFunction orig_get_random_number_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_OPM_RANDOM_NUMBER* random_number); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetSuggestedOPMProtectedOutputArraySize( + GetSuggestedOPMProtectedOutputArraySizeFunction + orig_get_suggested_size_function, + PUNICODE_STRING device_name, + DWORD* suggested_output_array_size); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetSetOPMSigningKeyAndSequenceNumbers( + SetOPMSigningKeyAndSequenceNumbersFunction orig_set_signing_keys_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_ENCRYPTED_PARAMETERS* parameters); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_policy.cc b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_policy.cc new file mode 100644 index 0000000000..8ab915ec33 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_policy.cc @@ -0,0 +1,410 @@ +// Copyright 2014 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/win/src/process_mitigations_win32k_policy.h" + +#include <stddef.h> + +#include "sandbox/win/src/process_mitigations_win32k_interception.h" + +namespace sandbox { + +namespace { + +// Define GUIDs for OPM APIs +const GUID DXGKMDT_OPM_GET_CONNECTOR_TYPE = { + 0x81d0bfd5, + 0x6afe, + 0x48c2, + {0x99, 0xc0, 0x95, 0xa0, 0x8f, 0x97, 0xc5, 0xda}}; +const GUID DXGKMDT_OPM_GET_SUPPORTED_PROTECTION_TYPES = { + 0x38f2a801, + 0x9a6c, + 0x48bb, + {0x91, 0x07, 0xb6, 0x69, 0x6e, 0x6f, 0x17, 0x97}}; +const GUID DXGKMDT_OPM_GET_VIRTUAL_PROTECTION_LEVEL = { + 0xb2075857, + 0x3eda, + 0x4d5d, + {0x88, 0xdb, 0x74, 0x8f, 0x8c, 0x1a, 0x05, 0x49}}; +const GUID DXGKMDT_OPM_GET_ACTUAL_PROTECTION_LEVEL = { + 0x1957210a, + 0x7766, + 0x452a, + {0xb9, 0x9a, 0xd2, 0x7a, 0xed, 0x54, 0xf0, 0x3a}}; +const GUID DXGKMDT_OPM_SET_PROTECTION_LEVEL = { + 0x9bb9327c, + 0x4eb5, + 0x4727, + {0x9f, 0x00, 0xb4, 0x2b, 0x09, 0x19, 0xc0, 0xda}}; + +void StringToUnicodeString(PUNICODE_STRING unicode_string, + const std::wstring& device_name) { + static RtlInitUnicodeStringFunction RtlInitUnicodeString; + if (!RtlInitUnicodeString) { + HMODULE ntdll = ::GetModuleHandle(kNtdllName); + RtlInitUnicodeString = reinterpret_cast<RtlInitUnicodeStringFunction>( + GetProcAddress(ntdll, "RtlInitUnicodeString")); + } + RtlInitUnicodeString(unicode_string, device_name.c_str()); +} + +struct MonitorListState { + HMONITOR* monitor_list; + uint32_t monitor_list_size; + uint32_t monitor_list_pos; +}; + +BOOL CALLBACK DisplayMonitorEnumProc(HMONITOR monitor, + HDC hdc_monitor, + LPRECT rect_monitor, + LPARAM data) { + MonitorListState* state = reinterpret_cast<MonitorListState*>(data); + if (state->monitor_list_pos >= state->monitor_list_size) + return false; + state->monitor_list[state->monitor_list_pos++] = monitor; + return true; +} + +template <typename T> +T GetExportedFunc(const wchar_t* libname, const char* name) { + OverrideForTestFunction test_override = + ProcessMitigationsWin32KLockdownPolicy::GetOverrideForTestCallback(); + if (test_override) + return reinterpret_cast<T>(test_override(name)); + + static T func = nullptr; + if (!func) { + func = + reinterpret_cast<T>(::GetProcAddress(::GetModuleHandle(libname), name)); + DCHECK(!!func); + } + return func; +} + +#define GDIFUNC(name) GetExportedFunc<name##Function>(L"gdi32.dll", #name) +#define USERFUNC(name) GetExportedFunc<name##Function>(L"user32.dll", #name) + +struct ValidateMonitorParams { + HMONITOR monitor; + std::wstring device_name; + bool result; +}; + +bool GetMonitorDeviceName(HMONITOR monitor, std::wstring* device_name) { + MONITORINFOEXW monitor_info = {}; + monitor_info.cbSize = sizeof(monitor_info); + if (!USERFUNC(GetMonitorInfoW)(monitor, &monitor_info)) + return false; + if (monitor_info.szDevice[CCHDEVICENAME - 1] != 0) + return false; + *device_name = monitor_info.szDevice; + return true; +} + +BOOL CALLBACK ValidateMonitorEnumProc(HMONITOR monitor, + HDC, + LPRECT, + LPARAM data) { + ValidateMonitorParams* valid_params = + reinterpret_cast<ValidateMonitorParams*>(data); + std::wstring device_name; + bool result = false; + if (valid_params->device_name.empty()) { + result = monitor == valid_params->monitor; + } else if (GetMonitorDeviceName(monitor, &device_name)) { + result = device_name == valid_params->device_name; + } + valid_params->result = result; + if (!result) + return true; + return false; +} + +bool IsValidMonitorOrDeviceName(HMONITOR monitor, const wchar_t* device_name) { + ValidateMonitorParams params = {}; + params.monitor = monitor; + if (device_name) + params.device_name = device_name; + USERFUNC(EnumDisplayMonitors) + (nullptr, nullptr, ValidateMonitorEnumProc, + reinterpret_cast<LPARAM>(¶ms)); + return params.result; +} + +} // namespace + +OverrideForTestFunction + ProcessMitigationsWin32KLockdownPolicy::override_callback_; + +bool ProcessMitigationsWin32KLockdownPolicy::GenerateRules( + const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + PolicyRule rule(FAKE_SUCCESS); + if (!policy->AddRule(IpcTag::GDI_GDIDLLINITIALIZE, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_GETSTOCKOBJECT, &rule)) + return false; + if (!policy->AddRule(IpcTag::USER_REGISTERCLASSW, &rule)) + return false; + if (semantics != TargetPolicy::IMPLEMENT_OPM_APIS) + return true; + if (!policy->AddRule(IpcTag::USER_ENUMDISPLAYMONITORS, &rule)) + return false; + if (!policy->AddRule(IpcTag::USER_ENUMDISPLAYDEVICES, &rule)) + return false; + if (!policy->AddRule(IpcTag::USER_GETMONITORINFO, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_CREATEOPMPROTECTEDOUTPUTS, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_GETCERTIFICATE, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_GETCERTIFICATESIZE, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_DESTROYOPMPROTECTEDOUTPUT, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_CONFIGUREOPMPROTECTEDOUTPUT, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_GETOPMINFORMATION, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_GETOPMRANDOMNUMBER, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE, + &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_SETOPMSIGNINGKEYANDSEQUENCENUMBERS, &rule)) + return false; + return true; +} + +uint32_t ProcessMitigationsWin32KLockdownPolicy::EnumDisplayMonitorsAction( + const ClientInfo& client_info, + HMONITOR* monitor_list, + uint32_t monitor_list_size) { + MonitorListState state = {monitor_list, monitor_list_size, 0}; + USERFUNC(EnumDisplayMonitors) + (nullptr, nullptr, DisplayMonitorEnumProc, reinterpret_cast<LPARAM>(&state)); + return state.monitor_list_pos; +} + +bool ProcessMitigationsWin32KLockdownPolicy::GetMonitorInfoAction( + const ClientInfo& client_info, + HMONITOR monitor, + MONITORINFO* monitor_info_ptr) { + if (!IsValidMonitorOrDeviceName(monitor, nullptr)) + return false; + MONITORINFOEXW monitor_info = {}; + monitor_info.cbSize = sizeof(MONITORINFOEXW); + + bool success = USERFUNC(GetMonitorInfoW)( + monitor, reinterpret_cast<MONITORINFO*>(&monitor_info)); + if (success) + memcpy(monitor_info_ptr, &monitor_info, sizeof(monitor_info)); + return success; +} + +NTSTATUS ProcessMitigationsWin32KLockdownPolicy:: + GetSuggestedOPMProtectedOutputArraySizeAction( + const ClientInfo& client_info, + const std::wstring& device_name, + uint32_t* suggested_array_size) { + if (!IsValidMonitorOrDeviceName(nullptr, device_name.c_str())) { + return STATUS_ACCESS_DENIED; + } + UNICODE_STRING unicode_device_name; + StringToUnicodeString(&unicode_device_name, device_name); + DWORD suggested_array_size_dword = 0; + NTSTATUS status = GDIFUNC(GetSuggestedOPMProtectedOutputArraySize)( + &unicode_device_name, &suggested_array_size_dword); + if (!status) + *suggested_array_size = suggested_array_size_dword; + return status; +} + +NTSTATUS +ProcessMitigationsWin32KLockdownPolicy::CreateOPMProtectedOutputsAction( + const ClientInfo& client_info, + const std::wstring& device_name, + HANDLE* protected_outputs, + uint32_t array_input_size, + uint32_t* array_output_size) { + if (!IsValidMonitorOrDeviceName(nullptr, device_name.c_str())) { + return STATUS_ACCESS_DENIED; + } + + UNICODE_STRING unicode_device_name; + StringToUnicodeString(&unicode_device_name, device_name); + DWORD output_size = 0; + + NTSTATUS status = GDIFUNC(CreateOPMProtectedOutputs)( + &unicode_device_name, DXGKMDT_OPM_VOS_OPM_SEMANTICS, array_input_size, + &output_size, + reinterpret_cast<OPM_PROTECTED_OUTPUT_HANDLE*>(protected_outputs)); + if (!status) + *array_output_size = output_size; + return status; +} + +NTSTATUS ProcessMitigationsWin32KLockdownPolicy::GetCertificateSizeAction( + const ClientInfo& client_info, + const std::wstring& device_name, + uint32_t* cert_size) { + if (!IsValidMonitorOrDeviceName(nullptr, device_name.c_str())) { + return STATUS_ACCESS_DENIED; + } + UNICODE_STRING unicode_device_name; + StringToUnicodeString(&unicode_device_name, device_name); + + return GDIFUNC(GetCertificateSize)(&unicode_device_name, + DXGKMDT_OPM_CERTIFICATE, + reinterpret_cast<DWORD*>(cert_size)); +} + +NTSTATUS ProcessMitigationsWin32KLockdownPolicy::GetCertificateAction( + const ClientInfo& client_info, + const std::wstring& device_name, + BYTE* cert_data, + uint32_t cert_size) { + if (!IsValidMonitorOrDeviceName(nullptr, device_name.c_str())) { + return STATUS_ACCESS_DENIED; + } + UNICODE_STRING unicode_device_name; + StringToUnicodeString(&unicode_device_name, device_name); + + return GDIFUNC(GetCertificate)(&unicode_device_name, DXGKMDT_OPM_CERTIFICATE, + cert_data, cert_size); +} + +NTSTATUS +ProcessMitigationsWin32KLockdownPolicy::GetCertificateSizeByHandleAction( + const ClientInfo& client_info, + HANDLE protected_output, + uint32_t* cert_size) { + auto get_certificate_size_func = GDIFUNC(GetCertificateSizeByHandle); + if (get_certificate_size_func) { + return get_certificate_size_func(protected_output, DXGKMDT_OPM_CERTIFICATE, + reinterpret_cast<DWORD*>(cert_size)); + } + return STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS ProcessMitigationsWin32KLockdownPolicy::GetCertificateByHandleAction( + const ClientInfo& client_info, + HANDLE protected_output, + BYTE* cert_data, + uint32_t cert_size) { + auto get_certificate_func = GDIFUNC(GetCertificateByHandle); + if (get_certificate_func) { + return get_certificate_func(protected_output, DXGKMDT_OPM_CERTIFICATE, + cert_data, cert_size); + } + return STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS ProcessMitigationsWin32KLockdownPolicy::GetOPMRandomNumberAction( + const ClientInfo& client_info, + HANDLE protected_output, + void* random_number) { + return GDIFUNC(GetOPMRandomNumber)( + protected_output, static_cast<DXGKMDT_OPM_RANDOM_NUMBER*>(random_number)); +} + +NTSTATUS ProcessMitigationsWin32KLockdownPolicy:: + SetOPMSigningKeyAndSequenceNumbersAction(const ClientInfo& client_info, + HANDLE protected_output, + void* parameters) { + return GDIFUNC(SetOPMSigningKeyAndSequenceNumbers)( + protected_output, + static_cast<DXGKMDT_OPM_ENCRYPTED_PARAMETERS*>(parameters)); +} + +NTSTATUS +ProcessMitigationsWin32KLockdownPolicy::ConfigureOPMProtectedOutputAction( + const ClientInfo& client_info, + HANDLE protected_output, + void* parameters_ptr) { + DXGKMDT_OPM_CONFIGURE_PARAMETERS parameters; + memcpy(¶meters, parameters_ptr, sizeof(parameters)); + if (parameters.guidSetting != DXGKMDT_OPM_SET_PROTECTION_LEVEL || + parameters.cbParametersSize != + sizeof(DXGKMDT_OPM_SET_PROTECTION_LEVEL_PARAMETERS)) { + return STATUS_INVALID_PARAMETER; + } + + DXGKMDT_OPM_SET_PROTECTION_LEVEL_PARAMETERS prot_level; + memcpy(&prot_level, parameters.abParameters, sizeof(prot_level)); + if (prot_level.Reserved || prot_level.Reserved2) + return STATUS_INVALID_PARAMETER; + + if (prot_level.ulProtectionType != DXGKMDT_OPM_PROTECTION_TYPE_HDCP && + prot_level.ulProtectionType != DXGKMDT_OPM_PROTECTION_TYPE_DPCP) { + return STATUS_INVALID_PARAMETER; + } + + // Protection levels are same for HDCP and DPCP. + if (prot_level.ulProtectionLevel != DXGKMDT_OPM_HDCP_OFF && + prot_level.ulProtectionLevel != DXGKMDT_OPM_HDCP_ON) { + return STATUS_INVALID_PARAMETER; + } + + return GDIFUNC(ConfigureOPMProtectedOutput)(protected_output, ¶meters, 0, + nullptr); +} + +NTSTATUS ProcessMitigationsWin32KLockdownPolicy::GetOPMInformationAction( + const ClientInfo& client_info, + HANDLE protected_output, + void* parameters_ptr, + void* requested_info_ptr) { + DXGKMDT_OPM_GET_INFO_PARAMETERS parameters; + memcpy(¶meters, parameters_ptr, sizeof(parameters)); + + bool valid_parameters = false; + // Validate sizes based on the type being requested. + if ((parameters.guidInformation == DXGKMDT_OPM_GET_CONNECTOR_TYPE || + parameters.guidInformation == + DXGKMDT_OPM_GET_SUPPORTED_PROTECTION_TYPES) && + parameters.cbParametersSize == 0) { + valid_parameters = true; + } else if ((parameters.guidInformation == + DXGKMDT_OPM_GET_VIRTUAL_PROTECTION_LEVEL || + parameters.guidInformation == + DXGKMDT_OPM_GET_ACTUAL_PROTECTION_LEVEL) && + parameters.cbParametersSize == sizeof(uint32_t)) { + uint32_t param_value; + memcpy(¶m_value, parameters.abParameters, sizeof(param_value)); + if (param_value == DXGKMDT_OPM_PROTECTION_TYPE_HDCP || + param_value == DXGKMDT_OPM_PROTECTION_TYPE_DPCP) { + valid_parameters = true; + } + } + if (!valid_parameters) + return STATUS_INVALID_PARAMETER; + DXGKMDT_OPM_REQUESTED_INFORMATION requested_info = {}; + NTSTATUS status = GDIFUNC(GetOPMInformation)(protected_output, ¶meters, + &requested_info); + if (!status) + memcpy(requested_info_ptr, &requested_info, sizeof(requested_info)); + + return status; +} + +NTSTATUS +ProcessMitigationsWin32KLockdownPolicy::DestroyOPMProtectedOutputAction( + HANDLE protected_output) { + return GDIFUNC(DestroyOPMProtectedOutput)(protected_output); +} + +void ProcessMitigationsWin32KLockdownPolicy::SetOverrideForTestCallback( + OverrideForTestFunction callback) { + override_callback_ = callback; +} + +OverrideForTestFunction +ProcessMitigationsWin32KLockdownPolicy::GetOverrideForTestCallback() { + return override_callback_; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_policy.h b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_policy.h new file mode 100644 index 0000000000..0c2b2dff95 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_policy.h @@ -0,0 +1,91 @@ +// Copyright 2014 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. + +#ifndef SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_POLICY_H_ +#define SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_POLICY_H_ + +#include <string> + +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +// A callback function type to get a function for testing. +typedef void* (*OverrideForTestFunction)(const char* name); + +// This class centralizes most of the knowledge related to the process +// mitigations Win32K lockdown policy. +class ProcessMitigationsWin32KLockdownPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for the Win32K process mitigation policy. + // name is the object name, semantics is the desired semantics for the + // open or create and policy is the policy generator to which the rules are + // going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + static uint32_t EnumDisplayMonitorsAction(const ClientInfo& client_info, + HMONITOR* monitor_list, + uint32_t monitor_list_size); + static bool GetMonitorInfoAction(const ClientInfo& client_info, + HMONITOR monitor, + MONITORINFO* monitor_info); + + static NTSTATUS GetSuggestedOPMProtectedOutputArraySizeAction( + const ClientInfo& client_info, + const std::wstring& device_name, + uint32_t* suggested_array_size); + + static NTSTATUS CreateOPMProtectedOutputsAction( + const ClientInfo& client_info, + const std::wstring& device_name, + HANDLE* protected_outputs, + uint32_t array_input_size, + uint32_t* array_output_size); + + static NTSTATUS GetCertificateSizeAction(const ClientInfo& client_info, + const std::wstring& device_name, + uint32_t* cert_size); + static NTSTATUS GetCertificateAction(const ClientInfo& client_info, + const std::wstring& device_name, + BYTE* cert_data, + uint32_t cert_size); + static NTSTATUS GetCertificateSizeByHandleAction( + const ClientInfo& client_info, + HANDLE protected_output, + uint32_t* cert_size); + static NTSTATUS GetCertificateByHandleAction(const ClientInfo& client_info, + HANDLE protected_output, + BYTE* cert_data, + uint32_t cert_size); + static NTSTATUS GetOPMRandomNumberAction(const ClientInfo& client_info, + HANDLE protected_output, + void* random_number); + static NTSTATUS SetOPMSigningKeyAndSequenceNumbersAction( + const ClientInfo& client_info, + HANDLE protected_output, + void* parameters); + static NTSTATUS ConfigureOPMProtectedOutputAction( + const ClientInfo& client_info, + HANDLE protected_output, + void* parameters_ptr); + static NTSTATUS GetOPMInformationAction(const ClientInfo& client_info, + HANDLE protected_output, + void* parameters_ptr, + void* requested_information_ptr); + static NTSTATUS DestroyOPMProtectedOutputAction(HANDLE protected_output); + static void SetOverrideForTestCallback(OverrideForTestFunction callback); + static OverrideForTestFunction GetOverrideForTestCallback(); + + private: + static OverrideForTestFunction override_callback_; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_POLICY_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/process_policy_test.cc b/security/sandbox/chromium/sandbox/win/src/process_policy_test.cc new file mode 100644 index 0000000000..c321c3a088 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_policy_test.cc @@ -0,0 +1,548 @@ +// 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 <memory> +#include <string> + +#include "base/memory/free_deleter.h" +#include "base/strings/sys_string_conversions.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "base/win/windows_version.h" +#include "build/build_config.h" +#include "sandbox/win/src/process_thread_interception.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Creates a process with the |exe| and |command| parameter using the +// unicode and ascii version of the api. +sandbox::SboxTestResult CreateProcessHelper(const std::wstring& exe, + const std::wstring& command) { + base::win::ScopedProcessInformation pi; + STARTUPINFOW si = {sizeof(si)}; + const wchar_t* exe_name = nullptr; + if (!exe.empty()) + exe_name = exe.c_str(); + + std::unique_ptr<wchar_t, base::FreeDeleter> writable_command( + _wcsdup(command.c_str())); + + // Create the process with the unicode version of the API. + sandbox::SboxTestResult ret1 = sandbox::SBOX_TEST_FAILED; + PROCESS_INFORMATION temp_process_info = {}; + if (::CreateProcessW( + exe_name, command.empty() ? nullptr : writable_command.get(), nullptr, + nullptr, false, 0, nullptr, nullptr, &si, &temp_process_info)) { + pi.Set(temp_process_info); + ret1 = sandbox::SBOX_TEST_SUCCEEDED; + } else { + DWORD last_error = GetLastError(); + if ((ERROR_NOT_ENOUGH_QUOTA == last_error) || + (ERROR_ACCESS_DENIED == last_error) || + (ERROR_FILE_NOT_FOUND == last_error)) { + ret1 = sandbox::SBOX_TEST_DENIED; + } else { + ret1 = sandbox::SBOX_TEST_FAILED; + } + } + + pi.Close(); + + // Do the same with the ansi version of the api + STARTUPINFOA sia = {sizeof(sia)}; + sandbox::SboxTestResult ret2 = sandbox::SBOX_TEST_FAILED; + + std::string narrow_cmd_line = + base::SysWideToMultiByte(command.c_str(), CP_UTF8); + if (::CreateProcessA( + exe_name ? base::SysWideToMultiByte(exe_name, CP_UTF8).c_str() + : nullptr, + command.empty() ? nullptr : &narrow_cmd_line[0], nullptr, nullptr, + false, 0, nullptr, nullptr, &sia, &temp_process_info)) { + pi.Set(temp_process_info); + ret2 = sandbox::SBOX_TEST_SUCCEEDED; + } else { + DWORD last_error = GetLastError(); + if ((ERROR_NOT_ENOUGH_QUOTA == last_error) || + (ERROR_ACCESS_DENIED == last_error) || + (ERROR_FILE_NOT_FOUND == last_error)) { + ret2 = sandbox::SBOX_TEST_DENIED; + } else { + ret2 = sandbox::SBOX_TEST_FAILED; + } + } + + if (ret1 == ret2) + return ret1; + + return sandbox::SBOX_TEST_FAILED; +} + +} // namespace + +namespace sandbox { + +SBOX_TESTS_COMMAND int Process_RunApp1(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + std::wstring path = MakePathToSys(argv[0], false); + + // TEST 1: Try with the path in the app_name. + return CreateProcessHelper(path, std::wstring()); +} + +SBOX_TESTS_COMMAND int Process_RunApp2(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + std::wstring path = MakePathToSys(argv[0], false); + + // TEST 2: Try with the path in the cmd_line. + std::wstring cmd_line = L"\""; + cmd_line += path; + cmd_line += L"\""; + return CreateProcessHelper(std::wstring(), cmd_line); +} + +SBOX_TESTS_COMMAND int Process_RunApp3(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + // TEST 3: Try file name in the cmd_line. + return CreateProcessHelper(std::wstring(), argv[0]); +} + +SBOX_TESTS_COMMAND int Process_RunApp4(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + // TEST 4: Try file name in the app_name and current directory sets correctly. + std::wstring system32 = MakePathToSys(L"", false); + wchar_t current_directory[MAX_PATH + 1]; + DWORD ret = ::GetCurrentDirectory(MAX_PATH, current_directory); + if (!ret) + return SBOX_TEST_FIRST_ERROR; + if (ret >= MAX_PATH) + return SBOX_TEST_FAILED; + + current_directory[ret] = L'\\'; + current_directory[ret + 1] = L'\0'; + if (!::SetCurrentDirectory(system32.c_str())) + return SBOX_TEST_SECOND_ERROR; + + const int result4 = CreateProcessHelper(argv[0], std::wstring()); + return ::SetCurrentDirectory(current_directory) ? result4 : SBOX_TEST_FAILED; +} + +SBOX_TESTS_COMMAND int Process_RunApp5(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + std::wstring path = MakePathToSys(argv[0], false); + + // TEST 5: Try with the path in the cmd_line and arguments. + std::wstring cmd_line = L"\""; + cmd_line += path; + cmd_line += L"\" /I"; + return CreateProcessHelper(std::wstring(), cmd_line); +} + +SBOX_TESTS_COMMAND int Process_RunApp6(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + // TEST 6: Try with the file_name in the cmd_line and arguments. + std::wstring cmd_line = argv[0]; + cmd_line += L" /I"; + return CreateProcessHelper(std::wstring(), cmd_line); +} + +// Creates a process and checks if it's possible to get a handle to it's token. +SBOX_TESTS_COMMAND int Process_GetChildProcessToken(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + std::wstring path = MakePathToSys(argv[0], false); + + STARTUPINFOW si = {sizeof(si)}; + + PROCESS_INFORMATION temp_process_info = {}; + if (!::CreateProcessW(path.c_str(), nullptr, nullptr, nullptr, false, + CREATE_SUSPENDED, nullptr, nullptr, &si, + &temp_process_info)) { + return SBOX_TEST_FAILED; + } + base::win::ScopedProcessInformation pi(temp_process_info); + + HANDLE token = nullptr; + bool result = + ::OpenProcessToken(pi.process_handle(), TOKEN_IMPERSONATE, &token); + DWORD error = ::GetLastError(); + + base::win::ScopedHandle token_handle(token); + + if (!::TerminateProcess(pi.process_handle(), 0)) + return SBOX_TEST_FAILED; + + if (result && token) + return SBOX_TEST_SUCCEEDED; + + if (ERROR_ACCESS_DENIED == error) + return SBOX_TEST_DENIED; + + return SBOX_TEST_FAILED; +} + +// Creates a suspended process using CreateProcessA then kill it. +SBOX_TESTS_COMMAND int Process_CreateProcessA(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + STARTUPINFOA si = {sizeof(si)}; + + std::wstring path = MakePathToSys(argv[0], false); + + PROCESS_INFORMATION temp_process_info = {}; + // Create suspended to avoid popping calc. + if (!::CreateProcessA(base::SysWideToMultiByte(path, CP_UTF8).c_str(), + nullptr, nullptr, nullptr, false, CREATE_SUSPENDED, + nullptr, nullptr, &si, &temp_process_info)) { + return SBOX_TEST_FAILED; + } + base::win::ScopedProcessInformation pi(temp_process_info); + + if (!::TerminateProcess(pi.process_handle(), 0)) + return SBOX_TEST_FAILED; + + return SBOX_TEST_SUCCEEDED; +} + +SBOX_TESTS_COMMAND int Process_OpenToken(int argc, wchar_t** argv) { + HANDLE token; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)) { + if (ERROR_ACCESS_DENIED == ::GetLastError()) { + return SBOX_TEST_DENIED; + } + } else { + ::CloseHandle(token); + return SBOX_TEST_SUCCEEDED; + } + + return SBOX_TEST_FAILED; +} + +SBOX_TESTS_COMMAND int Process_Crash(int argc, wchar_t** argv) { + __debugbreak(); + return SBOX_TEST_FAILED; +} +// Generate a event name, used to test thread creation. +std::wstring GenerateEventName(DWORD pid) { + wchar_t buff[30] = {0}; + int res = swprintf_s(buff, sizeof(buff) / sizeof(buff[0]), + L"ProcessPolicyTest_%08x", pid); + if (-1 != res) { + return std::wstring(buff); + } + return std::wstring(); +} + +// This is the function that is called when testing thread creation. +// It is expected to set an event that the caller is waiting on. +DWORD WINAPI TestThreadFunc(LPVOID lpdwThreadParam) { + std::wstring event_name = GenerateEventName( + static_cast<DWORD>(reinterpret_cast<uintptr_t>(lpdwThreadParam))); + if (!event_name.length()) + return 1; + HANDLE event = ::OpenEvent(EVENT_ALL_ACCESS | EVENT_MODIFY_STATE, false, + event_name.c_str()); + if (!event) + return 1; + if (!SetEvent(event)) + return 1; + return 0; +} + +SBOX_TESTS_COMMAND int Process_CreateThread(int argc, wchar_t** argv) { + DWORD pid = ::GetCurrentProcessId(); + std::wstring event_name = GenerateEventName(pid); + if (!event_name.length()) + return SBOX_TEST_FIRST_ERROR; + HANDLE event = ::CreateEvent(nullptr, true, false, event_name.c_str()); + if (!event) + return SBOX_TEST_SECOND_ERROR; + + DWORD thread_id = 0; + HANDLE thread = nullptr; + thread = ::CreateThread(nullptr, 0, &TestThreadFunc, + reinterpret_cast<LPVOID>(static_cast<uintptr_t>(pid)), + 0, &thread_id); + + if (!thread) + return SBOX_TEST_THIRD_ERROR; + if (!thread_id) + return SBOX_TEST_FOURTH_ERROR; + if (WaitForSingleObject(thread, INFINITE) != WAIT_OBJECT_0) + return SBOX_TEST_FIFTH_ERROR; + DWORD exit_code = 0; + if (!GetExitCodeThread(thread, &exit_code)) + return SBOX_TEST_SIXTH_ERROR; + if (exit_code) + return SBOX_TEST_SEVENTH_ERROR; + if (WaitForSingleObject(event, INFINITE) != WAIT_OBJECT_0) + return SBOX_TEST_FAILED; + return SBOX_TEST_SUCCEEDED; +} + +// Creates a process and checks its exit code. Succeeds on exit code 0. +SBOX_TESTS_COMMAND int Process_CheckExitCode(int argc, wchar_t** argv) { + if (argc != 3) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (!argv || !argv[0] || !argv[1] || !argv[2]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + std::wstring path = MakePathToSys(argv[0], false); + std::wstring cmdline = argv[1]; + std::wstring cwd = argv[2]; + + STARTUPINFOW si = {sizeof(si)}; + + PROCESS_INFORMATION temp_process_info = {}; + if (!::CreateProcessW(path.c_str(), &cmdline[0], nullptr, nullptr, false, 0, + nullptr, cwd.c_str(), &si, &temp_process_info)) { + return SBOX_TEST_FAILED; + } + base::win::ScopedProcessInformation pi(temp_process_info); + DWORD ret = WaitForSingleObject(pi.process_handle(), 1000); + if (ret != WAIT_OBJECT_0) + return SBOX_TEST_FAILED; + + DWORD exit_code; + if (!GetExitCodeProcess(pi.process_handle(), &exit_code)) + return SBOX_TEST_FAILED; + + if (exit_code != 0) + return SBOX_TEST_FAILED; + + return SBOX_TEST_SUCCEEDED; +} + +TEST(ProcessPolicyTest, TestAllAccess) { + // Check if the "all access" rule fails to be added when the token is too + // powerful. + TestRunner runner; + + // Check the failing case. + runner.GetPolicy()->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN); + EXPECT_EQ(SBOX_ERROR_UNSUPPORTED, + runner.GetPolicy()->AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_ALL_EXEC, + L"this is not important")); + + // Check the working case. + runner.GetPolicy()->SetTokenLevel(USER_INTERACTIVE, USER_INTERACTIVE); + + EXPECT_EQ(SBOX_ALL_OK, + runner.GetPolicy()->AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_ALL_EXEC, + L"this is not important")); +} + +TEST(ProcessPolicyTest, CreateProcessAW) { + TestRunner runner; + std::wstring maybe_virtual_exe_path = MakePathToSys(L"findstr.exe", false); + std::wstring non_virtual_exe_path = MakePathToSys32(L"findstr.exe", false); + ASSERT_TRUE(!maybe_virtual_exe_path.empty()); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_MIN_EXEC, + maybe_virtual_exe_path.c_str())); + + if (non_virtual_exe_path != maybe_virtual_exe_path) { + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_MIN_EXEC, + non_virtual_exe_path.c_str())); + } + + // Need to add directory rules for the directories that we use in + // SetCurrentDirectory. + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_DIR_ANY, L"")); + + wchar_t current_directory[MAX_PATH]; + DWORD ret = ::GetCurrentDirectory(MAX_PATH, current_directory); + ASSERT_TRUE(0 != ret && ret < MAX_PATH); + + wcscat_s(current_directory, MAX_PATH, L"\\"); + EXPECT_TRUE( + runner.AddFsRule(TargetPolicy::FILES_ALLOW_DIR_ANY, current_directory)); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp1 calc.exe")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp2 calc.exe")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp3 calc.exe")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp4 calc.exe")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp5 calc.exe")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp6 calc.exe")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp1 findstr.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp2 findstr.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp3 findstr.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp4 findstr.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp5 findstr.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp6 findstr.exe")); +} + +// Tests that the broker correctly handles a process crashing within the job. +// Fails on Windows ARM64: https://crbug.com/905526 +#if defined(ARCH_CPU_ARM64) +#define MAYBE_CreateProcessCrashy DISABLED_CreateProcessCrashy +#else +#define MAYBE_CreateProcessCrashy CreateProcessCrashy +#endif +TEST(ProcessPolicyTest, MAYBE_CreateProcessCrashy) { + TestRunner runner; + EXPECT_EQ(static_cast<int>(STATUS_BREAKPOINT), + runner.RunTest(L"Process_Crash")); +} + +TEST(ProcessPolicyTest, CreateProcessWithCWD) { + TestRunner runner; + std::wstring sys_path = MakePathToSys(L"", false); + while (!sys_path.empty() && sys_path.back() == L'\\') + sys_path.erase(sys_path.length() - 1); + + std::wstring exe_path = MakePathToSys(L"cmd.exe", false); + std::wstring cmd_line = + L"\"/c if \\\"%CD%\\\" NEQ \\\"" + sys_path + L"\\\" exit 1\""; + + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_MIN_EXEC, exe_path.c_str())); + + std::wstring command = + L"Process_CheckExitCode cmd.exe " + cmd_line + L" " + sys_path; + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command.c_str())); +} + +TEST(ProcessPolicyTest, OpenToken) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Process_OpenToken")); +} + +TEST(ProcessPolicyTest, TestGetProcessTokenMinAccess) { + TestRunner runner; + std::wstring exe_path = MakePathToSys(L"findstr.exe", false); + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_MIN_EXEC, exe_path.c_str())); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"Process_GetChildProcessToken findstr.exe")); +} + +TEST(ProcessPolicyTest, TestGetProcessTokenMaxAccess) { + TestRunner runner(JOB_UNPROTECTED, USER_INTERACTIVE, USER_INTERACTIVE); + std::wstring exe_path = MakePathToSys(L"findstr.exe", false); + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_ALL_EXEC, exe_path.c_str())); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_GetChildProcessToken findstr.exe")); +} + +TEST(ProcessPolicyTest, TestGetProcessTokenMinAccessNoJob) { + TestRunner runner(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); + std::wstring exe_path = MakePathToSys(L"findstr.exe", false); + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_MIN_EXEC, exe_path.c_str())); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"Process_GetChildProcessToken findstr.exe")); +} + +TEST(ProcessPolicyTest, TestGetProcessTokenMaxAccessNoJob) { + TestRunner runner(JOB_NONE, USER_INTERACTIVE, USER_INTERACTIVE); + std::wstring exe_path = MakePathToSys(L"findstr.exe", false); + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_ALL_EXEC, exe_path.c_str())); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_GetChildProcessToken findstr.exe")); +} + +TEST(ProcessPolicyTest, TestCreateProcessA) { + TestRunner runner; + sandbox::TargetPolicy* policy = runner.GetPolicy(); + policy->SetJobLevel(JOB_NONE, 0); + policy->SetTokenLevel(USER_UNPROTECTED, USER_UNPROTECTED); + std::wstring exe_path = MakePathToSys(L"calc.exe", false); + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_ALL_EXEC, exe_path.c_str())); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_CreateProcessA calc.exe")); +} + +// This tests that the CreateThread works with CSRSS not locked down. +// In other words, that the interception passes through OK. +TEST(ProcessPolicyTest, TestCreateThreadWithCsrss) { + TestRunner runner(JOB_NONE, USER_INTERACTIVE, USER_INTERACTIVE); + runner.SetDisableCsrss(false); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Process_CreateThread")); +} + +// This tests that the CreateThread works with CSRSS locked down. +// In other words, that the interception correctly works. +TEST(ProcessPolicyTest, TestCreateThreadWithoutCsrss) { + TestRunner runner(JOB_NONE, USER_INTERACTIVE, USER_INTERACTIVE); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Process_CreateThread")); +} + +// This tests that our CreateThread interceptors works when called directly. +TEST(ProcessPolicyTest, TestCreateThreadOutsideSandbox) { + DWORD pid = ::GetCurrentProcessId(); + std::wstring event_name = GenerateEventName(pid); + ASSERT_STRNE(nullptr, event_name.c_str()); + HANDLE event = ::CreateEvent(nullptr, true, false, event_name.c_str()); + EXPECT_NE(static_cast<HANDLE>(nullptr), event); + + DWORD thread_id = 0; + HANDLE thread = nullptr; + thread = TargetCreateThread( + ::CreateThread, nullptr, 0, &TestThreadFunc, + reinterpret_cast<LPVOID>(static_cast<uintptr_t>(pid)), 0, &thread_id); + EXPECT_NE(static_cast<HANDLE>(nullptr), thread); + EXPECT_EQ(WAIT_OBJECT_0, WaitForSingleObject(thread, INFINITE)); + EXPECT_EQ(WAIT_OBJECT_0, WaitForSingleObject(event, INFINITE)); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_thread_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/process_thread_dispatcher.cc new file mode 100644 index 0000000000..7f0b0a6542 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_thread_dispatcher.cc @@ -0,0 +1,275 @@ +// Copyright (c) 2013 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/win/src/process_thread_dispatcher.h" + +#include <stddef.h> +#include <stdint.h> + +#include "base/logging.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/process_thread_interception.h" +#include "sandbox/win/src/process_thread_policy.h" +#include "sandbox/win/src/sandbox.h" + +namespace { + +// Extracts the application name from a command line. +// +// The application name is the first element of the command line. If +// there is no quotes, the first element is delimited by the first space. +// If there are quotes, the first element is delimited by the quotes. +// +// The create process call is smarter than us. It tries really hard to launch +// the process even if the command line is wrong. For example: +// "c:\program files\test param" will first try to launch c:\program.exe then +// c:\program files\test.exe. We don't do that, we stop after at the first +// space when there is no quotes. +std::wstring GetPathFromCmdLine(const std::wstring& cmd_line) { + std::wstring exe_name; + // Check if it starts with '"'. + if (cmd_line[0] == L'\"') { + // Find the position of the second '"', this terminates the path. + std::wstring::size_type pos = cmd_line.find(L'\"', 1); + if (std::wstring::npos == pos) + return cmd_line; + exe_name = cmd_line.substr(1, pos - 1); + } else { + // There is no '"', that means that the appname is terminated at the + // first space. + std::wstring::size_type pos = cmd_line.find(L' '); + if (std::wstring::npos == pos) { + // There is no space, the cmd_line contains only the app_name + exe_name = cmd_line; + } else { + exe_name = cmd_line.substr(0, pos); + } + } + + return exe_name; +} + +// Returns true is the path in parameter is relative. False if it's +// absolute. +bool IsPathRelative(const std::wstring& path) { + // A path is Relative if it's not a UNC path beginnning with \\ or a + // path beginning with a drive. (i.e. X:\) + if (path.find(L"\\\\") == 0 || path.find(L":\\") == 1) + return false; + return true; +} + +// Converts a relative path to an absolute path. +bool ConvertToAbsolutePath(const std::wstring& child_current_directory, + bool use_env_path, + std::wstring* path) { + wchar_t file_buffer[MAX_PATH]; + wchar_t* file_part = nullptr; + + // Here we should start by looking at the path where the child application was + // started. We don't have this information yet. + DWORD result = 0; + if (use_env_path) { + // Try with the complete path + result = ::SearchPath(nullptr, path->c_str(), nullptr, MAX_PATH, + file_buffer, &file_part); + } + + if (0 == result) { + // Try with the current directory of the child + result = ::SearchPath(child_current_directory.c_str(), path->c_str(), + nullptr, MAX_PATH, file_buffer, &file_part); + } + + if (0 == result || result >= MAX_PATH) + return false; + + *path = file_buffer; + return true; +} + +} // namespace +namespace sandbox { + +ThreadProcessDispatcher::ThreadProcessDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall open_thread = { + {IpcTag::NTOPENTHREAD, {UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::NtOpenThread)}; + + static const IPCCall open_process = { + {IpcTag::NTOPENPROCESS, {UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::NtOpenProcess)}; + + static const IPCCall process_token = { + {IpcTag::NTOPENPROCESSTOKEN, {VOIDPTR_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::NtOpenProcessToken)}; + + static const IPCCall process_tokenex = { + {IpcTag::NTOPENPROCESSTOKENEX, {VOIDPTR_TYPE, UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::NtOpenProcessTokenEx)}; + + static const IPCCall create_params = { + {IpcTag::CREATEPROCESSW, + {WCHAR_TYPE, WCHAR_TYPE, WCHAR_TYPE, WCHAR_TYPE, INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::CreateProcessW)}; + + // NOTE(liamjm): 2nd param is size_t: Using VOIDPTR_TYPE as they are + // the same size on windows. + static_assert(sizeof(size_t) == sizeof(void*), + "VOIDPTR_TYPE not same size as size_t"); + static const IPCCall create_thread_params = { + {IpcTag::CREATETHREAD, + {VOIDPTR_TYPE, VOIDPTR_TYPE, VOIDPTR_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::CreateThread)}; + + ipc_calls_.push_back(open_thread); + ipc_calls_.push_back(open_process); + ipc_calls_.push_back(process_token); + ipc_calls_.push_back(process_tokenex); + ipc_calls_.push_back(create_params); + ipc_calls_.push_back(create_thread_params); +} + +bool ThreadProcessDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + switch (service) { + case IpcTag::NTOPENTHREAD: + case IpcTag::NTOPENPROCESS: + case IpcTag::NTOPENPROCESSTOKEN: + case IpcTag::NTOPENPROCESSTOKENEX: + case IpcTag::CREATETHREAD: + // There is no explicit policy for these services. + NOTREACHED(); + return false; + + case IpcTag::CREATEPROCESSW: + return INTERCEPT_EAT(manager, kKerneldllName, CreateProcessW, + CREATE_PROCESSW_ID, 44) && + INTERCEPT_EAT(manager, L"kernel32.dll", CreateProcessA, + CREATE_PROCESSA_ID, 44); + + default: + return false; + } +} + +bool ThreadProcessDispatcher::NtOpenThread(IPCInfo* ipc, + uint32_t desired_access, + uint32_t thread_id) { + HANDLE handle; + NTSTATUS ret = ProcessPolicy::OpenThreadAction( + *ipc->client_info, desired_access, thread_id, &handle); + ipc->return_info.nt_status = ret; + ipc->return_info.handle = handle; + return true; +} + +bool ThreadProcessDispatcher::NtOpenProcess(IPCInfo* ipc, + uint32_t desired_access, + uint32_t process_id) { + HANDLE handle; + NTSTATUS ret = ProcessPolicy::OpenProcessAction( + *ipc->client_info, desired_access, process_id, &handle); + ipc->return_info.nt_status = ret; + ipc->return_info.handle = handle; + return true; +} + +bool ThreadProcessDispatcher::NtOpenProcessToken(IPCInfo* ipc, + HANDLE process, + uint32_t desired_access) { + HANDLE handle; + NTSTATUS ret = ProcessPolicy::OpenProcessTokenAction( + *ipc->client_info, process, desired_access, &handle); + ipc->return_info.nt_status = ret; + ipc->return_info.handle = handle; + return true; +} + +bool ThreadProcessDispatcher::NtOpenProcessTokenEx(IPCInfo* ipc, + HANDLE process, + uint32_t desired_access, + uint32_t attributes) { + HANDLE handle; + NTSTATUS ret = ProcessPolicy::OpenProcessTokenExAction( + *ipc->client_info, process, desired_access, attributes, &handle); + ipc->return_info.nt_status = ret; + ipc->return_info.handle = handle; + return true; +} + +bool ThreadProcessDispatcher::CreateProcessW(IPCInfo* ipc, + std::wstring* name, + std::wstring* cmd_line, + std::wstring* cur_dir, + std::wstring* target_cur_dir, + CountedBuffer* info) { + if (sizeof(PROCESS_INFORMATION) != info->Size()) + return false; + + // Check if there is an application name. + std::wstring exe_name; + if (!name->empty()) + exe_name = *name; + else + exe_name = GetPathFromCmdLine(*cmd_line); + + if (IsPathRelative(exe_name)) { + if (!ConvertToAbsolutePath(*cur_dir, name->empty(), &exe_name)) { + // Cannot find the path. Maybe the file does not exist. + ipc->return_info.win32_result = ERROR_FILE_NOT_FOUND; + return true; + } + } + + const wchar_t* const_exe_name = exe_name.c_str(); + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(const_exe_name); + + EvalResult eval = + policy_base_->EvalPolicy(IpcTag::CREATEPROCESSW, params.GetBase()); + + PROCESS_INFORMATION* proc_info = + reinterpret_cast<PROCESS_INFORMATION*>(info->Buffer()); + // Here we force the app_name to be the one we used for the policy lookup. + // If our logic was wrong, at least we wont allow create a random process. + DWORD ret = ProcessPolicy::CreateProcessWAction( + eval, *ipc->client_info, exe_name, *cmd_line, *target_cur_dir, proc_info); + + ipc->return_info.win32_result = ret; + return true; +} + +bool ThreadProcessDispatcher::CreateThread(IPCInfo* ipc, + SIZE_T stack_size, + LPTHREAD_START_ROUTINE start_address, + LPVOID parameter, + DWORD creation_flags) { + if (!start_address) { + return false; + } + + HANDLE handle; + DWORD ret = ProcessPolicy::CreateThreadAction( + *ipc->client_info, stack_size, start_address, parameter, creation_flags, + nullptr, &handle); + + ipc->return_info.nt_status = ret; + ipc->return_info.handle = handle; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_thread_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/process_thread_dispatcher.h new file mode 100644 index 0000000000..07466c46e2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_thread_dispatcher.h @@ -0,0 +1,69 @@ +// Copyright (c) 2010 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. + +#ifndef SANDBOX_SRC_PROCESS_THREAD_DISPATCHER_H_ +#define SANDBOX_SRC_PROCESS_THREAD_DISPATCHER_H_ + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles process and thread-related IPC calls. +class ThreadProcessDispatcher : public Dispatcher { + public: + explicit ThreadProcessDispatcher(PolicyBase* policy_base); + ~ThreadProcessDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Processes IPC requests coming from calls to NtOpenThread() in the target. + bool NtOpenThread(IPCInfo* ipc, uint32_t desired_access, uint32_t thread_id); + + // Processes IPC requests coming from calls to NtOpenProcess() in the target. + bool NtOpenProcess(IPCInfo* ipc, + uint32_t desired_access, + uint32_t process_id); + + // Processes IPC requests from calls to NtOpenProcessToken() in the target. + bool NtOpenProcessToken(IPCInfo* ipc, + HANDLE process, + uint32_t desired_access); + + // Processes IPC requests from calls to NtOpenProcessTokenEx() in the target. + bool NtOpenProcessTokenEx(IPCInfo* ipc, + HANDLE process, + uint32_t desired_access, + uint32_t attributes); + + // Processes IPC requests coming from calls to CreateProcessW() in the target. + bool CreateProcessW(IPCInfo* ipc, + std::wstring* name, + std::wstring* cmd_line, + std::wstring* cur_dir, + std::wstring* target_cur_dir, + CountedBuffer* info); + + // Processes IPC requests coming from calls to CreateThread() in the target. + bool CreateThread(IPCInfo* ipc, + SIZE_T stack_size, + LPTHREAD_START_ROUTINE start_address, + LPVOID parameter, + DWORD creation_flags); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(ThreadProcessDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_THREAD_DISPATCHER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/process_thread_interception.cc b/security/sandbox/chromium/sandbox/win/src/process_thread_interception.cc new file mode 100644 index 0000000000..0bd3d5a8d2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_thread_interception.cc @@ -0,0 +1,520 @@ +// Copyright (c) 2013 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/win/src/process_thread_interception.h" + +#include <stdint.h> + +#include "base/win/windows_version.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" +#include "mozilla/sandboxing/sandboxLogging.h" + +namespace sandbox { + +SANDBOX_INTERCEPT NtExports g_nt; + +// Hooks NtOpenThread and proxy the call to the broker if it's trying to +// open a thread in the same process. +NTSTATUS WINAPI TargetNtOpenThread(NtOpenThreadFunction orig_OpenThread, + PHANDLE thread, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id) { + NTSTATUS status = + orig_OpenThread(thread, desired_access, object_attributes, client_id); + if (NT_SUCCESS(status)) + return status; + + mozilla::sandboxing::LogBlocked("NtOpenThread"); + do { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + break; + if (!client_id) + break; + + uint32_t thread_id = 0; + bool should_break = false; + __try { + // We support only the calls for the current process + if (client_id->UniqueProcess) + should_break = true; + + // Object attributes should be nullptr or empty. + if (!should_break && object_attributes) { + if (object_attributes->Attributes || object_attributes->ObjectName || + object_attributes->RootDirectory || + object_attributes->SecurityDescriptor || + object_attributes->SecurityQualityOfService) { + should_break = true; + } + } + + thread_id = static_cast<uint32_t>( + reinterpret_cast<ULONG_PTR>(client_id->UniqueThread)); + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + if (should_break) + break; + + if (!ValidParameter(thread, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::NTOPENTHREAD, desired_access, + thread_id, &answer); + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + // The nt_status here is most likely STATUS_INVALID_CID because + // in the broker we set the process id in the CID (client ID) param + // to be the current process. If you try to open a thread from another + // process you will get this INVALID_CID error. On the other hand, if you + // try to open a thread in your own process, it should return success. + // We don't want to return STATUS_INVALID_CID here, so we return the + // return of the original open thread status, which is most likely + // STATUS_ACCESS_DENIED. + break; + + __try { + // Write the output parameters. + *thread = answer.handle; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + mozilla::sandboxing::LogAllowed("NtOpenThread"); + return answer.nt_status; + } while (false); + + return status; +} + +// Hooks NtOpenProcess and proxy the call to the broker if it's trying to +// open the current process. +NTSTATUS WINAPI TargetNtOpenProcess(NtOpenProcessFunction orig_OpenProcess, + PHANDLE process, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id) { + NTSTATUS status = + orig_OpenProcess(process, desired_access, object_attributes, client_id); + if (NT_SUCCESS(status)) + return status; + + do { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + break; + if (!client_id) + break; + + uint32_t process_id = 0; + bool should_break = false; + __try { + // Object attributes should be nullptr or empty. + if (!should_break && object_attributes) { + if (object_attributes->Attributes || object_attributes->ObjectName || + object_attributes->RootDirectory || + object_attributes->SecurityDescriptor || + object_attributes->SecurityQualityOfService) { + should_break = true; + } + } + + process_id = static_cast<uint32_t>( + reinterpret_cast<ULONG_PTR>(client_id->UniqueProcess)); + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + if (should_break) + break; + + if (!ValidParameter(process, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::NTOPENPROCESS, desired_access, + process_id, &answer); + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + return answer.nt_status; + + __try { + // Write the output parameters. + *process = answer.handle; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + return answer.nt_status; + } while (false); + + return status; +} + +NTSTATUS WINAPI +TargetNtOpenProcessToken(NtOpenProcessTokenFunction orig_OpenProcessToken, + HANDLE process, + ACCESS_MASK desired_access, + PHANDLE token) { + NTSTATUS status = orig_OpenProcessToken(process, desired_access, token); + if (NT_SUCCESS(status)) + return status; + + mozilla::sandboxing::LogBlocked("NtOpenProcessToken"); + do { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + break; + + if (CURRENT_PROCESS != process) + break; + + if (!ValidParameter(token, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::NTOPENPROCESSTOKEN, process, + desired_access, &answer); + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + return answer.nt_status; + + __try { + // Write the output parameters. + *token = answer.handle; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + mozilla::sandboxing::LogAllowed("NtOpenProcessToken"); + return answer.nt_status; + } while (false); + + return status; +} + +NTSTATUS WINAPI +TargetNtOpenProcessTokenEx(NtOpenProcessTokenExFunction orig_OpenProcessTokenEx, + HANDLE process, + ACCESS_MASK desired_access, + ULONG handle_attributes, + PHANDLE token) { + NTSTATUS status = orig_OpenProcessTokenEx(process, desired_access, + handle_attributes, token); + if (NT_SUCCESS(status)) + return status; + + mozilla::sandboxing::LogBlocked("NtOpenProcessTokenEx"); + do { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + break; + + if (CURRENT_PROCESS != process) + break; + + if (!ValidParameter(token, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::NTOPENPROCESSTOKENEX, process, + desired_access, handle_attributes, &answer); + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + return answer.nt_status; + + __try { + // Write the output parameters. + *token = answer.handle; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + mozilla::sandboxing::LogAllowed("NtOpenProcessTokenEx"); + return answer.nt_status; + } while (false); + + return status; +} + +BOOL WINAPI TargetCreateProcessW(CreateProcessWFunction orig_CreateProcessW, + LPCWSTR application_name, + LPWSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCWSTR current_directory, + LPSTARTUPINFOW startup_info, + LPPROCESS_INFORMATION process_information) { + if (SandboxFactory::GetTargetServices()->GetState()->IsCsrssConnected() && + orig_CreateProcessW(application_name, command_line, process_attributes, + thread_attributes, inherit_handles, flags, + environment, current_directory, startup_info, + process_information)) { + return true; + } + + mozilla::sandboxing::LogBlocked("CreateProcessW", application_name); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return false; + + // Don't call GetLastError before InitCalled() succeeds because kernel32 may + // not be mapped yet. + DWORD original_error = ::GetLastError(); + + do { + if (!ValidParameter(process_information, sizeof(PROCESS_INFORMATION), + WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + const wchar_t* cur_dir = nullptr; + + wchar_t this_current_directory[MAX_PATH]; + DWORD result = ::GetCurrentDirectory(MAX_PATH, this_current_directory); + if (0 != result && result < MAX_PATH) + cur_dir = this_current_directory; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + + InOutCountedBuffer proc_info(process_information, + sizeof(PROCESS_INFORMATION)); + + ResultCode code = + CrossCall(ipc, IpcTag::CREATEPROCESSW, application_name, command_line, + cur_dir, current_directory, proc_info, &answer); + if (SBOX_ALL_OK != code) + break; + + ::SetLastError(answer.win32_result); + if (ERROR_SUCCESS != answer.win32_result) + return false; + + mozilla::sandboxing::LogAllowed("CreateProcessW", application_name); + return true; + } while (false); + + ::SetLastError(original_error); + return false; +} + +BOOL WINAPI TargetCreateProcessA(CreateProcessAFunction orig_CreateProcessA, + LPCSTR application_name, + LPSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCSTR current_directory, + LPSTARTUPINFOA startup_info, + LPPROCESS_INFORMATION process_information) { + if (SandboxFactory::GetTargetServices()->GetState()->IsCsrssConnected() && + orig_CreateProcessA(application_name, command_line, process_attributes, + thread_attributes, inherit_handles, flags, + environment, current_directory, startup_info, + process_information)) { + return true; + } + + mozilla::sandboxing::LogBlocked("CreateProcessA", application_name); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return false; + + // Don't call GetLastError before InitCalled() succeeds because kernel32 may + // not be mapped yet. + DWORD original_error = ::GetLastError(); + + do { + if (!ValidParameter(process_information, sizeof(PROCESS_INFORMATION), + WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + // Convert the input params to unicode. + UNICODE_STRING* cmd_unicode = nullptr; + UNICODE_STRING* app_unicode = nullptr; + UNICODE_STRING* cwd_unicode = nullptr; + if (command_line) { + cmd_unicode = AnsiToUnicode(command_line); + if (!cmd_unicode) + break; + } + + if (application_name) { + app_unicode = AnsiToUnicode(application_name); + if (!app_unicode) { + operator delete(cmd_unicode, NT_ALLOC); + break; + } + } + + if (current_directory) { + cwd_unicode = AnsiToUnicode(current_directory); + if (!cwd_unicode) { + operator delete(cmd_unicode, NT_ALLOC); + operator delete(app_unicode, NT_ALLOC); + break; + } + } + + const wchar_t* cmd_line = cmd_unicode ? cmd_unicode->Buffer : nullptr; + const wchar_t* app_name = app_unicode ? app_unicode->Buffer : nullptr; + const wchar_t* cwd = cwd_unicode ? cwd_unicode->Buffer : nullptr; + const wchar_t* cur_dir = nullptr; + + wchar_t target_current_directory[MAX_PATH]; + DWORD result = ::GetCurrentDirectory(MAX_PATH, target_current_directory); + if (0 != result && result < MAX_PATH) + cur_dir = target_current_directory; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + + InOutCountedBuffer proc_info(process_information, + sizeof(PROCESS_INFORMATION)); + + ResultCode code = CrossCall(ipc, IpcTag::CREATEPROCESSW, app_name, cmd_line, + cur_dir, cwd, proc_info, &answer); + + operator delete(cmd_unicode, NT_ALLOC); + operator delete(app_unicode, NT_ALLOC); + operator delete(cwd_unicode, NT_ALLOC); + + if (SBOX_ALL_OK != code) + break; + + ::SetLastError(answer.win32_result); + if (ERROR_SUCCESS != answer.win32_result) + return false; + + mozilla::sandboxing::LogAllowed("CreateProcessA", application_name); + return true; + } while (false); + + ::SetLastError(original_error); + return false; +} + +HANDLE WINAPI TargetCreateThread(CreateThreadFunction orig_CreateThread, + LPSECURITY_ATTRIBUTES thread_attributes, + SIZE_T stack_size, + LPTHREAD_START_ROUTINE start_address, + LPVOID parameter, + DWORD creation_flags, + LPDWORD thread_id) { + HANDLE hThread = nullptr; + + TargetServices* target_services = SandboxFactory::GetTargetServices(); + if (!target_services || target_services->GetState()->IsCsrssConnected()) { + hThread = orig_CreateThread(thread_attributes, stack_size, start_address, + parameter, creation_flags, thread_id); + if (hThread) + return hThread; + } + + DWORD original_error = ::GetLastError(); + do { + if (!target_services) + break; + + // We don't trust that the IPC can work this early. + if (!target_services->GetState()->InitCalled()) + break; + + __try { + if (thread_id && !ValidParameter(thread_id, sizeof(*thread_id), WRITE)) + break; + + if (!start_address) + break; + // We don't support thread_attributes not being null. + if (thread_attributes) + break; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + + // NOTE: we don't pass the thread_attributes through. This matches the + // approach in CreateProcess and in CreateThreadInternal(). + ResultCode code = CrossCall(ipc, IpcTag::CREATETHREAD, + reinterpret_cast<LPVOID>(stack_size), + reinterpret_cast<LPVOID>(start_address), + parameter, creation_flags, &answer); + if (SBOX_ALL_OK != code) + break; + + ::SetLastError(answer.win32_result); + if (ERROR_SUCCESS != answer.win32_result) + return nullptr; + + __try { + if (thread_id) + *thread_id = ::GetThreadId(answer.handle); + return answer.handle; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + } while (false); + + ::SetLastError(original_error); + return nullptr; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_thread_interception.h b/security/sandbox/chromium/sandbox/win/src/process_thread_interception.h new file mode 100644 index 0000000000..2608d7d31e --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_thread_interception.h @@ -0,0 +1,101 @@ +// Copyright (c) 2014 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. + +#ifndef SANDBOX_WIN_SRC_PROCESS_THREAD_INTERCEPTION_H_ +#define SANDBOX_WIN_SRC_PROCESS_THREAD_INTERCEPTION_H_ + +#include <windows.h> + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +namespace { + +using CreateProcessWFunction = decltype(&::CreateProcessW); + +using CreateProcessAFunction = decltype(&::CreateProcessA); + +using CreateThreadFunction = decltype(&::CreateThread); + +using GetUserDefaultLCIDFunction = decltype(&::GetUserDefaultLCID); + +} // namespace + +extern "C" { + +// Interception of NtOpenThread on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenThread(NtOpenThreadFunction orig_OpenThread, + PHANDLE thread, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id); + +// Interception of NtOpenProcess on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcess(NtOpenProcessFunction orig_OpenProcess, + PHANDLE process, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id); + +// Interception of NtOpenProcessToken on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcessToken(NtOpenProcessTokenFunction orig_OpenProcessToken, + HANDLE process, + ACCESS_MASK desired_access, + PHANDLE token); + +// Interception of NtOpenProcessTokenEx on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcessTokenEx(NtOpenProcessTokenExFunction orig_OpenProcessTokenEx, + HANDLE process, + ACCESS_MASK desired_access, + ULONG handle_attributes, + PHANDLE token); + +// Interception of CreateProcessW and A in kernel32.dll. +SANDBOX_INTERCEPT BOOL WINAPI +TargetCreateProcessW(CreateProcessWFunction orig_CreateProcessW, + LPCWSTR application_name, + LPWSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCWSTR current_directory, + LPSTARTUPINFOW startup_info, + LPPROCESS_INFORMATION process_information); + +SANDBOX_INTERCEPT BOOL WINAPI +TargetCreateProcessA(CreateProcessAFunction orig_CreateProcessA, + LPCSTR application_name, + LPSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCSTR current_directory, + LPSTARTUPINFOA startup_info, + LPPROCESS_INFORMATION process_information); + +// Interception of CreateThread in kernel32.dll. +SANDBOX_INTERCEPT HANDLE WINAPI +TargetCreateThread(CreateThreadFunction orig_CreateThread, + LPSECURITY_ATTRIBUTES thread_attributes, + SIZE_T stack_size, + LPTHREAD_START_ROUTINE start_address, + LPVOID parameter, + DWORD creation_flags, + LPDWORD thread_id); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_PROCESS_THREAD_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/process_thread_policy.cc b/security/sandbox/chromium/sandbox/win/src/process_thread_policy.cc new file mode 100644 index 0000000000..20146a83df --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_thread_policy.cc @@ -0,0 +1,269 @@ +// Copyright (c) 2011 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/win/src/process_thread_policy.h" + +#include <stdint.h> + +#include <memory> +#include <string> + +#include "base/memory/free_deleter.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +// These are the only safe rights that can be given to a sandboxed +// process for the process created by the broker. All others are potential +// vectors of privilege elevation. +const DWORD kProcessRights = SYNCHRONIZE | PROCESS_QUERY_INFORMATION | + PROCESS_QUERY_LIMITED_INFORMATION | + PROCESS_TERMINATE | PROCESS_SUSPEND_RESUME; + +const DWORD kThreadRights = SYNCHRONIZE | THREAD_TERMINATE | + THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION | + THREAD_QUERY_LIMITED_INFORMATION | + THREAD_SET_LIMITED_INFORMATION; + +// Creates a child process and duplicates the handles to 'target_process'. The +// remaining parameters are the same as CreateProcess(). +bool CreateProcessExWHelper(HANDLE target_process, + bool give_full_access, + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + bool bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation) { + if (!::CreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes, + lpThreadAttributes, bInheritHandles, dwCreationFlags, + lpEnvironment, lpCurrentDirectory, lpStartupInfo, + lpProcessInformation)) { + return false; + } + + DWORD process_access = kProcessRights; + DWORD thread_access = kThreadRights; + if (give_full_access) { + process_access = PROCESS_ALL_ACCESS; + thread_access = THREAD_ALL_ACCESS; + } + if (!::DuplicateHandle(::GetCurrentProcess(), lpProcessInformation->hProcess, + target_process, &lpProcessInformation->hProcess, + process_access, false, DUPLICATE_CLOSE_SOURCE)) { + ::CloseHandle(lpProcessInformation->hThread); + return false; + } + if (!::DuplicateHandle(::GetCurrentProcess(), lpProcessInformation->hThread, + target_process, &lpProcessInformation->hThread, + thread_access, false, DUPLICATE_CLOSE_SOURCE)) { + return false; + } + return true; +} + +} // namespace + +namespace sandbox { + +bool ProcessPolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + std::unique_ptr<PolicyRule> process; + switch (semantics) { + case TargetPolicy::PROCESS_MIN_EXEC: { + process.reset(new PolicyRule(GIVE_READONLY)); + break; + }; + case TargetPolicy::PROCESS_ALL_EXEC: { + process.reset(new PolicyRule(GIVE_ALLACCESS)); + break; + }; + default: { return false; }; + } + + if (!process->AddStringMatch(IF, NameBased::NAME, name, CASE_INSENSITIVE)) { + return false; + } + if (!policy->AddRule(IpcTag::CREATEPROCESSW, process.get())) { + return false; + } + return true; +} + +NTSTATUS ProcessPolicy::OpenThreadAction(const ClientInfo& client_info, + uint32_t desired_access, + uint32_t thread_id, + HANDLE* handle) { + *handle = nullptr; + + NtOpenThreadFunction NtOpenThread = nullptr; + ResolveNTFunctionPtr("NtOpenThread", &NtOpenThread); + + OBJECT_ATTRIBUTES attributes = {0}; + attributes.Length = sizeof(attributes); + CLIENT_ID client_id = {0}; + client_id.UniqueProcess = + reinterpret_cast<PVOID>(static_cast<ULONG_PTR>(client_info.process_id)); + client_id.UniqueThread = + reinterpret_cast<PVOID>(static_cast<ULONG_PTR>(thread_id)); + + HANDLE local_handle = nullptr; + NTSTATUS status = + NtOpenThread(&local_handle, desired_access, &attributes, &client_id); + if (NT_SUCCESS(status)) { + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + } + + return status; +} + +NTSTATUS ProcessPolicy::OpenProcessAction(const ClientInfo& client_info, + uint32_t desired_access, + uint32_t process_id, + HANDLE* handle) { + *handle = nullptr; + + NtOpenProcessFunction NtOpenProcess = nullptr; + ResolveNTFunctionPtr("NtOpenProcess", &NtOpenProcess); + + if (client_info.process_id != process_id) + return STATUS_ACCESS_DENIED; + + OBJECT_ATTRIBUTES attributes = {0}; + attributes.Length = sizeof(attributes); + CLIENT_ID client_id = {0}; + client_id.UniqueProcess = + reinterpret_cast<PVOID>(static_cast<ULONG_PTR>(client_info.process_id)); + HANDLE local_handle = nullptr; + NTSTATUS status = + NtOpenProcess(&local_handle, desired_access, &attributes, &client_id); + if (NT_SUCCESS(status)) { + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + } + + return status; +} + +NTSTATUS ProcessPolicy::OpenProcessTokenAction(const ClientInfo& client_info, + HANDLE process, + uint32_t desired_access, + HANDLE* handle) { + *handle = nullptr; + NtOpenProcessTokenFunction NtOpenProcessToken = nullptr; + ResolveNTFunctionPtr("NtOpenProcessToken", &NtOpenProcessToken); + + if (CURRENT_PROCESS != process) + return STATUS_ACCESS_DENIED; + + HANDLE local_handle = nullptr; + NTSTATUS status = + NtOpenProcessToken(client_info.process, desired_access, &local_handle); + if (NT_SUCCESS(status)) { + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + } + return status; +} + +NTSTATUS ProcessPolicy::OpenProcessTokenExAction(const ClientInfo& client_info, + HANDLE process, + uint32_t desired_access, + uint32_t attributes, + HANDLE* handle) { + *handle = nullptr; + NtOpenProcessTokenExFunction NtOpenProcessTokenEx = nullptr; + ResolveNTFunctionPtr("NtOpenProcessTokenEx", &NtOpenProcessTokenEx); + + if (CURRENT_PROCESS != process) + return STATUS_ACCESS_DENIED; + + HANDLE local_handle = nullptr; + NTSTATUS status = NtOpenProcessTokenEx(client_info.process, desired_access, + attributes, &local_handle); + if (NT_SUCCESS(status)) { + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + } + return status; +} + +DWORD ProcessPolicy::CreateProcessWAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& app_name, + const std::wstring& command_line, + const std::wstring& current_dir, + PROCESS_INFORMATION* process_info) { + // The only action supported is ASK_BROKER which means create the process. + if (GIVE_ALLACCESS != eval_result && GIVE_READONLY != eval_result) { + return ERROR_ACCESS_DENIED; + } + + STARTUPINFO startup_info = {0}; + startup_info.cb = sizeof(startup_info); + std::unique_ptr<wchar_t, base::FreeDeleter> cmd_line( + _wcsdup(command_line.c_str())); + + bool should_give_full_access = (GIVE_ALLACCESS == eval_result); + + const wchar_t* cwd = current_dir.c_str(); + if (current_dir.empty()) + cwd = nullptr; + + if (!CreateProcessExWHelper(client_info.process, should_give_full_access, + app_name.c_str(), cmd_line.get(), nullptr, + nullptr, false, 0, nullptr, cwd, &startup_info, + process_info)) { + return ERROR_ACCESS_DENIED; + } + return ERROR_SUCCESS; +} + +DWORD ProcessPolicy::CreateThreadAction( + const ClientInfo& client_info, + const SIZE_T stack_size, + const LPTHREAD_START_ROUTINE start_address, + const LPVOID parameter, + const DWORD creation_flags, + LPDWORD thread_id, + HANDLE* handle) { + *handle = nullptr; + HANDLE local_handle = + ::CreateRemoteThread(client_info.process, nullptr, stack_size, + start_address, parameter, creation_flags, thread_id); + if (!local_handle) { + return ::GetLastError(); + } + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return ERROR_ACCESS_DENIED; + } + return ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_thread_policy.h b/security/sandbox/chromium/sandbox/win/src/process_thread_policy.h new file mode 100644 index 0000000000..f6f96dd0ff --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_thread_policy.h @@ -0,0 +1,91 @@ +// Copyright (c) 2006-2010 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. + +#ifndef SANDBOX_SRC_PROCESS_THREAD_POLICY_H_ +#define SANDBOX_SRC_PROCESS_THREAD_POLICY_H_ + +#include <stdint.h> + +#include <string> + +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +// This class centralizes most of the knowledge related to process execution. +class ProcessPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level. + // policy rule for process creation + // 'name' is the executable to be spawn. + // 'semantics' is the desired semantics. + // 'policy' is the policy generator to which the rules are going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Opens a thread from the child process and returns the handle. + // client_info contains the information about the child process, + // desired_access is the access requested by the child and thread_id + // is the thread_id to be opened. + // The function returns the return value of NtOpenThread. + static NTSTATUS OpenThreadAction(const ClientInfo& client_info, + uint32_t desired_access, + uint32_t thread_id, + HANDLE* handle); + + // Opens the process id passed in and returns the duplicated handle to + // the child. We only allow the child processes to open themselves. Any other + // pid open is denied. + static NTSTATUS OpenProcessAction(const ClientInfo& client_info, + uint32_t desired_access, + uint32_t process_id, + HANDLE* handle); + + // Opens the token associated with the process and returns the duplicated + // handle to the child. We only allow the child processes to open its own + // token (using ::GetCurrentProcess()). + static NTSTATUS OpenProcessTokenAction(const ClientInfo& client_info, + HANDLE process, + uint32_t desired_access, + HANDLE* handle); + + // Opens the token associated with the process and returns the duplicated + // handle to the child. We only allow the child processes to open its own + // token (using ::GetCurrentProcess()). + static NTSTATUS OpenProcessTokenExAction(const ClientInfo& client_info, + HANDLE process, + uint32_t desired_access, + uint32_t attributes, + HANDLE* handle); + + // Processes a 'CreateProcessW()' request from the target. + // 'client_info' : the target process that is making the request. + // 'eval_result' : The desired policy action to accomplish. + // 'app_name' : The full path of the process to be created. + // 'command_line' : The command line passed to the created process. + // 'current_dir' : The CWD with which to spawn the child process. + static DWORD CreateProcessWAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& app_name, + const std::wstring& command_line, + const std::wstring& current_dir, + PROCESS_INFORMATION* process_info); + + // Processes a 'CreateThread()' request from the target. + // 'client_info' : the target process that is making the request. + static DWORD CreateThreadAction(const ClientInfo& client_info, + SIZE_T stack_size, + LPTHREAD_START_ROUTINE start_address, + PVOID parameter, + DWORD creation_flags, + LPDWORD thread_id, + HANDLE* handle); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_THREAD_POLICY_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/registry_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/registry_dispatcher.cc new file mode 100644 index 0000000000..26d2bb3ffc --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/registry_dispatcher.cc @@ -0,0 +1,167 @@ +// Copyright (c) 2011 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/win/src/registry_dispatcher.h" + +#include <stdint.h> + +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/registry_interception.h" +#include "sandbox/win/src/registry_policy.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace { + +// Builds a path using the root directory and the name. +bool GetCompletePath(HANDLE root, + const std::wstring& name, + std::wstring* complete_name) { + if (root) { + if (!sandbox::GetPathFromHandle(root, complete_name)) + return false; + + *complete_name += L"\\"; + *complete_name += name; + } else { + *complete_name = name; + } + + return true; +} + +} // namespace + +namespace sandbox { + +RegistryDispatcher::RegistryDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall create_params = { + {IpcTag::NTCREATEKEY, + {WCHAR_TYPE, UINT32_TYPE, VOIDPTR_TYPE, UINT32_TYPE, UINT32_TYPE, + UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>(&RegistryDispatcher::NtCreateKey)}; + + static const IPCCall open_params = { + {IpcTag::NTOPENKEY, {WCHAR_TYPE, UINT32_TYPE, VOIDPTR_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>(&RegistryDispatcher::NtOpenKey)}; + + ipc_calls_.push_back(create_params); + ipc_calls_.push_back(open_params); +} + +bool RegistryDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + if (IpcTag::NTCREATEKEY == service) + return INTERCEPT_NT(manager, NtCreateKey, CREATE_KEY_ID, 32); + + if (IpcTag::NTOPENKEY == service) { + bool result = INTERCEPT_NT(manager, NtOpenKey, OPEN_KEY_ID, 16); + result &= INTERCEPT_NT(manager, NtOpenKeyEx, OPEN_KEY_EX_ID, 20); + return result; + } + + return false; +} + +bool RegistryDispatcher::NtCreateKey(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + HANDLE root, + uint32_t desired_access, + uint32_t title_index, + uint32_t create_options) { + base::win::ScopedHandle root_handle; + std::wstring real_path = *name; + + // If there is a root directory, we need to duplicate the handle to make + // it valid in this process. + if (root) { + if (!::DuplicateHandle(ipc->client_info->process, root, + ::GetCurrentProcess(), &root, 0, false, + DUPLICATE_SAME_ACCESS)) + return false; + + root_handle.Set(root); + } + + if (!GetCompletePath(root, *name, &real_path)) + return false; + + const wchar_t* regname = real_path.c_str(); + CountedParameterSet<OpenKey> params; + params[OpenKey::NAME] = ParamPickerMake(regname); + params[OpenKey::ACCESS] = ParamPickerMake(desired_access); + + EvalResult result = + policy_base_->EvalPolicy(IpcTag::NTCREATEKEY, params.GetBase()); + + HANDLE handle; + NTSTATUS nt_status; + ULONG disposition = 0; + if (!RegistryPolicy::CreateKeyAction( + result, *ipc->client_info, *name, attributes, root, desired_access, + title_index, create_options, &handle, &nt_status, &disposition)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + // Return operation status on the IPC. + ipc->return_info.extended[0].unsigned_int = disposition; + ipc->return_info.nt_status = nt_status; + ipc->return_info.handle = handle; + return true; +} + +bool RegistryDispatcher::NtOpenKey(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + HANDLE root, + uint32_t desired_access) { + base::win::ScopedHandle root_handle; + std::wstring real_path = *name; + + // If there is a root directory, we need to duplicate the handle to make + // it valid in this process. + if (root) { + if (!::DuplicateHandle(ipc->client_info->process, root, + ::GetCurrentProcess(), &root, 0, false, + DUPLICATE_SAME_ACCESS)) + return false; + root_handle.Set(root); + } + + if (!GetCompletePath(root, *name, &real_path)) + return false; + + const wchar_t* regname = real_path.c_str(); + CountedParameterSet<OpenKey> params; + params[OpenKey::NAME] = ParamPickerMake(regname); + params[OpenKey::ACCESS] = ParamPickerMake(desired_access); + + EvalResult result = + policy_base_->EvalPolicy(IpcTag::NTOPENKEY, params.GetBase()); + HANDLE handle; + NTSTATUS nt_status; + if (!RegistryPolicy::OpenKeyAction(result, *ipc->client_info, *name, + attributes, root, desired_access, &handle, + &nt_status)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + // Return operation status on the IPC. + ipc->return_info.nt_status = nt_status; + ipc->return_info.handle = handle; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/registry_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/registry_dispatcher.h new file mode 100644 index 0000000000..c23b95098b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/registry_dispatcher.h @@ -0,0 +1,51 @@ +// Copyright (c) 2010 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. + +#ifndef SANDBOX_SRC_REGISTRY_DISPATCHER_H_ +#define SANDBOX_SRC_REGISTRY_DISPATCHER_H_ + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles registry-related IPC calls. +class RegistryDispatcher : public Dispatcher { + public: + explicit RegistryDispatcher(PolicyBase* policy_base); + ~RegistryDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Processes IPC requests coming from calls to NtCreateKey in the target. + bool NtCreateKey(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + HANDLE root, + uint32_t desired_access, + uint32_t title_index, + uint32_t create_options); + + // Processes IPC requests coming from calls to NtOpenKey in the target. + bool NtOpenKey(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + HANDLE root, + uint32_t desired_access); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(RegistryDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_REGISTRY_DISPATCHER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/registry_interception.cc b/security/sandbox/chromium/sandbox/win/src/registry_interception.cc new file mode 100644 index 0000000000..d8bb82949b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/registry_interception.cc @@ -0,0 +1,261 @@ +// Copyright (c) 2006-2008 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/win/src/registry_interception.h" + +#include <stdint.h> + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" +#include "mozilla/sandboxing/sandboxLogging.h" + +namespace sandbox { + +NTSTATUS WINAPI TargetNtCreateKey(NtCreateKeyFunction orig_CreateKey, + PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + ULONG title_index, + PUNICODE_STRING class_name, + ULONG create_options, + PULONG disposition) { + // Check if the process can create it first. + NTSTATUS status = + orig_CreateKey(key, desired_access, object_attributes, title_index, + class_name, create_options, disposition); + if (NT_SUCCESS(status)) + return status; + + if (STATUS_OBJECT_NAME_NOT_FOUND != status) { + mozilla::sandboxing::LogBlocked("NtCreateKey", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(key, sizeof(HANDLE), WRITE)) + break; + + if (disposition && !ValidParameter(disposition, sizeof(ULONG), WRITE)) + break; + + // At this point we don't support class_name. + if (class_name && class_name->Buffer && class_name->Length) + break; + + // We don't support creating link keys, volatile keys and backup/restore. + if (create_options) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes = 0; + HANDLE root_directory = 0; + NTSTATUS ret = AllocAndCopyName(object_attributes, &name, &attributes, + &root_directory); + if (!NT_SUCCESS(ret) || !name) + break; + + uint32_t desired_access_uint32 = desired_access; + CountedParameterSet<OpenKey> params; + params[OpenKey::ACCESS] = ParamPickerMake(desired_access_uint32); + + bool query_broker = false; + { + std::unique_ptr<wchar_t, NtAllocDeleter> full_name; + const wchar_t* name_ptr = name.get(); + const wchar_t* full_name_ptr = nullptr; + + if (root_directory) { + ret = sandbox::AllocAndGetFullPath(root_directory, name.get(), + &full_name); + if (!NT_SUCCESS(ret) || !full_name) + break; + full_name_ptr = full_name.get(); + params[OpenKey::NAME] = ParamPickerMake(full_name_ptr); + } else { + params[OpenKey::NAME] = ParamPickerMake(name_ptr); + } + + query_broker = QueryBroker(IpcTag::NTCREATEKEY, params.GetBase()); + } + + if (!query_broker) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + + ResultCode code = CrossCall(ipc, IpcTag::NTCREATEKEY, name.get(), + attributes, root_directory, desired_access, + title_index, create_options, &answer); + + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + // TODO(nsylvain): We should return answer.nt_status here instead + // of status. We can do this only after we checked the policy. + // otherwise we will returns ACCESS_DENIED for all paths + // that are not specified by a policy, even though your token allows + // access to that path, and the original call had a more meaningful + // error. Bug 4369 + break; + + __try { + *key = answer.handle; + + if (disposition) + *disposition = answer.extended[0].unsigned_int; + + status = answer.nt_status; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + mozilla::sandboxing::LogAllowed("NtCreateKey", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +NTSTATUS WINAPI CommonNtOpenKey(NTSTATUS status, + PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes) { + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(key, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes; + HANDLE root_directory; + NTSTATUS ret = AllocAndCopyName(object_attributes, &name, &attributes, + &root_directory); + if (!NT_SUCCESS(ret) || !name) + break; + + uint32_t desired_access_uint32 = desired_access; + CountedParameterSet<OpenKey> params; + params[OpenKey::ACCESS] = ParamPickerMake(desired_access_uint32); + + bool query_broker = false; + { + std::unique_ptr<wchar_t, NtAllocDeleter> full_name; + const wchar_t* name_ptr = name.get(); + const wchar_t* full_name_ptr = nullptr; + + if (root_directory) { + ret = sandbox::AllocAndGetFullPath(root_directory, name.get(), + &full_name); + if (!NT_SUCCESS(ret) || !full_name) + break; + full_name_ptr = full_name.get(); + params[OpenKey::NAME] = ParamPickerMake(full_name_ptr); + } else { + params[OpenKey::NAME] = ParamPickerMake(name_ptr); + } + + query_broker = QueryBroker(IpcTag::NTOPENKEY, params.GetBase()); + } + + if (!query_broker) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::NTOPENKEY, name.get(), attributes, + root_directory, desired_access, &answer); + + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + // TODO(nsylvain): We should return answer.nt_status here instead + // of status. We can do this only after we checked the policy. + // otherwise we will returns ACCESS_DENIED for all paths + // that are not specified by a policy, even though your token allows + // access to that path, and the original call had a more meaningful + // error. Bug 4369 + break; + + __try { + *key = answer.handle; + status = answer.nt_status; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + mozilla::sandboxing::LogAllowed("NtOpenKey[Ex]", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +NTSTATUS WINAPI TargetNtOpenKey(NtOpenKeyFunction orig_OpenKey, + PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes) { + // Check if the process can open it first. + NTSTATUS status = orig_OpenKey(key, desired_access, object_attributes); + if (NT_SUCCESS(status)) + return status; + + if (STATUS_OBJECT_NAME_NOT_FOUND != status) { + mozilla::sandboxing::LogBlocked("NtOpenKey", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } + + return CommonNtOpenKey(status, key, desired_access, object_attributes); +} + +NTSTATUS WINAPI TargetNtOpenKeyEx(NtOpenKeyExFunction orig_OpenKeyEx, + PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + ULONG open_options) { + // Check if the process can open it first. + NTSTATUS status = + orig_OpenKeyEx(key, desired_access, object_attributes, open_options); + + // We do not support open_options at this time. The 2 current known values + // are REG_OPTION_CREATE_LINK, to open a symbolic link, and + // REG_OPTION_BACKUP_RESTORE to open the key with special privileges. + if (NT_SUCCESS(status) || open_options != 0) + return status; + + if (STATUS_OBJECT_NAME_NOT_FOUND != status) { + mozilla::sandboxing::LogBlocked("NtOpenKeyEx", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } + + return CommonNtOpenKey(status, key, desired_access, object_attributes); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/registry_interception.h b/security/sandbox/chromium/sandbox/win/src/registry_interception.h new file mode 100644 index 0000000000..f47e56ce39 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/registry_interception.h @@ -0,0 +1,38 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_WIN_SRC_REGISTRY_INTERCEPTION_H_ +#define SANDBOX_WIN_SRC_REGISTRY_INTERCEPTION_H_ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +// Interception of NtCreateKey on the child process. +// It should never be called directly +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtCreateKey( + NtCreateKeyFunction orig_CreateKey, PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, ULONG title_index, + PUNICODE_STRING class_name, ULONG create_options, PULONG disposition); + +// Interception of NtOpenKey on the child process. +// It should never be called directly +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenKey( + NtOpenKeyFunction orig_OpenKey, PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes); + +// Interception of NtOpenKeyEx on the child process. +// It should never be called directly +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenKeyEx( + NtOpenKeyExFunction orig_OpenKeyEx, PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, ULONG open_options); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_REGISTRY_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/registry_policy.cc b/security/sandbox/chromium/sandbox/win/src/registry_policy.cc new file mode 100644 index 0000000000..212ff713c4 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/registry_policy.cc @@ -0,0 +1,230 @@ +// Copyright (c) 2006-2008 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/win/src/registry_policy.h" + +#include <stdint.h> + +#include <string> + +#include "base/logging.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +static const uint32_t kAllowedRegFlags = + KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | KEY_READ | + GENERIC_READ | GENERIC_EXECUTE | READ_CONTROL | KEY_WOW64_64KEY | + KEY_WOW64_32KEY; + +// Opens the key referenced by |obj_attributes| with |access| and +// checks what permission was given. Remove the WRITE flags and update +// |access| with the new value. +NTSTATUS TranslateMaximumAllowed(OBJECT_ATTRIBUTES* obj_attributes, + DWORD* access) { + NtOpenKeyFunction NtOpenKey = nullptr; + ResolveNTFunctionPtr("NtOpenKey", &NtOpenKey); + + NtCloseFunction NtClose = nullptr; + ResolveNTFunctionPtr("NtClose", &NtClose); + + NtQueryObjectFunction NtQueryObject = nullptr; + ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject); + + // Open the key. + HANDLE handle; + NTSTATUS status = NtOpenKey(&handle, *access, obj_attributes); + if (!NT_SUCCESS(status)) + return status; + + OBJECT_BASIC_INFORMATION info = {0}; + status = NtQueryObject(handle, ObjectBasicInformation, &info, sizeof(info), + nullptr); + CHECK(NT_SUCCESS(NtClose(handle))); + if (!NT_SUCCESS(status)) + return status; + + *access = info.GrantedAccess & kAllowedRegFlags; + return STATUS_SUCCESS; +} + +NTSTATUS NtCreateKeyInTarget(HANDLE* target_key_handle, + ACCESS_MASK desired_access, + OBJECT_ATTRIBUTES* obj_attributes, + ULONG title_index, + UNICODE_STRING* class_name, + ULONG create_options, + ULONG* disposition, + HANDLE target_process) { + *target_key_handle = nullptr; + NtCreateKeyFunction NtCreateKey = nullptr; + ResolveNTFunctionPtr("NtCreateKey", &NtCreateKey); + + if (MAXIMUM_ALLOWED & desired_access) { + NTSTATUS status = TranslateMaximumAllowed(obj_attributes, &desired_access); + if (!NT_SUCCESS(status)) + return STATUS_ACCESS_DENIED; + } + + HANDLE local_handle = INVALID_HANDLE_VALUE; + NTSTATUS status = + NtCreateKey(&local_handle, desired_access, obj_attributes, title_index, + class_name, create_options, disposition); + if (!NT_SUCCESS(status)) + return status; + + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, target_process, + target_key_handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return STATUS_SUCCESS; +} + +NTSTATUS NtOpenKeyInTarget(HANDLE* target_key_handle, + ACCESS_MASK desired_access, + OBJECT_ATTRIBUTES* obj_attributes, + HANDLE target_process) { + *target_key_handle = nullptr; + NtOpenKeyFunction NtOpenKey = nullptr; + ResolveNTFunctionPtr("NtOpenKey", &NtOpenKey); + + if (MAXIMUM_ALLOWED & desired_access) { + NTSTATUS status = TranslateMaximumAllowed(obj_attributes, &desired_access); + if (!NT_SUCCESS(status)) + return STATUS_ACCESS_DENIED; + } + + HANDLE local_handle = INVALID_HANDLE_VALUE; + NTSTATUS status = NtOpenKey(&local_handle, desired_access, obj_attributes); + + if (!NT_SUCCESS(status)) + return status; + + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, target_process, + target_key_handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return STATUS_SUCCESS; +} + +} // namespace + +namespace sandbox { + +bool RegistryPolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + std::wstring resolved_name(name); + if (resolved_name.empty()) { + return false; + } + + if (!ResolveRegistryName(resolved_name, &resolved_name)) + return false; + + name = resolved_name.c_str(); + + EvalResult result = ASK_BROKER; + + PolicyRule open(result); + PolicyRule create(result); + + switch (semantics) { + case TargetPolicy::REG_ALLOW_READONLY: { + // We consider all flags that are not known to be readonly as potentially + // used for write. Here we also support MAXIMUM_ALLOWED, but we are going + // to expand it to read-only before the call. + uint32_t restricted_flags = ~(kAllowedRegFlags | MAXIMUM_ALLOWED); + open.AddNumberMatch(IF_NOT, OpenKey::ACCESS, restricted_flags, AND); + create.AddNumberMatch(IF_NOT, OpenKey::ACCESS, restricted_flags, AND); + break; + } + case TargetPolicy::REG_ALLOW_ANY: { + break; + } + default: { + NOTREACHED(); + return false; + } + } + + if (!create.AddStringMatch(IF, OpenKey::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IpcTag::NTCREATEKEY, &create)) { + return false; + } + + if (!open.AddStringMatch(IF, OpenKey::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IpcTag::NTOPENKEY, &open)) { + return false; + } + + return true; +} + +bool RegistryPolicy::CreateKeyAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& key, + uint32_t attributes, + HANDLE root_directory, + uint32_t desired_access, + uint32_t title_index, + uint32_t create_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG* disposition) { + // The only action supported is ASK_BROKER which means create the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + // We don't support creating link keys, volatile keys or backup/restore. + if (create_options) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + UNICODE_STRING uni_name = {0}; + OBJECT_ATTRIBUTES obj_attributes = {0}; + InitObjectAttribs(key, attributes, root_directory, &obj_attributes, &uni_name, + nullptr); + *nt_status = NtCreateKeyInTarget(handle, desired_access, &obj_attributes, + title_index, nullptr, create_options, + disposition, client_info.process); + return true; +} + +bool RegistryPolicy::OpenKeyAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& key, + uint32_t attributes, + HANDLE root_directory, + uint32_t desired_access, + HANDLE* handle, + NTSTATUS* nt_status) { + // The only action supported is ASK_BROKER which means open the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + UNICODE_STRING uni_name = {0}; + OBJECT_ATTRIBUTES obj_attributes = {0}; + InitObjectAttribs(key, attributes, root_directory, &obj_attributes, &uni_name, + nullptr); + *nt_status = NtOpenKeyInTarget(handle, desired_access, &obj_attributes, + client_info.process); + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/registry_policy.h b/security/sandbox/chromium/sandbox/win/src/registry_policy.h new file mode 100644 index 0000000000..9a36932869 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/registry_policy.h @@ -0,0 +1,56 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_SRC_REGISTRY_POLICY_H__ +#define SANDBOX_SRC_REGISTRY_POLICY_H__ + +#include <stdint.h> + +#include <string> + +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +// This class centralizes most of the knowledge related to registry policy +class RegistryPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for registry IO, in particular open or create actions. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Performs the desired policy action on a create request with an + // API that is compatible with the IPC-received parameters. + static bool CreateKeyAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& key, + uint32_t attributes, + HANDLE root_directory, + uint32_t desired_access, + uint32_t title_index, + uint32_t create_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG* disposition); + + // Performs the desired policy action on an open request with an + // API that is compatible with the IPC-received parameters. + static bool OpenKeyAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& key, + uint32_t attributes, + HANDLE root_directory, + uint32_t desired_access, + HANDLE* handle, + NTSTATUS* nt_status); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_REGISTRY_POLICY_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/registry_policy_test.cc b/security/sandbox/chromium/sandbox/win/src/registry_policy_test.cc new file mode 100644 index 0000000000..83ce586877 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/registry_policy_test.cc @@ -0,0 +1,322 @@ +// Copyright (c) 2006-2010 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 <shlobj.h> + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/registry_policy.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/win_utils.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +static const DWORD kAllowedRegFlags = KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | + KEY_NOTIFY | KEY_READ | GENERIC_READ | + GENERIC_EXECUTE | READ_CONTROL; + +#define BINDNTDLL(name) \ + name##Function name = reinterpret_cast<name##Function>( \ + ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), #name)) + +bool IsKeyOpenForRead(HKEY handle) { + BINDNTDLL(NtQueryObject); + + OBJECT_BASIC_INFORMATION info = {0}; + NTSTATUS status = NtQueryObject(handle, ObjectBasicInformation, &info, + sizeof(info), nullptr); + + if (!NT_SUCCESS(status)) + return false; + + if ((info.GrantedAccess & (~kAllowedRegFlags)) != 0) + return false; + return true; +} + +} // namespace + +namespace sandbox { + +SBOX_TESTS_COMMAND int Reg_OpenKey(int argc, wchar_t** argv) { + if (argc != 4) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + REGSAM desired_access = 0; + ULONG options = 0; + if (wcscmp(argv[1], L"read") == 0) { + desired_access = KEY_READ; + } else if (wcscmp(argv[1], L"write") == 0) { + desired_access = KEY_ALL_ACCESS; + } else if (wcscmp(argv[1], L"link") == 0) { + options = REG_OPTION_CREATE_LINK; + desired_access = KEY_ALL_ACCESS; + } else { + desired_access = MAXIMUM_ALLOWED; + } + + HKEY root = GetReservedKeyFromName(argv[2]); + HKEY key; + LRESULT result = 0; + + if (wcscmp(argv[0], L"create") == 0) + result = ::RegCreateKeyEx(root, argv[3], 0, nullptr, options, + desired_access, nullptr, &key, nullptr); + else + result = ::RegOpenKeyEx(root, argv[3], 0, desired_access, &key); + + if (ERROR_SUCCESS == result) { + if (MAXIMUM_ALLOWED == desired_access) { + if (!IsKeyOpenForRead(key)) { + ::RegCloseKey(key); + return SBOX_TEST_FAILED; + } + } + ::RegCloseKey(key); + return SBOX_TEST_SUCCEEDED; + } else if (ERROR_ACCESS_DENIED == result) { + return SBOX_TEST_DENIED; + } + + return SBOX_TEST_FAILED; +} + +TEST(RegistryPolicyTest, TestKeyAnyAccess) { + TestRunner runner; + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Microsoft")); + + // Tests read access on key allowed for read-write. + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest( + L"Reg_OpenKey create read HKEY_LOCAL_MACHINE software\\microsoft")); + + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest( + L"Reg_OpenKey open read HKEY_LOCAL_MACHINE software\\microsoft")); + + if (::IsUserAnAdmin()) { + // Tests write access on key allowed for read-write. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Reg_OpenKey create write HKEY_LOCAL_MACHINE " + L"software\\microsoft")); + + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest( + L"Reg_OpenKey open write HKEY_LOCAL_MACHINE software\\microsoft")); + } + + // Tests subdirectory access on keys where we don't have subdirectory acess. + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"Reg_OpenKey create read " + L"HKEY_LOCAL_MACHINE software\\microsoft\\Windows")); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"Reg_OpenKey open read " + L"HKEY_LOCAL_MACHINE software\\microsoft\\windows")); + + // Tests to see if we can create keys where we dont have subdirectory access. + // This is denied. + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest( + L"Reg_OpenKey create write " + L"HKEY_LOCAL_MACHINE software\\Microsoft\\google_unit_tests")); + + RegDeleteKey(HKEY_LOCAL_MACHINE, L"software\\Microsoft\\google_unit_tests"); + + // Tests if we need to handle differently the "\\" at the end. + // This is denied. We need to add both rules. + EXPECT_EQ( + SBOX_TEST_DENIED, + runner.RunTest( + L"Reg_OpenKey create read HKEY_LOCAL_MACHINE software\\microsoft\\")); + + EXPECT_EQ( + SBOX_TEST_DENIED, + runner.RunTest( + L"Reg_OpenKey open read HKEY_LOCAL_MACHINE software\\microsoft\\")); +} + +TEST(RegistryPolicyTest, TestKeyNoAccess) { + TestRunner runner; + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE")); + + // Tests read access where we don't have access at all. + EXPECT_EQ( + SBOX_TEST_DENIED, + runner.RunTest(L"Reg_OpenKey create read HKEY_LOCAL_MACHINE software")); + + EXPECT_EQ( + SBOX_TEST_DENIED, + runner.RunTest(L"Reg_OpenKey open read HKEY_LOCAL_MACHINE software")); +} + +TEST(RegistryPolicyTest, TestKeyReadOnlyAccess) { + TestRunner runner; + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies\\*")); + + // Tests subdirectory acess on keys where we have subdirectory acess. + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Reg_OpenKey create read " + L"HKEY_LOCAL_MACHINE software\\Policies\\microsoft")); + + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Reg_OpenKey open read " + L"HKEY_LOCAL_MACHINE software\\Policies\\microsoft")); + + // Tests to see if we can create keys where we have subdirectory access. + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest( + L"Reg_OpenKey create write " + L"HKEY_LOCAL_MACHINE software\\Policies\\google_unit_tests")); + + RegDeleteKey(HKEY_LOCAL_MACHINE, L"software\\Policies\\google_unit_tests"); +} + +TEST(RegistryPolicyTest, TestKeyAllAccessSubDir) { + TestRunner runner; + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies\\*")); + + if (::IsUserAnAdmin()) { + // Tests to see if we can create keys where we have subdirectory access. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest( + L"Reg_OpenKey create write " + L"HKEY_LOCAL_MACHINE software\\Policies\\google_unit_tests")); + + RegDeleteKey(HKEY_LOCAL_MACHINE, L"software\\Policies\\google_unit_tests"); + } +} + +TEST(RegistryPolicyTest, TestKeyCreateLink) { + TestRunner runner; + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies\\*")); + + // Tests to see if we can create a registry link key. + // NOTE: In theory here we should make sure to check for SBOX_TEST_DENIED + // instead of !SBOX_TEST_SUCCEEDED, but unfortunately the result is not + // access denied. Internally RegCreateKeyEx (At least on Vista 64) tries to + // create the link, and we return successfully access denied, then, it + // decides to try to break the path in multiple chunks, and create the links + // one by one. In this scenario, it tries to create "HKLM\Software" as a + // link key, which obviously fail with STATUS_OBJECT_NAME_COLLISION, and + // this is what is returned to the user. + EXPECT_NE(SBOX_TEST_SUCCEEDED, + runner.RunTest( + L"Reg_OpenKey create link " + L"HKEY_LOCAL_MACHINE software\\Policies\\google_unit_tests")); + + // In case our code fails, and the call works, we need to delete the new + // link. There is no api for this, so we need to use the NT call. + HKEY key = nullptr; + LRESULT result = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, + L"software\\Policies\\google_unit_tests", + REG_OPTION_OPEN_LINK, MAXIMUM_ALLOWED, &key); + + if (!result) { + HMODULE ntdll = GetModuleHandle(L"ntdll.dll"); + NtDeleteKeyFunction NtDeleteKey = reinterpret_cast<NtDeleteKeyFunction>( + GetProcAddress(ntdll, "NtDeleteKey")); + NtDeleteKey(key); + } +} + +TEST(RegistryPolicyTest, TestKeyReadOnlyHKCU) { + TestRunner runner; + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_CURRENT_USER")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_CURRENT_USER\\Software")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_USERS\\.default")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_USERS\\.default\\software")); + + // Tests read access where we only have read-only access. + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Reg_OpenKey create read HKEY_CURRENT_USER software")); + + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Reg_OpenKey open read HKEY_CURRENT_USER software")); + + // Tests write access where we only have read-only acess. + EXPECT_EQ( + SBOX_TEST_DENIED, + runner.RunTest(L"Reg_OpenKey create write HKEY_CURRENT_USER software")); + + EXPECT_EQ( + SBOX_TEST_DENIED, + runner.RunTest(L"Reg_OpenKey open write HKEY_CURRENT_USER software")); + + // Tests maximum allowed access where we only have read-only access. + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest( + L"Reg_OpenKey create maximum_allowed HKEY_CURRENT_USER software")); + + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest( + L"Reg_OpenKey open maximum_allowed HKEY_CURRENT_USER software")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/resolver.cc b/security/sandbox/chromium/sandbox/win/src/resolver.cc new file mode 100644 index 0000000000..6ed20e9b51 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/resolver.cc @@ -0,0 +1,63 @@ +// Copyright (c) 2006-2010 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/win/src/resolver.h" + +#include <stddef.h> + +#include "base/win/pe_image.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +NTSTATUS ResolverThunk::Init(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes) { + if (!thunk_storage || 0 == storage_bytes || !target_module || !target_name) + return STATUS_INVALID_PARAMETER; + + if (storage_bytes < GetThunkSize()) + return STATUS_BUFFER_TOO_SMALL; + + NTSTATUS ret = STATUS_SUCCESS; + if (!interceptor_entry_point) { + ret = ResolveInterceptor(interceptor_module, interceptor_name, + &interceptor_entry_point); + if (!NT_SUCCESS(ret)) + return ret; + } + + ret = ResolveTarget(target_module, target_name, &target_); + if (!NT_SUCCESS(ret)) + return ret; + + interceptor_ = interceptor_entry_point; + + return ret; +} + +NTSTATUS ResolverThunk::ResolveInterceptor(const void* interceptor_module, + const char* interceptor_name, + const void** address) { + DCHECK_NT(address); + if (!interceptor_module) + return STATUS_INVALID_PARAMETER; + + base::win::PEImage pe(interceptor_module); + if (!pe.VerifyMagic()) + return STATUS_INVALID_IMAGE_FORMAT; + + *address = reinterpret_cast<void*>(pe.GetProcAddress(interceptor_name)); + + if (!(*address)) + return STATUS_PROCEDURE_NOT_FOUND; + + return STATUS_SUCCESS; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/resolver.h b/security/sandbox/chromium/sandbox/win/src/resolver.h new file mode 100644 index 0000000000..3ce427b74b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/resolver.h @@ -0,0 +1,107 @@ +// Copyright (c) 2010 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. + +#ifndef SANDBOX_WIN_SRC_RESOLVER_H_ +#define SANDBOX_WIN_SRC_RESOLVER_H_ + +// Defines ResolverThunk, the interface for classes that perform interceptions. +// For more details see +// http://dev.chromium.org/developers/design-documents/sandbox . + +#include <stddef.h> + +#include "base/macros.h" +#include "sandbox/win/src/nt_internals.h" + +namespace sandbox { + +// A resolver is the object in charge of performing the actual interception of +// a function. There should be a concrete implementation of a resolver roughly +// per type of interception. +class ResolverThunk { + public: + ResolverThunk() {} + virtual ~ResolverThunk() {} + + // Performs the actual interception of a function. + // target_name is an exported function from the module loaded at + // target_module, and must be replaced by interceptor_name, exported from + // interceptor_module. interceptor_entry_point can be provided instead of + // interceptor_name / interceptor_module. + // thunk_storage must point to a buffer on the child's address space, to hold + // the patch thunk, and related data. If provided, storage_used will receive + // the number of bytes used from thunk_storage. + // + // Example: (without error checking) + // + // size_t size = resolver.GetThunkSize(); + // char* buffer = ::VirtualAllocEx(child_process, nullptr, size, + // MEM_COMMIT, PAGE_READWRITE); + // resolver.Setup(ntdll_module, nullptr, L"NtCreateFile", nullptr, + // &MyReplacementFunction, buffer, size, nullptr); + // + // In general, the idea is to allocate a single big buffer for all + // interceptions on the same dll, and call Setup n times. + // WARNING: This means that any data member that is specific to a single + // interception must be reset within this method. + virtual NTSTATUS Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) = 0; + + // Gets the address of function_name inside module (main exe). + virtual NTSTATUS ResolveInterceptor(const void* module, + const char* function_name, + const void** address); + + // Gets the address of an exported function_name inside module. + virtual NTSTATUS ResolveTarget(const void* module, + const char* function_name, + void** address); + + // Gets the required buffer size for this type of thunk. + virtual size_t GetThunkSize() const = 0; + + protected: + // Performs basic initialization on behalf of a concrete instance of a + // resolver. That is, parameter validation and resolution of the target + // and the interceptor into the member variables. + // + // target_name is an exported function from the module loaded at + // target_module, and must be replaced by interceptor_name, exported from + // interceptor_module. interceptor_entry_point can be provided instead of + // interceptor_name / interceptor_module. + // thunk_storage must point to a buffer on the child's address space, to hold + // the patch thunk, and related data. + virtual NTSTATUS Init(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes); + + // Gets the required buffer size for the internal part of the thunk. + size_t GetInternalThunkSize() const; + + // Initializes the internal part of the thunk. + // interceptor is the function to be called instead of original_function. + bool SetInternalThunk(void* storage, size_t storage_bytes, + const void* original_function, const void* interceptor); + + // Holds the resolved interception target. + void* target_; + // Holds the resolved interception interceptor. + const void* interceptor_; + + DISALLOW_COPY_AND_ASSIGN(ResolverThunk); +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_RESOLVER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/resolver_32.cc b/security/sandbox/chromium/sandbox/win/src/resolver_32.cc new file mode 100644 index 0000000000..ff78f53fb1 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/resolver_32.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2006-2010 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/win/src/resolver.h" + +#include <stddef.h> + +// For placement new. This file must not depend on the CRT at runtime, but +// placement operator new is inline. +#include <new> + +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace { + +#pragma pack(push, 1) +struct InternalThunk { + // This struct contains roughly the following code: + // sub esp, 8 // Create working space + // push edx // Save register + // mov edx, [esp + 0xc] // Get return adddress + // mov [esp + 8], edx // Store return address + // mov dword ptr [esp + 0xc], 0x7c401200 // Store extra argument + // mov dword ptr [esp + 4], 0x40010203 // Store address to jump to + // pop edx // Restore register + // ret // Jump to interceptor + // + // This code only modifies esp and eip so it must work with to normal calling + // convention. It is assembled as: + // + // 00 83ec08 sub esp,8 + // 03 52 push edx + // 04 8b54240c mov edx,dword ptr [esp + 0Ch] + // 08 89542408 mov dword ptr [esp + 8], edx + // 0c c744240c0012407c mov dword ptr [esp + 0Ch], 7C401200h + // 14 c744240403020140 mov dword ptr [esp + 4], 40010203h + // 1c 5a pop edx + // 1d c3 ret + InternalThunk() { + opcodes_1 = 0x5208ec83; + opcodes_2 = 0x0c24548b; + opcodes_3 = 0x08245489; + opcodes_4 = 0x0c2444c7; + opcodes_5 = 0x042444c7; + opcodes_6 = 0xc35a; + extra_argument = 0; + interceptor_function = 0; + } + ULONG opcodes_1; // = 0x5208ec83 + ULONG opcodes_2; // = 0x0c24548b + ULONG opcodes_3; // = 0x08245489 + ULONG opcodes_4; // = 0x0c2444c7 + ULONG extra_argument; + ULONG opcodes_5; // = 0x042444c7 + ULONG interceptor_function; + USHORT opcodes_6; // = 0xc35a +}; +#pragma pack(pop) + +} // namespace + +namespace sandbox { + +bool ResolverThunk::SetInternalThunk(void* storage, + size_t storage_bytes, + const void* original_function, + const void* interceptor) { + if (storage_bytes < sizeof(InternalThunk)) + return false; + + InternalThunk* thunk = new (storage) InternalThunk; + +#pragma warning(push) +#pragma warning(disable : 4311) + // These casts generate warnings because they are 32 bit specific. + thunk->interceptor_function = reinterpret_cast<ULONG>(interceptor); + thunk->extra_argument = reinterpret_cast<ULONG>(original_function); +#pragma warning(pop) + + return true; +} + +size_t ResolverThunk::GetInternalThunkSize() const { + return sizeof(InternalThunk); +} + +NTSTATUS ResolverThunk::ResolveTarget(const void* module, + const char* function_name, + void** address) { + const void** casted = const_cast<const void**>(address); + return ResolverThunk::ResolveInterceptor(module, function_name, casted); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/resolver_64.cc b/security/sandbox/chromium/sandbox/win/src/resolver_64.cc new file mode 100644 index 0000000000..573b2bc3a9 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/resolver_64.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2006-2010 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/win/src/resolver.h" + +#include <stddef.h> + +// For placement new. This file must not depend on the CRT at runtime, but +// placement operator new is inline. +#include <new> + +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace { + +#if defined(_M_X64) + +const USHORT kMovRax = 0xB848; +const USHORT kJmpRax = 0xe0ff; + +#pragma pack(push, 1) +struct InternalThunk { + // This struct contains roughly the following code: + // 01 48b8f0debc9a78563412 mov rax,123456789ABCDEF0h + // ff e0 jmp rax + // + // The code modifies rax, but that's fine for x64 ABI. + + InternalThunk() { + mov_rax = kMovRax; + jmp_rax = kJmpRax; + interceptor_function = 0; + } + USHORT mov_rax; // = 48 B8 + ULONG_PTR interceptor_function; + USHORT jmp_rax; // = ff e0 +}; +#pragma pack(pop) + +#elif defined(_M_ARM64) + +const ULONG kLdrX16Pc4 = 0x58000050; +const ULONG kBrX16 = 0xD61F0200; + +#pragma pack(push, 4) +struct InternalThunk { + // This struct contains roughly the following code: + // 00 58000050 ldr x16, pc+4 + // 04 D61F0200 br x16 + // 08 123456789ABCDEF0H + + InternalThunk() { + ldr_x16_pc4 = kLdrX16Pc4; + br_x16 = kBrX16; + interceptor_function = 0; + } + ULONG ldr_x16_pc4; + ULONG br_x16; + ULONG_PTR interceptor_function; +}; +#pragma pack(pop) +#else +#error "Unsupported Windows 64-bit Arch" +#endif + +} // namespace. + +namespace sandbox { + +size_t ResolverThunk::GetInternalThunkSize() const { + return sizeof(InternalThunk); +} + +bool ResolverThunk::SetInternalThunk(void* storage, + size_t storage_bytes, + const void* original_function, + const void* interceptor) { + if (storage_bytes < sizeof(InternalThunk)) + return false; + + InternalThunk* thunk = new (storage) InternalThunk; + thunk->interceptor_function = reinterpret_cast<ULONG_PTR>(interceptor); + + return true; +} + +NTSTATUS ResolverThunk::ResolveTarget(const void* module, + const char* function_name, + void** address) { + // We don't support sidestep & co. + return STATUS_NOT_IMPLEMENTED; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/restricted_token.cc b/security/sandbox/chromium/sandbox/win/src/restricted_token.cc new file mode 100644 index 0000000000..5b20e88264 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/restricted_token.cc @@ -0,0 +1,432 @@ +// 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 "sandbox/win/src/restricted_token.h" + +#include <stddef.h> + +#include <memory> +#include <vector> + +#include "base/logging.h" +#include "sandbox/win/src/acl.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +// Wrapper for utility version to unwrap ScopedHandle. +std::unique_ptr<BYTE[]> GetTokenInfo(const base::win::ScopedHandle& token, + TOKEN_INFORMATION_CLASS info_class, + DWORD* error) { + std::unique_ptr<BYTE[]> buffer; + *error = sandbox::GetTokenInformation(token.Get(), info_class, &buffer); + if (*error != ERROR_SUCCESS) + return nullptr; + return buffer; +} + +} // namespace + +namespace sandbox { + +RestrictedToken::RestrictedToken() + : integrity_level_(INTEGRITY_LEVEL_LAST), + init_(false), + lockdown_default_dacl_(false) {} + +RestrictedToken::~RestrictedToken() {} + +DWORD RestrictedToken::Init(const HANDLE effective_token) { + if (init_) + return ERROR_ALREADY_INITIALIZED; + + HANDLE temp_token; + if (effective_token) { + // We duplicate the handle to be able to use it even if the original handle + // is closed. + if (!::DuplicateHandle(::GetCurrentProcess(), effective_token, + ::GetCurrentProcess(), &temp_token, 0, false, + DUPLICATE_SAME_ACCESS)) { + return ::GetLastError(); + } + } else { + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &temp_token)) { + return ::GetLastError(); + } + } + effective_token_.Set(temp_token); + + init_ = true; + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::GetRestrictedToken( + base::win::ScopedHandle* token) const { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + size_t deny_size = sids_for_deny_only_.size(); + size_t restrict_size = sids_to_restrict_.size(); + size_t privileges_size = privileges_to_disable_.size(); + + SID_AND_ATTRIBUTES* deny_only_array = nullptr; + if (deny_size) { + deny_only_array = new SID_AND_ATTRIBUTES[deny_size]; + + for (unsigned int i = 0; i < sids_for_deny_only_.size(); ++i) { + deny_only_array[i].Attributes = SE_GROUP_USE_FOR_DENY_ONLY; + deny_only_array[i].Sid = sids_for_deny_only_[i].GetPSID(); + } + } + + SID_AND_ATTRIBUTES* sids_to_restrict_array = nullptr; + if (restrict_size) { + sids_to_restrict_array = new SID_AND_ATTRIBUTES[restrict_size]; + + for (unsigned int i = 0; i < restrict_size; ++i) { + sids_to_restrict_array[i].Attributes = 0; + sids_to_restrict_array[i].Sid = sids_to_restrict_[i].GetPSID(); + } + } + + LUID_AND_ATTRIBUTES* privileges_to_disable_array = nullptr; + if (privileges_size) { + privileges_to_disable_array = new LUID_AND_ATTRIBUTES[privileges_size]; + + for (unsigned int i = 0; i < privileges_size; ++i) { + privileges_to_disable_array[i].Attributes = 0; + privileges_to_disable_array[i].Luid = privileges_to_disable_[i]; + } + } + + bool result = true; + HANDLE new_token_handle = nullptr; + if (deny_size || restrict_size || privileges_size) { + result = ::CreateRestrictedToken( + effective_token_.Get(), 0, static_cast<DWORD>(deny_size), + deny_only_array, static_cast<DWORD>(privileges_size), + privileges_to_disable_array, static_cast<DWORD>(restrict_size), + sids_to_restrict_array, &new_token_handle); + } else { + // Duplicate the token even if it's not modified at this point + // because any subsequent changes to this token would also affect the + // current process. + result = ::DuplicateTokenEx(effective_token_.Get(), TOKEN_ALL_ACCESS, + nullptr, SecurityIdentification, TokenPrimary, + &new_token_handle); + } + auto last_error = ::GetLastError(); + + if (deny_only_array) + delete[] deny_only_array; + + if (sids_to_restrict_array) + delete[] sids_to_restrict_array; + + if (privileges_to_disable_array) + delete[] privileges_to_disable_array; + + if (!result) + return last_error; + + base::win::ScopedHandle new_token(new_token_handle); + + if (lockdown_default_dacl_) { + // Don't add Restricted sid and also remove logon sid access. + if (!RevokeLogonSidFromDefaultDacl(new_token.Get())) + return ::GetLastError(); + } else { + // Modify the default dacl on the token to contain Restricted. + if (!AddSidToDefaultDacl(new_token.Get(), WinRestrictedCodeSid, + GRANT_ACCESS, GENERIC_ALL)) { + return ::GetLastError(); + } + } + + for (const auto& default_dacl_sid : sids_for_default_dacl_) { + if (!AddSidToDefaultDacl(new_token.Get(), std::get<0>(default_dacl_sid), + std::get<1>(default_dacl_sid), + std::get<2>(default_dacl_sid))) { + return ::GetLastError(); + } + } + + // Add user to default dacl. + if (!AddUserSidToDefaultDacl(new_token.Get(), GENERIC_ALL)) + return ::GetLastError(); + + DWORD error = SetTokenIntegrityLevel(new_token.Get(), integrity_level_); + if (ERROR_SUCCESS != error) + return error; + + HANDLE token_handle; + if (!::DuplicateHandle(::GetCurrentProcess(), new_token.Get(), + ::GetCurrentProcess(), &token_handle, TOKEN_ALL_ACCESS, + false, // Don't inherit. + 0)) { + return ::GetLastError(); + } + + token->Set(token_handle); + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::GetRestrictedTokenForImpersonation( + base::win::ScopedHandle* token) const { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + base::win::ScopedHandle restricted_token; + DWORD err_code = GetRestrictedToken(&restricted_token); + if (ERROR_SUCCESS != err_code) + return err_code; + + HANDLE impersonation_token_handle; + if (!::DuplicateToken(restricted_token.Get(), SecurityImpersonation, + &impersonation_token_handle)) { + return ::GetLastError(); + } + base::win::ScopedHandle impersonation_token(impersonation_token_handle); + + HANDLE token_handle; + if (!::DuplicateHandle(::GetCurrentProcess(), impersonation_token.Get(), + ::GetCurrentProcess(), &token_handle, TOKEN_ALL_ACCESS, + false, // Don't inherit. + 0)) { + return ::GetLastError(); + } + + token->Set(token_handle); + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::AddAllSidsForDenyOnly(std::vector<Sid>* exceptions) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + DWORD error; + std::unique_ptr<BYTE[]> buffer = + GetTokenInfo(effective_token_, TokenGroups, &error); + + if (!buffer) + return error; + + TOKEN_GROUPS* token_groups = reinterpret_cast<TOKEN_GROUPS*>(buffer.get()); + + // Build the list of the deny only group SIDs + for (unsigned int i = 0; i < token_groups->GroupCount; ++i) { + if ((token_groups->Groups[i].Attributes & SE_GROUP_INTEGRITY) == 0 && + (token_groups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == 0) { + bool should_ignore = false; + if (exceptions) { + for (unsigned int j = 0; j < exceptions->size(); ++j) { + if (::EqualSid((*exceptions)[j].GetPSID(), + token_groups->Groups[i].Sid)) { + should_ignore = true; + break; + } + } + } + if (!should_ignore) { + sids_for_deny_only_.push_back( + reinterpret_cast<SID*>(token_groups->Groups[i].Sid)); + } + } + } + + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::AddSidForDenyOnly(const Sid& sid) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + sids_for_deny_only_.push_back(sid); + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::AddUserSidForDenyOnly() { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; + std::unique_ptr<BYTE[]> buffer(new BYTE[size]); + TOKEN_USER* token_user = reinterpret_cast<TOKEN_USER*>(buffer.get()); + + bool result = ::GetTokenInformation(effective_token_.Get(), TokenUser, + token_user, size, &size); + + if (!result) + return ::GetLastError(); + + Sid user = reinterpret_cast<SID*>(token_user->User.Sid); + sids_for_deny_only_.push_back(user); + + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::DeleteAllPrivileges( + const std::vector<std::wstring>* exceptions) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + DWORD error; + std::unique_ptr<BYTE[]> buffer = + GetTokenInfo(effective_token_, TokenPrivileges, &error); + + if (!buffer) + return error; + + TOKEN_PRIVILEGES* token_privileges = + reinterpret_cast<TOKEN_PRIVILEGES*>(buffer.get()); + + // Build the list of privileges to disable + for (unsigned int i = 0; i < token_privileges->PrivilegeCount; ++i) { + bool should_ignore = false; + if (exceptions) { + for (unsigned int j = 0; j < exceptions->size(); ++j) { + LUID luid = {0}; + ::LookupPrivilegeValue(nullptr, (*exceptions)[j].c_str(), &luid); + if (token_privileges->Privileges[i].Luid.HighPart == luid.HighPart && + token_privileges->Privileges[i].Luid.LowPart == luid.LowPart) { + should_ignore = true; + break; + } + } + } + if (!should_ignore) { + privileges_to_disable_.push_back(token_privileges->Privileges[i].Luid); + } + } + + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::DeletePrivilege(const wchar_t* privilege) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + LUID luid = {0}; + if (LookupPrivilegeValue(nullptr, privilege, &luid)) + privileges_to_disable_.push_back(luid); + else + return ::GetLastError(); + + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::AddRestrictingSid(const Sid& sid) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + sids_to_restrict_.push_back(sid); // No attributes + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::AddRestrictingSidLogonSession() { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + DWORD error; + std::unique_ptr<BYTE[]> buffer = + GetTokenInfo(effective_token_, TokenGroups, &error); + + if (!buffer) + return error; + + TOKEN_GROUPS* token_groups = reinterpret_cast<TOKEN_GROUPS*>(buffer.get()); + + SID* logon_sid = nullptr; + for (unsigned int i = 0; i < token_groups->GroupCount; ++i) { + if ((token_groups->Groups[i].Attributes & SE_GROUP_LOGON_ID) != 0) { + logon_sid = static_cast<SID*>(token_groups->Groups[i].Sid); + break; + } + } + + if (logon_sid) + sids_to_restrict_.push_back(logon_sid); + + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::AddRestrictingSidCurrentUser() { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; + std::unique_ptr<BYTE[]> buffer(new BYTE[size]); + TOKEN_USER* token_user = reinterpret_cast<TOKEN_USER*>(buffer.get()); + + bool result = ::GetTokenInformation(effective_token_.Get(), TokenUser, + token_user, size, &size); + + if (!result) + return ::GetLastError(); + + Sid user = reinterpret_cast<SID*>(token_user->User.Sid); + sids_to_restrict_.push_back(user); + + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::AddRestrictingSidAllSids() { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + // Add the current user to the list. + DWORD error = AddRestrictingSidCurrentUser(); + if (ERROR_SUCCESS != error) + return error; + + std::unique_ptr<BYTE[]> buffer = + GetTokenInfo(effective_token_, TokenGroups, &error); + + if (!buffer) + return error; + + TOKEN_GROUPS* token_groups = reinterpret_cast<TOKEN_GROUPS*>(buffer.get()); + + // Build the list of restricting sids from all groups. + for (unsigned int i = 0; i < token_groups->GroupCount; ++i) { + if ((token_groups->Groups[i].Attributes & SE_GROUP_INTEGRITY) == 0) + AddRestrictingSid(reinterpret_cast<SID*>(token_groups->Groups[i].Sid)); + } + + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::SetIntegrityLevel(IntegrityLevel integrity_level) { + integrity_level_ = integrity_level; + return ERROR_SUCCESS; +} + +void RestrictedToken::SetLockdownDefaultDacl() { + lockdown_default_dacl_ = true; +} + +DWORD RestrictedToken::AddDefaultDaclSid(const Sid& sid, + ACCESS_MODE access_mode, + ACCESS_MASK access) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + sids_for_default_dacl_.push_back(std::make_tuple(sid, access_mode, access)); + return ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/restricted_token.h b/security/sandbox/chromium/sandbox/win/src/restricted_token.h new file mode 100644 index 0000000000..c89427af14 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/restricted_token.h @@ -0,0 +1,207 @@ +// Copyright (c) 2010 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. + +#ifndef SANDBOX_SRC_RESTRICTED_TOKEN_H_ +#define SANDBOX_SRC_RESTRICTED_TOKEN_H_ + +#include <windows.h> + +#include <tuple> +#include <vector> + +#include <string> + +#include "base/macros.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/security_level.h" +#include "sandbox/win/src/sid.h" + +// Flags present in the Group SID list. These 2 flags are new in Windows Vista +#ifndef SE_GROUP_INTEGRITY +#define SE_GROUP_INTEGRITY (0x00000020L) +#endif +#ifndef SE_GROUP_INTEGRITY_ENABLED +#define SE_GROUP_INTEGRITY_ENABLED (0x00000040L) +#endif + +namespace sandbox { + +// Handles the creation of a restricted token using the effective token or +// any token handle. +// Sample usage: +// RestrictedToken restricted_token; +// DWORD err_code = restricted_token.Init(nullptr); // Use the current +// // effective token +// if (ERROR_SUCCESS != err_code) { +// // handle error. +// } +// +// restricted_token.AddRestrictingSid(ATL::Sids::Users().GetPSID()); +// base::win::ScopedHandle token_handle; +// err_code = restricted_token.GetRestrictedToken(&token_handle); +// if (ERROR_SUCCESS != err_code) { +// // handle error. +// } +// [...] +class RestrictedToken { + public: + // Init() has to be called before calling any other method in the class. + RestrictedToken(); + ~RestrictedToken(); + + // Initializes the RestrictedToken object with effective_token. + // If effective_token is nullptr, it initializes the RestrictedToken object + // with the effective token of the current process. + DWORD Init(HANDLE effective_token); + + // Creates a restricted token. + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD GetRestrictedToken(base::win::ScopedHandle* token) const; + + // Creates a restricted token and uses this new token to create a new token + // for impersonation. Returns this impersonation token. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + // + // The sample usage is the same as the GetRestrictedToken function. + DWORD GetRestrictedTokenForImpersonation( + base::win::ScopedHandle* token) const; + + // Lists all sids in the token and mark them as Deny Only except for those + // present in the exceptions parameter. If there is no exception needed, + // the caller can pass an empty list or nullptr for the exceptions + // parameter. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + // + // Sample usage: + // std::vector<Sid> sid_exceptions; + // sid_exceptions.push_back(ATL::Sids::Users().GetPSID()); + // sid_exceptions.push_back(ATL::Sids::World().GetPSID()); + // restricted_token.AddAllSidsForDenyOnly(&sid_exceptions); + // Note: A Sid marked for Deny Only in a token cannot be used to grant + // access to any resource. It can only be used to deny access. + DWORD AddAllSidsForDenyOnly(std::vector<Sid>* exceptions); + + // Adds a user or group SID for Deny Only in the restricted token. + // Parameter: sid is the SID to add in the Deny Only list. + // The return value is always ERROR_SUCCESS. + // + // Sample Usage: + // restricted_token.AddSidForDenyOnly(ATL::Sids::Admins().GetPSID()); + DWORD AddSidForDenyOnly(const Sid& sid); + + // Adds the user sid of the token for Deny Only in the restricted token. + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD AddUserSidForDenyOnly(); + + // Lists all privileges in the token and add them to the list of privileges + // to remove except for those present in the exceptions parameter. If + // there is no exception needed, the caller can pass an empty list or nullptr + // for the exceptions parameter. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + // + // Sample usage: + // std::vector<std::wstring> privilege_exceptions; + // privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + // restricted_token.DeleteAllPrivileges(&privilege_exceptions); + DWORD DeleteAllPrivileges(const std::vector<std::wstring>* exceptions); + + // Adds a privilege to the list of privileges to remove in the restricted + // token. + // Parameter: privilege is the privilege name to remove. This is the string + // representing the privilege. (e.g. "SeChangeNotifyPrivilege"). + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + // + // Sample usage: + // restricted_token.DeletePrivilege(SE_LOAD_DRIVER_NAME); + DWORD DeletePrivilege(const wchar_t* privilege); + + // Adds a SID to the list of restricting sids in the restricted token. + // Parameter: sid is the sid to add to the list restricting sids. + // The return value is always ERROR_SUCCESS. + // + // Sample usage: + // restricted_token.AddRestrictingSid(ATL::Sids::Users().GetPSID()); + // Note: The list of restricting is used to force Windows to perform all + // access checks twice. The first time using your user SID and your groups, + // and the second time using your list of restricting sids. The access has + // to be granted in both places to get access to the resource requested. + DWORD AddRestrictingSid(const Sid& sid); + + // Adds the logon sid of the token in the list of restricting sids for the + // restricted token. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD AddRestrictingSidLogonSession(); + + // Adds the owner sid of the token in the list of restricting sids for the + // restricted token. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD AddRestrictingSidCurrentUser(); + + // Adds all group sids and the user sid to the restricting sids list. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD AddRestrictingSidAllSids(); + + // Sets the token integrity level. This is only valid on Vista. The integrity + // level cannot be higher than your current integrity level. + DWORD SetIntegrityLevel(IntegrityLevel integrity_level); + + // Set a flag which indicates the created token should have a locked down + // default DACL when created. + void SetLockdownDefaultDacl(); + + // Add a SID to the default DACL. These SIDs are added regardless of the + // SetLockdownDefaultDacl state. + DWORD AddDefaultDaclSid(const Sid& sid, + ACCESS_MODE access_mode, + ACCESS_MASK access); + + private: + // The list of restricting sids in the restricted token. + std::vector<Sid> sids_to_restrict_; + // The list of privileges to remove in the restricted token. + std::vector<LUID> privileges_to_disable_; + // The list of sids to mark as Deny Only in the restricted token. + std::vector<Sid> sids_for_deny_only_; + // The list of sids to add to the default DACL of the restricted token. + std::vector<std::tuple<Sid, ACCESS_MODE, ACCESS_MASK>> sids_for_default_dacl_; + // The token to restrict. Can only be set in a constructor. + base::win::ScopedHandle effective_token_; + // The token integrity level. Only valid on Vista. + IntegrityLevel integrity_level_; + // Tells if the object is initialized or not (if Init() has been called) + bool init_; + // Lockdown the default DACL when creating new tokens. + bool lockdown_default_dacl_; + + DISALLOW_COPY_AND_ASSIGN(RestrictedToken); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_RESTRICTED_TOKEN_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/restricted_token_unittest.cc b/security/sandbox/chromium/sandbox/win/src/restricted_token_unittest.cc new file mode 100644 index 0000000000..1855054a7c --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/restricted_token_unittest.cc @@ -0,0 +1,829 @@ +// 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. + +// This file contains unit tests for the RestrictedToken. + +#include "sandbox/win/src/restricted_token.h" + +#include <vector> + +#include "base/win/atl.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/acl.h" +#include "sandbox/win/src/security_capabilities.h" +#include "sandbox/win/src/sid.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +void TestDefaultDalc(bool restricted_required) { + RestrictedToken token; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + if (!restricted_required) + token.SetLockdownDefaultDacl(); + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSid(ATL::Sids::World().GetPSID())); + + base::win::ScopedHandle handle; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(handle.Take()); + + ATL::CDacl dacl; + ASSERT_TRUE(restricted_token.GetDefaultDacl(&dacl)); + + ATL::CSid logon_sid; + ASSERT_TRUE(restricted_token.GetLogonSid(&logon_sid)); + + bool restricted_found = false; + bool logon_sid_found = false; + + unsigned int ace_count = dacl.GetAceCount(); + for (unsigned int i = 0; i < ace_count; ++i) { + ATL::CSid sid; + ACCESS_MASK mask = 0; + dacl.GetAclEntry(i, &sid, &mask); + if (sid == ATL::Sids::RestrictedCode() && mask == GENERIC_ALL) { + restricted_found = true; + } else if (sid == logon_sid) { + logon_sid_found = true; + } + } + + ASSERT_EQ(restricted_required, restricted_found); + if (!restricted_required) + ASSERT_FALSE(logon_sid_found); +} + +bool GetVariableTokenInformation(HANDLE token, + TOKEN_INFORMATION_CLASS information_class, + std::vector<char>* information) { + DWORD return_length; + if (!::GetTokenInformation(token, information_class, nullptr, 0, + &return_length)) { + if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + return false; + } + } + + information->resize(return_length); + return !!::GetTokenInformation(token, information_class, information->data(), + return_length, &return_length); +} + +bool GetVariableTokenInformation(const base::win::ScopedHandle& token, + TOKEN_INFORMATION_CLASS information_class, + std::vector<char>* information) { + return GetVariableTokenInformation(token.Get(), information_class, + information); +} + +void CheckDaclForPackageSid(const base::win::ScopedHandle& token, + PSECURITY_CAPABILITIES security_capabilities, + bool package_sid_required) { + DWORD length_needed = 0; + ::GetKernelObjectSecurity(token.Get(), DACL_SECURITY_INFORMATION, nullptr, 0, + &length_needed); + ASSERT_EQ(::GetLastError(), DWORD{ERROR_INSUFFICIENT_BUFFER}); + + std::vector<char> security_desc_buffer(length_needed); + SECURITY_DESCRIPTOR* security_desc = + reinterpret_cast<SECURITY_DESCRIPTOR*>(security_desc_buffer.data()); + + ASSERT_TRUE(::GetKernelObjectSecurity(token.Get(), DACL_SECURITY_INFORMATION, + security_desc, length_needed, + &length_needed)); + + ATL::CSecurityDesc token_sd(*security_desc); + ATL::CDacl dacl; + ASSERT_TRUE(token_sd.GetDacl(&dacl)); + + ATL::CSid package_sid( + static_cast<SID*>(security_capabilities->AppContainerSid)); + ATL::CSid all_package_sid( + static_cast<SID*>(sandbox::Sid(::WinBuiltinAnyPackageSid).GetPSID())); + + unsigned int ace_count = dacl.GetAceCount(); + for (unsigned int i = 0; i < ace_count; ++i) { + ATL::CSid sid; + ACCESS_MASK mask = 0; + BYTE type = 0; + dacl.GetAclEntry(i, &sid, &mask, &type); + if (mask != TOKEN_ALL_ACCESS || type != ACCESS_ALLOWED_ACE_TYPE) + continue; + if (sid == package_sid) + EXPECT_TRUE(package_sid_required); + else if (sid == all_package_sid) + EXPECT_FALSE(package_sid_required); + } +} + +void CheckLowBoxToken(const base::win::ScopedHandle& token, + TOKEN_TYPE token_type, + PSECURITY_CAPABILITIES security_capabilities) { + DWORD appcontainer; + DWORD return_length; + ASSERT_TRUE(::GetTokenInformation(token.Get(), ::TokenIsAppContainer, + &appcontainer, sizeof(appcontainer), + &return_length)); + ASSERT_TRUE(appcontainer); + TOKEN_TYPE token_type_real; + ASSERT_TRUE(::GetTokenInformation(token.Get(), ::TokenType, &token_type_real, + sizeof(token_type_real), &return_length)); + ASSERT_EQ(token_type_real, token_type); + if (token_type == ::TokenImpersonation) { + SECURITY_IMPERSONATION_LEVEL imp_level; + ASSERT_TRUE(::GetTokenInformation(token.Get(), ::TokenImpersonationLevel, + &imp_level, sizeof(imp_level), + &return_length)); + ASSERT_EQ(imp_level, ::SecurityImpersonation); + } + + std::vector<char> package_sid_buf; + ASSERT_TRUE(GetVariableTokenInformation(token, ::TokenAppContainerSid, + &package_sid_buf)); + PTOKEN_APPCONTAINER_INFORMATION package_sid = + reinterpret_cast<PTOKEN_APPCONTAINER_INFORMATION>(package_sid_buf.data()); + EXPECT_TRUE(::EqualSid(security_capabilities->AppContainerSid, + package_sid->TokenAppContainer)); + + std::vector<char> capabilities_buf; + ASSERT_TRUE(GetVariableTokenInformation(token, ::TokenCapabilities, + &capabilities_buf)); + PTOKEN_GROUPS capabilities = + reinterpret_cast<PTOKEN_GROUPS>(capabilities_buf.data()); + ASSERT_EQ(capabilities->GroupCount, security_capabilities->CapabilityCount); + for (DWORD index = 0; index < capabilities->GroupCount; ++index) { + EXPECT_EQ(capabilities->Groups[index].Attributes, + security_capabilities->Capabilities[index].Attributes); + EXPECT_TRUE(::EqualSid(capabilities->Groups[index].Sid, + security_capabilities->Capabilities[index].Sid)); + } + + CheckDaclForPackageSid(token, security_capabilities, true); +} + +// Checks if a sid is in the restricting list of the restricted token. +// Asserts if it's not the case. If count is a positive number, the number of +// elements in the restricting sids list has to be equal. +void CheckRestrictingSid(HANDLE restricted_token, ATL::CSid sid, int count) { + std::vector<char> memory; + ASSERT_TRUE(GetVariableTokenInformation(restricted_token, + ::TokenRestrictedSids, &memory)); + PTOKEN_GROUPS groups = reinterpret_cast<PTOKEN_GROUPS>(memory.data()); + ATL::CTokenGroups atl_groups(*groups); + + if (count >= 0) + ASSERT_EQ(static_cast<unsigned>(count), atl_groups.GetCount()); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + atl_groups.GetSidsAndAttributes(&sids, &attributes); + + bool present = false; + for (unsigned int i = 0; i < sids.GetCount(); ++i) { + if (sids[i] == sid) { + present = true; + break; + } + } + + ASSERT_TRUE(present); +} + +void CheckRestrictingSid(const ATL::CAccessToken& restricted_token, + ATL::CSid sid, + int count) { + CheckRestrictingSid(restricted_token.GetHandle(), sid, count); +} + +} // namespace + +// Tests the initializatioin with an invalid token handle. +TEST(RestrictedTokenTest, InvalidHandle) { + RestrictedToken token; + ASSERT_EQ(static_cast<DWORD>(ERROR_INVALID_HANDLE), + token.Init(reinterpret_cast<HANDLE>(0x5555))); +} + +// Tests the initialization with nullptr as parameter. +TEST(RestrictedTokenTest, DefaultInit) { + // Get the current process token. + HANDLE token_handle = INVALID_HANDLE_VALUE; + ASSERT_TRUE(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &token_handle)); + + ASSERT_NE(INVALID_HANDLE_VALUE, token_handle); + + ATL::CAccessToken access_token; + access_token.Attach(token_handle); + + // Create the token using the current token. + RestrictedToken token_default; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token_default.Init(nullptr)); + + // Get the handle to the restricted token. + + base::win::ScopedHandle restricted_token_handle; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token_default.GetRestrictedToken(&restricted_token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(restricted_token_handle.Take()); + + ATL::CSid sid_user_restricted; + ATL::CSid sid_user_default; + ATL::CSid sid_owner_restricted; + ATL::CSid sid_owner_default; + ASSERT_TRUE(restricted_token.GetUser(&sid_user_restricted)); + ASSERT_TRUE(access_token.GetUser(&sid_user_default)); + ASSERT_TRUE(restricted_token.GetOwner(&sid_owner_restricted)); + ASSERT_TRUE(access_token.GetOwner(&sid_owner_default)); + + // Check if both token have the same owner and user. + ASSERT_EQ(sid_user_restricted, sid_user_default); + ASSERT_EQ(sid_owner_restricted, sid_owner_default); +} + +// Tests the initialization with a custom token as parameter. +TEST(RestrictedTokenTest, CustomInit) { + // Get the current process token. + HANDLE token_handle = INVALID_HANDLE_VALUE; + ASSERT_TRUE(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &token_handle)); + + ASSERT_NE(INVALID_HANDLE_VALUE, token_handle); + + ATL::CAccessToken access_token; + access_token.Attach(token_handle); + + // Change the primary group. + access_token.SetPrimaryGroup(ATL::Sids::World()); + + // Create the token using the current token. + RestrictedToken token; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.Init(access_token.GetHandle())); + + // Get the handle to the restricted token. + + base::win::ScopedHandle restricted_token_handle; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&restricted_token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(restricted_token_handle.Take()); + + ATL::CSid sid_restricted; + ATL::CSid sid_default; + ASSERT_TRUE(restricted_token.GetPrimaryGroup(&sid_restricted)); + ASSERT_TRUE(access_token.GetPrimaryGroup(&sid_default)); + + // Check if both token have the same owner. + ASSERT_EQ(sid_restricted, sid_default); +} + +// Verifies that the token created by the object are valid. +TEST(RestrictedTokenTest, ResultToken) { + RestrictedToken token; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSid(ATL::Sids::World().GetPSID())); + + base::win::ScopedHandle restricted_token; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&restricted_token)); + + ASSERT_TRUE(::IsTokenRestricted(restricted_token.Get())); + + DWORD length = 0; + TOKEN_TYPE type; + ASSERT_TRUE(::GetTokenInformation(restricted_token.Get(), ::TokenType, &type, + sizeof(type), &length)); + + ASSERT_EQ(type, TokenPrimary); + + base::win::ScopedHandle impersonation_token; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedTokenForImpersonation(&impersonation_token)); + + ASSERT_TRUE(::IsTokenRestricted(impersonation_token.Get())); + + ASSERT_TRUE(::GetTokenInformation(impersonation_token.Get(), ::TokenType, + &type, sizeof(type), &length)); + + ASSERT_EQ(type, TokenImpersonation); +} + +// Verifies that the token created has "Restricted" in its default dacl. +TEST(RestrictedTokenTest, DefaultDacl) { + TestDefaultDalc(true); +} + +// Verifies that the token created does not have "Restricted" in its default +// dacl. +TEST(RestrictedTokenTest, DefaultDaclLockdown) { + TestDefaultDalc(false); +} + +// Tests the method "AddSidForDenyOnly". +TEST(RestrictedTokenTest, DenySid) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddSidForDenyOnly(Sid(WinWorldSid))); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + for (unsigned int i = 0; i < sids.GetCount(); i++) { + if (ATL::Sids::World() == sids[i]) { + ASSERT_EQ(static_cast<DWORD>(SE_GROUP_USE_FOR_DENY_ONLY), + attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } + } +} + +// Tests the method "AddAllSidsForDenyOnly". +TEST(RestrictedTokenTest, DenySids) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddAllSidsForDenyOnly(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + // Verify that all sids are really gone. + for (unsigned int i = 0; i < sids.GetCount(); i++) { + if ((attributes[i] & SE_GROUP_LOGON_ID) == 0 && + (attributes[i] & SE_GROUP_INTEGRITY) == 0) { + ASSERT_EQ(static_cast<DWORD>(SE_GROUP_USE_FOR_DENY_ONLY), + attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } + } +} + +// Tests the method "AddAllSidsForDenyOnly" using an exception list. +TEST(RestrictedTokenTest, DenySidsException) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + std::vector<Sid> sids_exception; + sids_exception.push_back(Sid(WinWorldSid)); + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddAllSidsForDenyOnly(&sids_exception)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + // Verify that all sids are really gone. + for (unsigned int i = 0; i < sids.GetCount(); i++) { + if ((attributes[i] & SE_GROUP_LOGON_ID) == 0 && + (attributes[i] & SE_GROUP_INTEGRITY) == 0) { + if (ATL::Sids::World() == sids[i]) { + ASSERT_EQ(0u, attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } else { + ASSERT_EQ(static_cast<DWORD>(SE_GROUP_USE_FOR_DENY_ONLY), + attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } + } + } +} + +// Tests test method AddOwnerSidForDenyOnly. +TEST(RestrictedTokenTest, DenyOwnerSid) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.AddUserSidForDenyOnly()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + ATL::CSid user_sid; + ASSERT_TRUE(restricted_token.GetUser(&user_sid)); + + for (unsigned int i = 0; i < sids.GetCount(); ++i) { + if (user_sid == sids[i]) { + ASSERT_EQ(static_cast<DWORD>(SE_GROUP_USE_FOR_DENY_ONLY), + attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } + } +} + +// Tests test method AddOwnerSidForDenyOnly with a custom effective token. +TEST(RestrictedTokenTest, DenyOwnerSidCustom) { + // Get the current process token. + HANDLE access_handle = INVALID_HANDLE_VALUE; + ASSERT_TRUE(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &access_handle)); + + ASSERT_NE(INVALID_HANDLE_VALUE, access_handle); + + ATL::CAccessToken access_token; + access_token.Attach(access_handle); + + RestrictedToken token; + base::win::ScopedHandle token_handle; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.Init(access_token.GetHandle())); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.AddUserSidForDenyOnly()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + ATL::CSid user_sid; + ASSERT_TRUE(restricted_token.GetUser(&user_sid)); + + for (unsigned int i = 0; i < sids.GetCount(); ++i) { + if (user_sid == sids[i]) { + ASSERT_EQ(static_cast<DWORD>(SE_GROUP_USE_FOR_DENY_ONLY), + attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } + } +} + +// Tests the method DeleteAllPrivileges. +TEST(RestrictedTokenTest, DeleteAllPrivileges) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.DeleteAllPrivileges(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenPrivileges privileges; + ASSERT_TRUE(restricted_token.GetPrivileges(&privileges)); + + ASSERT_EQ(0u, privileges.GetCount()); +} + +// Tests the method DeleteAllPrivileges with an exception list. +TEST(RestrictedTokenTest, DeleteAllPrivilegesException) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + std::vector<std::wstring> exceptions; + exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.DeleteAllPrivileges(&exceptions)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenPrivileges privileges; + ASSERT_TRUE(restricted_token.GetPrivileges(&privileges)); + + ATL::CTokenPrivileges::CNames privilege_names; + ATL::CTokenPrivileges::CAttributes privilege_name_attributes; + privileges.GetNamesAndAttributes(&privilege_names, + &privilege_name_attributes); + + ASSERT_EQ(1u, privileges.GetCount()); + + for (unsigned int i = 0; i < privileges.GetCount(); ++i) { + ASSERT_EQ(privilege_names[i], SE_CHANGE_NOTIFY_NAME); + } +} + +// Tests the method DeletePrivilege. +TEST(RestrictedTokenTest, DeletePrivilege) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.DeletePrivilege(SE_CHANGE_NOTIFY_NAME)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenPrivileges privileges; + ASSERT_TRUE(restricted_token.GetPrivileges(&privileges)); + + ATL::CTokenPrivileges::CNames privilege_names; + ATL::CTokenPrivileges::CAttributes privilege_name_attributes; + privileges.GetNamesAndAttributes(&privilege_names, + &privilege_name_attributes); + + for (unsigned int i = 0; i < privileges.GetCount(); ++i) { + ASSERT_NE(privilege_names[i], SE_CHANGE_NOTIFY_NAME); + } +} + +// Tests the method AddRestrictingSid. +TEST(RestrictedTokenTest, AddRestrictingSid) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSid(ATL::Sids::World().GetPSID())); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + CheckRestrictingSid(restricted_token, ATL::Sids::World(), 1); +} + +// Tests the method AddRestrictingSidCurrentUser. +TEST(RestrictedTokenTest, AddRestrictingSidCurrentUser) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSidCurrentUser()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + ATL::CSid user; + restricted_token.GetUser(&user); + + CheckRestrictingSid(restricted_token, user, 1); +} + +// Tests the method AddRestrictingSidCurrentUser with a custom effective token. +TEST(RestrictedTokenTest, AddRestrictingSidCurrentUserCustom) { + // Get the current process token. + HANDLE access_handle = INVALID_HANDLE_VALUE; + ASSERT_TRUE(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &access_handle)); + + ASSERT_NE(INVALID_HANDLE_VALUE, access_handle); + + ATL::CAccessToken access_token; + access_token.Attach(access_handle); + + RestrictedToken token; + base::win::ScopedHandle token_handle; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.Init(access_token.GetHandle())); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSidCurrentUser()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + ATL::CSid user; + restricted_token.GetUser(&user); + + CheckRestrictingSid(restricted_token, user, 1); +} + +// Tests the method AddRestrictingSidLogonSession. +TEST(RestrictedTokenTest, AddRestrictingSidLogonSession) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSidLogonSession()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + ATL::CSid session; + restricted_token.GetLogonSid(&session); + + CheckRestrictingSid(restricted_token, session, 1); +} + +// Tests adding a lot of restricting sids. +TEST(RestrictedTokenTest, AddMultipleRestrictingSids) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSidCurrentUser()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSidLogonSession()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSid(ATL::Sids::World().GetPSID())); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + ATL::CSid session; + restricted_token.GetLogonSid(&session); + + std::vector<char> memory; + ASSERT_TRUE(GetVariableTokenInformation(restricted_token.GetHandle(), + ::TokenRestrictedSids, &memory)); + PTOKEN_GROUPS groups = reinterpret_cast<PTOKEN_GROUPS>(memory.data()); + ATL::CTokenGroups atl_groups(*groups); + ASSERT_EQ(3u, atl_groups.GetCount()); +} + +// Tests the method "AddRestrictingSidAllSids". +TEST(RestrictedTokenTest, AddAllSidToRestrictingSids) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSidAllSids()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + // Verify that all group sids are in the restricting sid list. + for (unsigned int i = 0; i < sids.GetCount(); i++) { + if ((attributes[i] & SE_GROUP_INTEGRITY) == 0) { + CheckRestrictingSid(restricted_token, sids[i], -1); + } + } + + // Verify that the user is in the restricting sid list. + ATL::CSid user; + restricted_token.GetUser(&user); + CheckRestrictingSid(restricted_token, user, -1); +} + +// Checks the error code when the object is initialized twice. +TEST(RestrictedTokenTest, DoubleInit) { + RestrictedToken token; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + + ASSERT_EQ(static_cast<DWORD>(ERROR_ALREADY_INITIALIZED), token.Init(nullptr)); +} + +TEST(RestrictedTokenTest, LockdownDefaultDaclNoLogonSid) { + ATL::CAccessToken anonymous_token; + ASSERT_TRUE(::ImpersonateAnonymousToken(::GetCurrentThread())); + ASSERT_TRUE(anonymous_token.GetThreadToken(TOKEN_ALL_ACCESS)); + ::RevertToSelf(); + ATL::CSid logon_sid; + // Verify that the anonymous token doesn't have the logon sid. + ASSERT_FALSE(anonymous_token.GetLogonSid(&logon_sid)); + + RestrictedToken token; + ASSERT_EQ(DWORD{ERROR_SUCCESS}, token.Init(anonymous_token.GetHandle())); + token.SetLockdownDefaultDacl(); + + base::win::ScopedHandle handle; + ASSERT_EQ(DWORD{ERROR_SUCCESS}, token.GetRestrictedToken(&handle)); +} + +TEST(RestrictedTokenTest, LowBoxToken) { + if (base::win::GetVersion() < base::win::Version::WIN8) + return; + base::win::ScopedHandle token; + + Sid package_sid = Sid::FromSddlString(L"S-1-15-2-1-2-3-4-5-6-7"); + SecurityCapabilities caps_no_capabilities(package_sid); + + ASSERT_EQ(DWORD{ERROR_INVALID_PARAMETER}, + CreateLowBoxToken(nullptr, PRIMARY, &caps_no_capabilities, nullptr, + 0, nullptr)); + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxToken(nullptr, PRIMARY, &caps_no_capabilities, nullptr, + 0, &token)); + ASSERT_TRUE(token.IsValid()); + CheckLowBoxToken(token, ::TokenPrimary, &caps_no_capabilities); + + ASSERT_TRUE(ReplacePackageSidInDacl(token.Get(), SE_KERNEL_OBJECT, + Sid(caps_no_capabilities.AppContainerSid), + TOKEN_ALL_ACCESS)); + CheckDaclForPackageSid(token, &caps_no_capabilities, false); + + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxToken(nullptr, IMPERSONATION, &caps_no_capabilities, + nullptr, 0, &token)); + ASSERT_TRUE(token.IsValid()); + CheckLowBoxToken(token, ::TokenImpersonation, &caps_no_capabilities); + + std::vector<Sid> capabilities; + capabilities.push_back(Sid::FromKnownCapability(kInternetClient)); + capabilities.push_back(Sid::FromKnownCapability(kPrivateNetworkClientServer)); + SecurityCapabilities caps_with_capabilities(package_sid, capabilities); + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxToken(nullptr, PRIMARY, &caps_with_capabilities, + nullptr, 0, &token)); + ASSERT_TRUE(token.IsValid()); + CheckLowBoxToken(token, ::TokenPrimary, &caps_with_capabilities); + + RestrictedToken restricted_token; + base::win::ScopedHandle token_handle; + ASSERT_EQ(DWORD{ERROR_SUCCESS}, restricted_token.Init(nullptr)); + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + restricted_token.AddRestrictingSid(ATL::Sids::World().GetPSID())); + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + restricted_token.GetRestrictedToken(&token_handle)); + + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxToken(token_handle.Get(), PRIMARY, + &caps_with_capabilities, nullptr, 0, &token)); + ASSERT_TRUE(token.IsValid()); + CheckLowBoxToken(token, ::TokenPrimary, &caps_with_capabilities); + CheckRestrictingSid(token.Get(), ATL::Sids::World(), 1); + + SecurityCapabilities caps_for_handles( + Sid::FromSddlString(L"S-1-15-2-1-2-3-4-5-6-8")); + base::win::ScopedHandle object_handle; + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxObjectDirectory(caps_for_handles.AppContainerSid, true, + &object_handle)); + HANDLE saved_handles[] = {object_handle.Get()}; + + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxToken(token_handle.Get(), PRIMARY, &caps_for_handles, + saved_handles, 1, &token)); + ASSERT_TRUE(token.IsValid()); + object_handle.Close(); + ASSERT_FALSE(object_handle.IsValid()); + ASSERT_EQ(DWORD{ERROR_ALREADY_EXISTS}, + CreateLowBoxObjectDirectory(caps_for_handles.AppContainerSid, false, + &object_handle)); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/restricted_token_utils.cc b/security/sandbox/chromium/sandbox/win/src/restricted_token_utils.cc new file mode 100644 index 0000000000..863dee8d69 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/restricted_token_utils.cc @@ -0,0 +1,480 @@ +// 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 "sandbox/win/src/restricted_token_utils.h" + +#include <aclapi.h> +#include <sddl.h> + +#include <memory> +#include <vector> + +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/job.h" +#include "sandbox/win/src/restricted_token.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/security_level.h" +#include "sandbox/win/src/sid.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +namespace { + +DWORD GetObjectSecurityDescriptor(HANDLE handle, + SECURITY_INFORMATION security_info, + std::vector<char>* security_desc_buffer, + PSECURITY_DESCRIPTOR* security_desc) { + DWORD last_error = 0; + DWORD length_needed = 0; + + ::GetKernelObjectSecurity(handle, security_info, nullptr, 0, &length_needed); + last_error = ::GetLastError(); + if (last_error != ERROR_INSUFFICIENT_BUFFER) + return last_error; + + security_desc_buffer->resize(length_needed); + *security_desc = + reinterpret_cast<PSECURITY_DESCRIPTOR>(security_desc_buffer->data()); + + if (!::GetKernelObjectSecurity(handle, security_info, *security_desc, + length_needed, &length_needed)) { + return ::GetLastError(); + } + + return ERROR_SUCCESS; +} + +} // namespace + +DWORD CreateRestrictedToken(HANDLE effective_token, + TokenLevel security_level, + IntegrityLevel integrity_level, + TokenType token_type, + bool lockdown_default_dacl, + PSID unique_restricted_sid, + bool use_restricting_sids, + base::win::ScopedHandle* token) { + RestrictedToken restricted_token; + restricted_token.Init(effective_token); + if (lockdown_default_dacl) + restricted_token.SetLockdownDefaultDacl(); + if (unique_restricted_sid) { + restricted_token.AddDefaultDaclSid(Sid(unique_restricted_sid), GRANT_ACCESS, + GENERIC_ALL); + restricted_token.AddDefaultDaclSid(Sid(WinCreatorOwnerRightsSid), + GRANT_ACCESS, READ_CONTROL); + } + + std::vector<std::wstring> privilege_exceptions; + std::vector<Sid> sid_exceptions; + + bool deny_sids = true; + bool remove_privileges = true; + + switch (security_level) { + case USER_UNPROTECTED: { + deny_sids = false; + remove_privileges = false; + break; + } + case USER_RESTRICTED_SAME_ACCESS: { + deny_sids = false; + remove_privileges = false; + + if (use_restricting_sids) { + unsigned err_code = restricted_token.AddRestrictingSidAllSids(); + if (ERROR_SUCCESS != err_code) { + return err_code; + } + } + + break; + } + case USER_NON_ADMIN: { + sid_exceptions.push_back(WinBuiltinUsersSid); + sid_exceptions.push_back(WinWorldSid); + sid_exceptions.push_back(WinInteractiveSid); + sid_exceptions.push_back(WinAuthenticatedUserSid); + privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + break; + } + case USER_RESTRICTED_NON_ADMIN: { + sid_exceptions.push_back(WinBuiltinUsersSid); + sid_exceptions.push_back(WinWorldSid); + sid_exceptions.push_back(WinInteractiveSid); + sid_exceptions.push_back(WinAuthenticatedUserSid); + privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + restricted_token.AddRestrictingSid(WinBuiltinUsersSid); + restricted_token.AddRestrictingSid(WinWorldSid); + restricted_token.AddRestrictingSid(WinInteractiveSid); + restricted_token.AddRestrictingSid(WinAuthenticatedUserSid); + restricted_token.AddRestrictingSid(WinRestrictedCodeSid); + restricted_token.AddRestrictingSidCurrentUser(); + restricted_token.AddRestrictingSidLogonSession(); + break; + } + case USER_INTERACTIVE: { + sid_exceptions.push_back(WinBuiltinUsersSid); + sid_exceptions.push_back(WinWorldSid); + sid_exceptions.push_back(WinInteractiveSid); + sid_exceptions.push_back(WinAuthenticatedUserSid); + privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + if (use_restricting_sids) { + restricted_token.AddRestrictingSid(WinBuiltinUsersSid); + restricted_token.AddRestrictingSid(WinWorldSid); + restricted_token.AddRestrictingSid(WinRestrictedCodeSid); + restricted_token.AddRestrictingSidCurrentUser(); + restricted_token.AddRestrictingSidLogonSession(); + if (unique_restricted_sid) + restricted_token.AddRestrictingSid(Sid(unique_restricted_sid)); + } + break; + } + case USER_LIMITED: { + sid_exceptions.push_back(WinBuiltinUsersSid); + sid_exceptions.push_back(WinWorldSid); + sid_exceptions.push_back(WinInteractiveSid); + privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + if (use_restricting_sids) { + restricted_token.AddRestrictingSid(WinBuiltinUsersSid); + restricted_token.AddRestrictingSid(WinWorldSid); + restricted_token.AddRestrictingSid(WinRestrictedCodeSid); + if (unique_restricted_sid) + restricted_token.AddRestrictingSid(Sid(unique_restricted_sid)); + + // This token has to be able to create objects in BNO. + // Unfortunately, on Vista+, it needs the current logon sid + // in the token to achieve this. You should also set the process to be + // low integrity level so it can't access object created by other + // processes. + restricted_token.AddRestrictingSidLogonSession(); + } else { + restricted_token.AddUserSidForDenyOnly(); + } + break; + } + case USER_RESTRICTED: { + privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + restricted_token.AddUserSidForDenyOnly(); + if (use_restricting_sids) { + restricted_token.AddRestrictingSid(WinRestrictedCodeSid); + if (unique_restricted_sid) + restricted_token.AddRestrictingSid(Sid(unique_restricted_sid)); + } + break; + } + case USER_LOCKDOWN: { + restricted_token.AddUserSidForDenyOnly(); + if (use_restricting_sids) { + restricted_token.AddRestrictingSid(WinNullSid); + if (unique_restricted_sid) + restricted_token.AddRestrictingSid(Sid(unique_restricted_sid)); + } + break; + } + default: { return ERROR_BAD_ARGUMENTS; } + } + + DWORD err_code = ERROR_SUCCESS; + if (deny_sids) { + err_code = restricted_token.AddAllSidsForDenyOnly(&sid_exceptions); + if (ERROR_SUCCESS != err_code) + return err_code; + } + + if (remove_privileges) { + err_code = restricted_token.DeleteAllPrivileges(&privilege_exceptions); + if (ERROR_SUCCESS != err_code) + return err_code; + } + + restricted_token.SetIntegrityLevel(integrity_level); + + switch (token_type) { + case PRIMARY: { + err_code = restricted_token.GetRestrictedToken(token); + break; + } + case IMPERSONATION: { + err_code = restricted_token.GetRestrictedTokenForImpersonation(token); + break; + } + default: { + err_code = ERROR_BAD_ARGUMENTS; + break; + } + } + + return err_code; +} + +DWORD SetObjectIntegrityLabel(HANDLE handle, + SE_OBJECT_TYPE type, + const wchar_t* ace_access, + const wchar_t* integrity_level_sid) { + // Build the SDDL string for the label. + std::wstring sddl = L"S:("; // SDDL for a SACL. + sddl += SDDL_MANDATORY_LABEL; // Ace Type is "Mandatory Label". + sddl += L";;"; // No Ace Flags. + sddl += ace_access; // Add the ACE access. + sddl += L";;;"; // No ObjectType and Inherited Object Type. + sddl += integrity_level_sid; // Trustee Sid. + sddl += L")"; + + DWORD error = ERROR_SUCCESS; + PSECURITY_DESCRIPTOR sec_desc = nullptr; + + PACL sacl = nullptr; + BOOL sacl_present = false; + BOOL sacl_defaulted = false; + + if (::ConvertStringSecurityDescriptorToSecurityDescriptorW( + sddl.c_str(), SDDL_REVISION, &sec_desc, nullptr)) { + if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl, + &sacl_defaulted)) { + error = ::SetSecurityInfo(handle, type, LABEL_SECURITY_INFORMATION, + nullptr, nullptr, nullptr, sacl); + } else { + error = ::GetLastError(); + } + + ::LocalFree(sec_desc); + } else { + return ::GetLastError(); + } + + return error; +} + +const wchar_t* GetIntegrityLevelString(IntegrityLevel integrity_level) { + switch (integrity_level) { + case INTEGRITY_LEVEL_SYSTEM: + return L"S-1-16-16384"; + case INTEGRITY_LEVEL_HIGH: + return L"S-1-16-12288"; + case INTEGRITY_LEVEL_MEDIUM: + return L"S-1-16-8192"; + case INTEGRITY_LEVEL_MEDIUM_LOW: + return L"S-1-16-6144"; + case INTEGRITY_LEVEL_LOW: + return L"S-1-16-4096"; + case INTEGRITY_LEVEL_BELOW_LOW: + return L"S-1-16-2048"; + case INTEGRITY_LEVEL_UNTRUSTED: + return L"S-1-16-0"; + case INTEGRITY_LEVEL_LAST: + return nullptr; + } + + NOTREACHED(); + return nullptr; +} +DWORD SetTokenIntegrityLevel(HANDLE token, IntegrityLevel integrity_level) { + const wchar_t* integrity_level_str = GetIntegrityLevelString(integrity_level); + if (!integrity_level_str) { + // No mandatory level specified, we don't change it. + return ERROR_SUCCESS; + } + + PSID integrity_sid = nullptr; + if (!::ConvertStringSidToSid(integrity_level_str, &integrity_sid)) + return ::GetLastError(); + + TOKEN_MANDATORY_LABEL label = {}; + label.Label.Attributes = SE_GROUP_INTEGRITY; + label.Label.Sid = integrity_sid; + + DWORD size = sizeof(TOKEN_MANDATORY_LABEL) + ::GetLengthSid(integrity_sid); + bool result = ::SetTokenInformation(token, TokenIntegrityLevel, &label, size); + auto last_error = ::GetLastError(); + ::LocalFree(integrity_sid); + + return result ? ERROR_SUCCESS : last_error; +} + +DWORD SetProcessIntegrityLevel(IntegrityLevel integrity_level) { + // We don't check for an invalid level here because we'll just let it + // fail on the SetTokenIntegrityLevel call later on. + if (integrity_level == INTEGRITY_LEVEL_LAST) { + // No mandatory level specified, we don't change it. + return ERROR_SUCCESS; + } + + HANDLE token_handle; + if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT, + &token_handle)) + return ::GetLastError(); + + base::win::ScopedHandle token(token_handle); + + return SetTokenIntegrityLevel(token.Get(), integrity_level); +} + +DWORD HardenTokenIntegrityLevelPolicy(HANDLE token) { + std::vector<char> security_desc_buffer; + PSECURITY_DESCRIPTOR security_desc = nullptr; + DWORD last_error = GetObjectSecurityDescriptor( + token, LABEL_SECURITY_INFORMATION, &security_desc_buffer, &security_desc); + if (last_error != ERROR_SUCCESS) + return last_error; + + PACL sacl = nullptr; + BOOL sacl_present = false; + BOOL sacl_defaulted = false; + + if (!::GetSecurityDescriptorSacl(security_desc, &sacl_present, &sacl, + &sacl_defaulted)) { + return ::GetLastError(); + } + + for (DWORD ace_index = 0; ace_index < sacl->AceCount; ++ace_index) { + PSYSTEM_MANDATORY_LABEL_ACE ace; + + if (::GetAce(sacl, ace_index, reinterpret_cast<LPVOID*>(&ace)) && + ace->Header.AceType == SYSTEM_MANDATORY_LABEL_ACE_TYPE) { + ace->Mask |= SYSTEM_MANDATORY_LABEL_NO_READ_UP | + SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP; + break; + } + } + + if (!::SetKernelObjectSecurity(token, LABEL_SECURITY_INFORMATION, + security_desc)) + return ::GetLastError(); + + return ERROR_SUCCESS; +} + +DWORD HardenProcessIntegrityLevelPolicy() { + HANDLE token_handle; + if (!::OpenProcessToken(GetCurrentProcess(), READ_CONTROL | WRITE_OWNER, + &token_handle)) + return ::GetLastError(); + + base::win::ScopedHandle token(token_handle); + + return HardenTokenIntegrityLevelPolicy(token.Get()); +} + +DWORD CreateLowBoxToken(HANDLE base_token, + TokenType token_type, + PSECURITY_CAPABILITIES security_capabilities, + PHANDLE saved_handles, + DWORD saved_handles_count, + base::win::ScopedHandle* token) { + NtCreateLowBoxToken CreateLowBoxToken = nullptr; + ResolveNTFunctionPtr("NtCreateLowBoxToken", &CreateLowBoxToken); + + if (base::win::GetVersion() < base::win::Version::WIN8) + return ERROR_CALL_NOT_IMPLEMENTED; + + if (token_type != PRIMARY && token_type != IMPERSONATION) + return ERROR_INVALID_PARAMETER; + + if (!token) + return ERROR_INVALID_PARAMETER; + + base::win::ScopedHandle base_token_handle; + if (!base_token) { + HANDLE process_token = nullptr; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &process_token)) { + return ::GetLastError(); + } + base_token_handle.Set(process_token); + base_token = process_token; + } + OBJECT_ATTRIBUTES obj_attr; + InitializeObjectAttributes(&obj_attr, nullptr, 0, nullptr, nullptr); + HANDLE token_lowbox = nullptr; + + NTSTATUS status = CreateLowBoxToken( + &token_lowbox, base_token, TOKEN_ALL_ACCESS, &obj_attr, + security_capabilities->AppContainerSid, + security_capabilities->CapabilityCount, + security_capabilities->Capabilities, saved_handles_count, + saved_handles_count > 0 ? saved_handles : nullptr); + if (!NT_SUCCESS(status)) + return GetLastErrorFromNtStatus(status); + + base::win::ScopedHandle token_lowbox_handle(token_lowbox); + DCHECK(token_lowbox_handle.IsValid()); + + // Default from NtCreateLowBoxToken is a Primary token. + if (token_type == PRIMARY) { + *token = std::move(token_lowbox_handle); + return ERROR_SUCCESS; + } + + HANDLE dup_handle = nullptr; + if (!::DuplicateTokenEx(token_lowbox_handle.Get(), TOKEN_ALL_ACCESS, nullptr, + ::SecurityImpersonation, ::TokenImpersonation, + &dup_handle)) { + return ::GetLastError(); + } + + // Copy security descriptor from primary token as the new object will have + // the DACL from the current token's default DACL. + base::win::ScopedHandle token_for_sd(dup_handle); + std::vector<char> security_desc_buffer; + PSECURITY_DESCRIPTOR security_desc = nullptr; + DWORD last_error = GetObjectSecurityDescriptor( + token_lowbox_handle.Get(), DACL_SECURITY_INFORMATION, + &security_desc_buffer, &security_desc); + if (last_error != ERROR_SUCCESS) + return last_error; + + if (!::SetKernelObjectSecurity(token_for_sd.Get(), DACL_SECURITY_INFORMATION, + security_desc)) { + return ::GetLastError(); + } + + *token = std::move(token_for_sd); + + return ERROR_SUCCESS; +} + +DWORD CreateLowBoxObjectDirectory(PSID lowbox_sid, + bool open_directory, + base::win::ScopedHandle* directory) { + DWORD session_id = 0; + if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &session_id)) + return ::GetLastError(); + + LPWSTR sid_string = nullptr; + if (!::ConvertSidToStringSid(lowbox_sid, &sid_string)) + return ::GetLastError(); + + std::unique_ptr<wchar_t, LocalFreeDeleter> sid_string_ptr(sid_string); + std::wstring directory_path = base::StringPrintf( + L"\\Sessions\\%d\\AppContainerNamedObjects\\%ls", session_id, sid_string); + + NtCreateDirectoryObjectFunction CreateObjectDirectory = nullptr; + ResolveNTFunctionPtr("NtCreateDirectoryObject", &CreateObjectDirectory); + + OBJECT_ATTRIBUTES obj_attr; + UNICODE_STRING obj_name; + DWORD attributes = OBJ_CASE_INSENSITIVE; + if (open_directory) + attributes |= OBJ_OPENIF; + + sandbox::InitObjectAttribs(directory_path, attributes, nullptr, &obj_attr, + &obj_name, nullptr); + + HANDLE handle = nullptr; + NTSTATUS status = + CreateObjectDirectory(&handle, DIRECTORY_ALL_ACCESS, &obj_attr); + + if (!NT_SUCCESS(status)) + return GetLastErrorFromNtStatus(status); + directory->Set(handle); + + return ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/restricted_token_utils.h b/security/sandbox/chromium/sandbox/win/src/restricted_token_utils.h new file mode 100644 index 0000000000..e1f75a89c9 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/restricted_token_utils.h @@ -0,0 +1,105 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_SRC_RESTRICTED_TOKEN_UTILS_H__ +#define SANDBOX_SRC_RESTRICTED_TOKEN_UTILS_H__ + +#include <accctrl.h> +#include <windows.h> + +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/restricted_token.h" +#include "sandbox/win/src/security_level.h" + +// Contains the utility functions to be able to create restricted tokens based +// on a security profiles. + +namespace sandbox { + +// The type of the token returned by the CreateNakedToken. +enum TokenType { IMPERSONATION = 0, PRIMARY }; + +// Creates a restricted token from effective token. If it's nullptr then +// effective token of process is used instead. The parameter security_level +// determines how much the token isrestricted. The token_type determines if +// the token will be used as a primarytoken or impersonation token. The +// integrity level of the token is set to |integrity level| on Vista only. +// |token| is the output value containing the handle of the newly created +// restricted token. +// |lockdown_default_dacl| indicates the token's default DACL should be locked +// down to restrict what other process can open kernel resources created while +// running under the token. +// If the function succeeds, the return value is ERROR_SUCCESS. If the +// function fails, the return value is the win32 error code corresponding to +// the error. +DWORD CreateRestrictedToken(HANDLE effective_token, + TokenLevel security_level, + IntegrityLevel integrity_level, + TokenType token_type, + bool lockdown_default_dacl, + PSID unique_restricted_sid, + bool use_restricting_sids, + base::win::ScopedHandle* token); + +// Sets the integrity label on a object handle. +DWORD SetObjectIntegrityLabel(HANDLE handle, + SE_OBJECT_TYPE type, + const wchar_t* ace_access, + const wchar_t* integrity_level_sid); + +// Sets the integrity level on a token. This is only valid on Vista. It returns +// without failing on XP. If the integrity level that you specify is greater +// than the current integrity level, the function will fail. +DWORD SetTokenIntegrityLevel(HANDLE token, IntegrityLevel integrity_level); + +// Returns the integrity level SDDL string associated with a given +// IntegrityLevel value. +const wchar_t* GetIntegrityLevelString(IntegrityLevel integrity_level); + +// Sets the integrity level on the current process on Vista. It returns without +// failing on XP. If the integrity level that you specify is greater than the +// current integrity level, the function will fail. +DWORD SetProcessIntegrityLevel(IntegrityLevel integrity_level); + +// Hardens the integrity level policy on a token. This is only valid on Win 7 +// and above. Specifically it sets the policy to block read and execute so +// that a lower privileged process cannot open the token for impersonate or +// duplicate permissions. This should limit potential security holes. +DWORD HardenTokenIntegrityLevelPolicy(HANDLE token); + +// Hardens the integrity level policy on the current process. This is only +// valid on Win 7 and above. Specifically it sets the policy to block read +// and execute so that a lower privileged process cannot open the token for +// impersonate or duplicate permissions. This should limit potential security +// holes. +DWORD HardenProcessIntegrityLevelPolicy(); + +// Create a lowbox token. This is not valid prior to Windows 8. +// |base_token| a base token to derive the lowbox token from. Can be nullptr. +// |security_capabilities| list of LowBox capabilities to use when creating the +// token. +// |token| is the output value containing the handle of the newly created +// restricted token. +// |lockdown_default_dacl| indicates the token's default DACL should be locked +// down to restrict what other process can open kernel resources created while +// running under the token. +DWORD CreateLowBoxToken(HANDLE base_token, + TokenType token_type, + PSECURITY_CAPABILITIES security_capabilities, + PHANDLE saved_handles, + DWORD saved_handles_count, + base::win::ScopedHandle* token); + +// Create a lowbox object directory token. This is not valid prior to Windows 8. +// This returns the Win32 error code from the operation. +// |lowbox_sid| the SID for the LowBox. +// |open_directory| open the directory if it already exists. +// |directory| is the output value for the directory object. +DWORD CreateLowBoxObjectDirectory(PSID lowbox_sid, + bool open_directory, + base::win::ScopedHandle* directory); + +} // namespace sandbox + +#endif // SANDBOX_SRC_RESTRICTED_TOKEN_UTILS_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox.cc b/security/sandbox/chromium/sandbox/win/src/sandbox.cc new file mode 100644 index 0000000000..f65e379683 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox.cc @@ -0,0 +1,47 @@ +// Copyright (c) 2006-2008 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/win/src/sandbox.h" + +#include <windows.h> + +#include "sandbox/win/src/broker_services.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { +// The section for IPC and policy. +SANDBOX_INTERCEPT HANDLE g_shared_section; +static bool s_is_broker = false; + +// GetBrokerServices: the current implementation relies on a shared section +// that is created by the broker and opened by the target. +BrokerServices* SandboxFactory::GetBrokerServices() { + // Can't be the broker if the shared section is open. + if (g_shared_section) + return nullptr; + // If the shared section does not exist we are the broker, then create + // the broker object. + s_is_broker = true; + return BrokerServicesBase::GetInstance(); +} + +// GetTargetServices implementation must follow the same technique as the +// GetBrokerServices, but in this case the logic is the opposite. +TargetServices* SandboxFactory::GetTargetServices() { + // Can't be the target if the section handle is not valid. + if (!g_shared_section) + return nullptr; + // We are the target + s_is_broker = false; + // Creates and returns the target services implementation. + return TargetServicesBase::GetInstance(); +} + +} // namespace sandbox + +// Allows querying for whether the current process has been sandboxed. +extern "C" bool __declspec(dllexport) IsSandboxedProcess() { + return !!sandbox::g_shared_section; +} diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox.h b/security/sandbox/chromium/sandbox/win/src/sandbox.h new file mode 100644 index 0000000000..858c350558 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox.h @@ -0,0 +1,228 @@ +// 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. + +// Sandbox is a sandbox library for windows processes. Use when you want a +// 'privileged' process and a 'locked down process' to interact with. +// The privileged process is called the broker and it is started by external +// means (such as the user starting it). The 'sandboxed' process is called the +// target and it is started by the broker. There can be many target processes +// started by a single broker process. This library provides facilities +// for both the broker and the target. +// +// The design rationale and relevant documents can be found at http://go/sbox. +// +// Note: this header does not include the SandboxFactory definitions because +// there are cases where the Sandbox library is linked against the main .exe +// while its API needs to be used in a DLL. + +#ifndef SANDBOX_WIN_SRC_SANDBOX_H_ +#define SANDBOX_WIN_SRC_SANDBOX_H_ + +#if !defined(SANDBOX_FUZZ_TARGET) +#include <windows.h> +#else +#include "sandbox/win/fuzzer/fuzzer_types.h" +#endif + +#include <stddef.h> +#include <memory> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/sandbox_types.h" + +// sandbox: Google User-Land Application Sandbox +namespace sandbox { + +class BrokerServices; +class PolicyDiagnosticsReceiver; +class ProcessState; +class TargetPolicy; +class TargetServices; + +// BrokerServices exposes all the broker API. +// The basic use is to start the target(s) and wait for them to end. +// +// This API is intended to be called in the following order +// (error checking omitted): +// BrokerServices* broker = SandboxFactory::GetBrokerServices(); +// broker->Init(); +// PROCESS_INFORMATION target; +// broker->SpawnTarget(target_exe_path, target_args, &target); +// ::ResumeThread(target->hThread); +// // -- later you can call: +// broker->WaitForAllTargets(option); +// +class BrokerServices { + public: + // Initializes the broker. Must be called before any other on this class. + // returns ALL_OK if successful. All other return values imply failure. + // If the return is ERROR_GENERIC, you can call ::GetLastError() to get + // more information. + virtual ResultCode Init() = 0; + + // Returns the interface pointer to a new, empty policy object. Use this + // interface to specify the sandbox policy for new processes created by + // SpawnTarget() + virtual scoped_refptr<TargetPolicy> CreatePolicy() = 0; + + // Creates a new target (child process) in a suspended state. + // Parameters: + // exe_path: This is the full path to the target binary. This parameter + // can be null and in this case the exe path must be the first argument + // of the command_line. + // command_line: The arguments to be passed as command line to the new + // process. This can be null if the exe_path parameter is not null. + // policy: This is the pointer to the policy object for the sandbox to + // be created. + // last_warning: The argument will contain an indication on whether + // the process security was initialized completely, Only set if the + // process can be used without a serious compromise in security. + // last_error: If an error or warning is returned from this method this + // parameter will hold the last Win32 error value. + // target: returns the resulting target process information such as process + // handle and PID just as if CreateProcess() had been called. The caller is + // responsible for closing the handles returned in this structure. + // Returns: + // ALL_OK if successful. All other return values imply failure. + virtual ResultCode SpawnTarget(const wchar_t* exe_path, + const wchar_t* command_line, + base::EnvironmentMap& env_map, + scoped_refptr<TargetPolicy> policy, + ResultCode* last_warning, + DWORD* last_error, + PROCESS_INFORMATION* target) = 0; + + // This call blocks (waits) for all the targets to terminate. + // Returns: + // ALL_OK if successful. All other return values imply failure. + // If the return is ERROR_GENERIC, you can call ::GetLastError() to get + // more information. + virtual ResultCode WaitForAllTargets() = 0; + + // Adds an unsandboxed process as a peer for policy decisions (e.g. + // HANDLES_DUP_ANY policy). + // Returns: + // ALL_OK if successful. All other return values imply failure. + // If the return is ERROR_GENERIC, you can call ::GetLastError() to get + // more information. + virtual ResultCode AddTargetPeer(HANDLE peer_process) = 0; + + // This call creates a snapshot of policies managed by the sandbox and + // returns them via a helper class. + // Parameters: + // receiver: The |PolicyDiagnosticsReceiver| implementation will be + // called to accept the results of the call. + // Returns: + // ALL_OK if the request was dispatched. All other return values + // imply failure, and the responder will not receive its completion + // callback. + virtual ResultCode GetPolicyDiagnostics( + std::unique_ptr<PolicyDiagnosticsReceiver> receiver) = 0; + + // Derive a capability PSID from the given string. + virtual bool DeriveCapabilitySidFromName(const wchar_t* name, + PSID derived_sid, + DWORD sid_buffer_length) = 0; + + protected: + ~BrokerServices() {} +}; + +// TargetServices models the current process from the perspective +// of a target process. To obtain a pointer to it use +// Sandbox::GetTargetServices(). Note that this call returns a non-null +// pointer only if this process is in fact a target. A process is a target +// only if the process was spawned by a call to BrokerServices::SpawnTarget(). +// +// This API allows the target to gain access to resources with a high +// privilege token and then when it is ready to perform dangerous activities +// (such as download content from the web) it can lower its token and +// enter into locked-down (sandbox) mode. +// The typical usage is as follows: +// +// TargetServices* target_services = Sandbox::GetTargetServices(); +// if (target_services) { +// // We are the target. +// target_services->Init(); +// // Do work that requires high privileges here. +// // .... +// // When ready to enter lock-down mode call LowerToken: +// target_services->LowerToken(); +// } +// +// For more information see the BrokerServices API documentation. +class TargetServices { + public: + // Initializes the target. Must call this function before any other. + // returns ALL_OK if successful. All other return values imply failure. + // If the return is ERROR_GENERIC, you can call ::GetLastError() to get + // more information. + virtual ResultCode Init() = 0; + + // Discards the impersonation token and uses the lower token, call before + // processing any untrusted data or running third-party code. If this call + // fails the current process could be terminated immediately. + virtual void LowerToken() = 0; + + // Returns the ProcessState object. Through that object it's possible to have + // information about the current state of the process, such as whether + // LowerToken has been called or not. + virtual ProcessState* GetState() = 0; + + // Requests the broker to duplicate the supplied handle into the target + // process. The target process must be an active sandbox child process + // and the source process must have a corresponding policy allowing + // handle duplication for this object type. + // Returns: + // ALL_OK if successful. All other return values imply failure. + // If the return is ERROR_GENERIC, you can call ::GetLastError() to get + // more information. + virtual ResultCode DuplicateHandle(HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options) = 0; + + virtual ResultCode GetComplexLineBreaks(const WCHAR* text, uint32_t length, + uint8_t* break_before) = 0; + + protected: + ~TargetServices() {} +}; + +class PolicyInfo { + public: + // Returns a JSON representation of the policy snapshot. + // This pointer has the same lifetime as this PolicyInfo object. + virtual const char* JsonString() = 0; + virtual ~PolicyInfo() {} +}; + +// This is returned by BrokerServices::GetPolicyDiagnostics(). +// PolicyInfo entries need not be ordered. +class PolicyList { + public: + virtual std::vector<std::unique_ptr<PolicyInfo>>::iterator begin() = 0; + virtual std::vector<std::unique_ptr<PolicyInfo>>::iterator end() = 0; + virtual size_t size() const = 0; + virtual ~PolicyList() {} +}; + +// This class mediates calls to BrokerServices::GetPolicyDiagnostics(). +class PolicyDiagnosticsReceiver { + public: + // ReceiveDiagnostics() should return quickly and should not block the + // thread on which it is called. + virtual void ReceiveDiagnostics(std::unique_ptr<PolicyList> policies) = 0; + // OnError() is passed any errors encountered and |ReceiveDiagnostics| + // will not be called. + virtual void OnError(ResultCode code) = 0; + virtual ~PolicyDiagnosticsReceiver() {} +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SANDBOX_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox.vcproj b/security/sandbox/chromium/sandbox/win/src/sandbox.vcproj new file mode 100644 index 0000000000..229441cbd5 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox.vcproj @@ -0,0 +1,648 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="sandbox" + ProjectGUID="{881F6A97-D539-4C48-B401-DF04385B2343}" + RootNamespace="sandbox" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="4" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + ForcedIncludeFiles="stdafx.h" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="4" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="security" + > + <File + RelativePath=".\acl.cc" + > + </File> + <File + RelativePath=".\acl.h" + > + </File> + <File + RelativePath=".\dep.cc" + > + </File> + <File + RelativePath=".\dep.h" + > + </File> + <File + RelativePath=".\job.cc" + > + </File> + <File + RelativePath=".\job.h" + > + </File> + <File + RelativePath=".\restricted_token.cc" + > + </File> + <File + RelativePath=".\restricted_token.h" + > + </File> + <File + RelativePath=".\restricted_token_utils.cc" + > + </File> + <File + RelativePath=".\restricted_token_utils.h" + > + </File> + <File + RelativePath=".\security_level.h" + > + </File> + <File + RelativePath=".\sid.cc" + > + </File> + <File + RelativePath=".\sid.h" + > + </File> + <File + RelativePath=".\window.cc" + > + </File> + <File + RelativePath=".\window.h" + > + </File> + </Filter> + <Filter + Name="Interception" + > + <File + RelativePath=".\eat_resolver.cc" + > + </File> + <File + RelativePath=".\eat_resolver.h" + > + </File> + <File + RelativePath=".\interception.cc" + > + </File> + <File + RelativePath=".\interception.h" + > + </File> + <File + RelativePath=".\interception_agent.cc" + > + </File> + <File + RelativePath=".\interception_agent.h" + > + </File> + <File + RelativePath=".\interception_internal.h" + > + </File> + <File + RelativePath=".\pe_image.cc" + > + </File> + <File + RelativePath=".\pe_image.h" + > + </File> + <File + RelativePath=".\resolver.cc" + > + </File> + <File + RelativePath=".\resolver.h" + > + </File> + <File + RelativePath=".\service_resolver.cc" + > + </File> + <File + RelativePath=".\service_resolver.h" + > + </File> + <File + RelativePath=".\sidestep_resolver.cc" + > + </File> + <File + RelativePath=".\sidestep_resolver.h" + > + </File> + <File + RelativePath=".\target_interceptions.cc" + > + </File> + <File + RelativePath=".\target_interceptions.h" + > + </File> + <File + RelativePath=".\Wow64.cc" + > + </File> + <File + RelativePath=".\Wow64.h" + > + </File> + <Filter + Name="sidestep" + > + <File + RelativePath=".\sidestep\ia32_modrm_map.cpp" + > + </File> + <File + RelativePath=".\sidestep\ia32_opcode_map.cpp" + > + </File> + <File + RelativePath=".\sidestep\mini_disassembler.cpp" + > + </File> + <File + RelativePath=".\sidestep\mini_disassembler.h" + > + </File> + <File + RelativePath=".\sidestep\mini_disassembler_types.h" + > + </File> + <File + RelativePath=".\sidestep\preamble_patcher.h" + > + </File> + <File + RelativePath=".\sidestep\preamble_patcher_with_stub.cpp" + > + </File> + </Filter> + </Filter> + <Filter + Name="nt_level" + > + <File + RelativePath=".\nt_internals.h" + > + </File> + <File + RelativePath=".\policy_target.cc" + > + </File> + <File + RelativePath=".\policy_target.h" + > + </File> + <File + RelativePath=".\sandbox_nt_types.h" + > + </File> + <File + RelativePath=".\sandbox_nt_util.cc" + > + </File> + <File + RelativePath=".\sandbox_nt_util.h" + > + </File> + </Filter> + <Filter + Name="Policy_handlers" + > + <File + RelativePath=".\filesystem_dispatcher.cc" + > + </File> + <File + RelativePath=".\filesystem_dispatcher.h" + > + </File> + <File + RelativePath=".\filesystem_interception.cc" + > + </File> + <File + RelativePath=".\filesystem_interception.h" + > + </File> + <File + RelativePath=".\filesystem_policy.cc" + > + </File> + <File + RelativePath=".\filesystem_policy.h" + > + </File> + <File + RelativePath=".\named_pipe_dispatcher.cc" + > + </File> + <File + RelativePath=".\named_pipe_dispatcher.h" + > + </File> + <File + RelativePath=".\named_pipe_interception.cc" + > + </File> + <File + RelativePath=".\named_pipe_interception.h" + > + </File> + <File + RelativePath=".\named_pipe_policy.cc" + > + </File> + <File + RelativePath=".\named_pipe_policy.h" + > + </File> + <File + RelativePath=".\policy_params.h" + > + </File> + <File + RelativePath=".\process_thread_dispatcher.cc" + > + </File> + <File + RelativePath=".\process_thread_dispatcher.h" + > + </File> + <File + RelativePath=".\process_thread_interception.cc" + > + </File> + <File + RelativePath=".\process_thread_interception.h" + > + </File> + <File + RelativePath=".\process_thread_policy.cc" + > + </File> + <File + RelativePath=".\process_thread_policy.h" + > + </File> + <File + RelativePath=".\registry_dispatcher.cc" + > + </File> + <File + RelativePath=".\registry_dispatcher.h" + > + </File> + <File + RelativePath=".\registry_interception.cc" + > + </File> + <File + RelativePath=".\registry_interception.h" + > + </File> + <File + RelativePath=".\registry_policy.cc" + > + </File> + <File + RelativePath=".\registry_policy.h" + > + </File> + <File + RelativePath=".\sync_dispatcher.cc" + > + </File> + <File + RelativePath=".\sync_dispatcher.h" + > + </File> + <File + RelativePath=".\sync_interception.cc" + > + </File> + <File + RelativePath=".\sync_interception.h" + > + </File> + <File + RelativePath=".\sync_policy.cc" + > + </File> + <File + RelativePath=".\sync_policy.h" + > + </File> + </Filter> + <Filter + Name="IPC" + > + <File + RelativePath=".\crosscall_client.h" + > + </File> + <File + RelativePath=".\crosscall_params.h" + > + </File> + <File + RelativePath=".\crosscall_server.cc" + > + </File> + <File + RelativePath=".\crosscall_server.h" + > + </File> + <File + RelativePath=".\ipc_tags.h" + > + </File> + <File + RelativePath=".\sharedmem_ipc_client.cc" + > + </File> + <File + RelativePath=".\sharedmem_ipc_client.h" + > + </File> + <File + RelativePath=".\sharedmem_ipc_server.cc" + > + </File> + <File + RelativePath=".\sharedmem_ipc_server.h" + > + </File> + </Filter> + <Filter + Name="Policy_base" + > + <File + RelativePath=".\policy_engine_opcodes.cc" + > + </File> + <File + RelativePath=".\policy_engine_opcodes.h" + > + </File> + <File + RelativePath=".\policy_engine_params.h" + > + </File> + <File + RelativePath=".\policy_engine_processor.cc" + > + </File> + <File + RelativePath=".\policy_engine_processor.h" + > + </File> + <File + RelativePath=".\policy_low_level.cc" + > + </File> + <File + RelativePath=".\policy_low_level.h" + > + </File> + <File + RelativePath=".\sandbox_policy_base.cc" + > + </File> + <File + RelativePath=".\sandbox_policy_base.h" + > + </File> + </Filter> + <File + RelativePath=".\broker_services.cc" + > + </File> + <File + RelativePath=".\broker_services.h" + > + </File> + <File + RelativePath=".\internal_types.h" + > + </File> + <File + RelativePath=".\policy_broker.cc" + > + </File> + <File + RelativePath=".\policy_broker.h" + > + </File> + <File + RelativePath=".\sandbox.cc" + > + </File> + <File + RelativePath=".\sandbox.h" + > + </File> + <File + RelativePath=".\sandbox_factory.h" + > + </File> + <File + RelativePath=".\sandbox_policy.h" + > + </File> + <File + RelativePath=".\sandbox_types.h" + > + </File> + <File + RelativePath=".\sandbox_utils.cc" + > + </File> + <File + RelativePath=".\sandbox_utils.h" + > + </File> + <File + RelativePath=".\shared_handles.cc" + > + </File> + <File + RelativePath=".\shared_handles.h" + > + </File> + <File + RelativePath=".\stdafx.cc" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="1" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0" + /> + </FileConfiguration> + </File> + <File + RelativePath=".\stdafx.h" + > + </File> + <File + RelativePath=".\target_process.cc" + > + </File> + <File + RelativePath=".\target_process.h" + > + </File> + <File + RelativePath=".\target_services.cc" + > + </File> + <File + RelativePath=".\target_services.h" + > + </File> + <File + RelativePath=".\win2k_threadpool.cc" + > + </File> + <File + RelativePath=".\win2k_threadpool.h" + > + </File> + <File + RelativePath=".\win_utils.cc" + > + </File> + <File + RelativePath=".\win_utils.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_factory.h b/security/sandbox/chromium/sandbox/win/src/sandbox_factory.h new file mode 100644 index 0000000000..07402521dc --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_factory.h @@ -0,0 +1,52 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_SRC_SANDBOX_FACTORY_H__ +#define SANDBOX_SRC_SANDBOX_FACTORY_H__ + +#include "base/macros.h" +#include "sandbox/win/src/sandbox.h" + +// SandboxFactory is a set of static methods to get access to the broker +// or target services object. Only one of the two methods (GetBrokerServices, +// GetTargetServices) will return a non-null pointer and that should be used +// as the indication that the process is the broker or the target: +// +// BrokerServices* broker_services = SandboxFactory::GetBrokerServices(); +// if (broker_services) { +// //we are the broker, call broker api here +// broker_services->Init(); +// } else { +// TargetServices* target_services = SandboxFactory::GetTargetServices(); +// if (target_services) { +// //we are the target, call target api here +// target_services->Init(); +// } +// +// The methods in this class are expected to be called from a single thread +// +// The Sandbox library needs to be linked against the main executable, but +// sometimes the API calls are issued from a DLL that loads into the exe +// process. These factory methods then need to be called from the main +// exe and the interface pointers then can be safely passed to the DLL where +// the Sandbox API calls are made. +namespace sandbox { + +class SandboxFactory { + public: + // Returns the Broker API interface, returns nullptr if this process is the + // target. + static BrokerServices* GetBrokerServices(); + + // Returns the Target API interface, returns nullptr if this process is the + // broker. + static TargetServices* GetTargetServices(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(SandboxFactory); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SANDBOX_FACTORY_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_globals.cc b/security/sandbox/chromium/sandbox/win/src/sandbox_globals.cc new file mode 100644 index 0000000000..3387e3f604 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_globals.cc @@ -0,0 +1,18 @@ +// Copyright 2013 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 "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +// The section for IPC and policy. +SANDBOX_INTERCEPT HANDLE g_shared_section = nullptr; + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt = {}; + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_nt_types.h b/security/sandbox/chromium/sandbox/win/src/sandbox_nt_types.h new file mode 100644 index 0000000000..eff0f019d3 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_nt_types.h @@ -0,0 +1,47 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_SRC_SANDBOX_NT_TYPES_H__ +#define SANDBOX_SRC_SANDBOX_NT_TYPES_H__ + +#include "sandbox/win/src/nt_internals.h" + +namespace sandbox { + +struct NtExports { + NtAllocateVirtualMemoryFunction AllocateVirtualMemory; + NtCloseFunction Close; + NtDuplicateObjectFunction DuplicateObject; + NtFreeVirtualMemoryFunction FreeVirtualMemory; + NtMapViewOfSectionFunction MapViewOfSection; + NtProtectVirtualMemoryFunction ProtectVirtualMemory; + NtQueryInformationProcessFunction QueryInformationProcess; + NtQueryObjectFunction QueryObject; + NtQuerySectionFunction QuerySection; + NtQueryVirtualMemoryFunction QueryVirtualMemory; + NtUnmapViewOfSectionFunction UnmapViewOfSection; + NtSignalAndWaitForSingleObjectFunction SignalAndWaitForSingleObject; + NtWaitForSingleObjectFunction WaitForSingleObject; + RtlAllocateHeapFunction RtlAllocateHeap; + RtlAnsiStringToUnicodeStringFunction RtlAnsiStringToUnicodeString; + RtlCompareUnicodeStringFunction RtlCompareUnicodeString; + RtlCreateHeapFunction RtlCreateHeap; + RtlCreateUserThreadFunction RtlCreateUserThread; + RtlDestroyHeapFunction RtlDestroyHeap; + RtlFreeHeapFunction RtlFreeHeap; + _strnicmpFunction _strnicmp; + strlenFunction strlen; + wcslenFunction wcslen; + memcpyFunction memcpy; +}; + +// This is the value used for the ntdll level allocator. +enum AllocationType { + NT_ALLOC, + NT_PAGE +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SANDBOX_NT_TYPES_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_nt_util.cc b/security/sandbox/chromium/sandbox/win/src/sandbox_nt_util.cc new file mode 100644 index 0000000000..2641a13d04 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_nt_util.cc @@ -0,0 +1,755 @@ +// 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 "sandbox/win/src/sandbox_nt_util.h" + +#include <stddef.h> +#include <stdint.h> + +#include <string> + +#include "base/compiler_specific.h" +#include "base/win/pe_image.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt; + +} // namespace sandbox + +namespace { + +#if defined(_WIN64) +// Align a pointer to the next allocation granularity boundary. +inline char* AlignToBoundary(void* ptr, size_t increment) { + const size_t kAllocationGranularity = (64 * 1024) - 1; + uintptr_t ptr_int = reinterpret_cast<uintptr_t>(ptr); + uintptr_t ret_ptr = + (ptr_int + increment + kAllocationGranularity) & ~kAllocationGranularity; + // Check for overflow. + if (ret_ptr < ptr_int) + return nullptr; + return reinterpret_cast<char*>(ret_ptr); +} + +// Allocate a memory block somewhere within 2GiB of a specified base address. +// This is used for the DLL hooking code to get a valid trampoline location +// which must be within +/- 2GiB of the base. We only consider +2GiB for now. +void* AllocateNearTo(void* source, size_t size) { + using sandbox::g_nt; + // 2GiB, maximum upper bound the allocation address must be within. + const size_t kMaxSize = 0x80000000ULL; + // We don't support null as a base as this would just pick an arbitrary + // address when passed to NtAllocateVirtualMemory. + if (!source) + return nullptr; + // Ignore an allocation which is larger than the maximum. + if (size > kMaxSize) + return nullptr; + + // Ensure base address is aligned to the allocation granularity boundary. + char* base = AlignToBoundary(source, 0); + if (!base) + return nullptr; + // Set top address to be base + 2GiB. + const char* top_address = base + kMaxSize; + + while (base < top_address) { + // Avoid memset inserted by -ftrivial-auto-var-init=pattern. + STACK_UNINITIALIZED MEMORY_BASIC_INFORMATION mem_info; + NTSTATUS status = + g_nt.QueryVirtualMemory(NtCurrentProcess, base, MemoryBasicInformation, + &mem_info, sizeof(mem_info), nullptr); + if (!NT_SUCCESS(status)) + break; + + if ((mem_info.State == MEM_FREE) && (mem_info.RegionSize >= size)) { + // We've found a valid free block, try and allocate it for use. + // Note that we need to both commit and reserve the block for the + // allocation to succeed as per Windows virtual memory requirements. + void* ret_base = mem_info.BaseAddress; + status = + g_nt.AllocateVirtualMemory(NtCurrentProcess, &ret_base, 0, &size, + MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + // Shouldn't fail, but if it does we'll just continue and try next block. + if (NT_SUCCESS(status)) + return ret_base; + } + + // Update base past current allocation region. + base = AlignToBoundary(mem_info.BaseAddress, mem_info.RegionSize); + if (!base) + break; + } + return nullptr; +} +#else // defined(_WIN64). +void* AllocateNearTo(void* source, size_t size) { + using sandbox::g_nt; + + // In 32-bit processes allocations below 512k are predictable, so mark + // anything in that range as reserved and retry until we get a good address. + const void* const kMinAddress = reinterpret_cast<void*>(512 * 1024); + NTSTATUS ret; + SIZE_T actual_size; + void* base; + do { + base = nullptr; + actual_size = 64 * 1024; + ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, 0, &actual_size, + MEM_RESERVE, PAGE_NOACCESS); + if (!NT_SUCCESS(ret)) + return nullptr; + } while (base < kMinAddress); + + actual_size = size; + ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, 0, &actual_size, + MEM_COMMIT, PAGE_READWRITE); + if (!NT_SUCCESS(ret)) + return nullptr; + return base; +} +#endif // defined(_WIN64). + +} // namespace. + +namespace sandbox { + +// Handle for our private heap. +void* g_heap = nullptr; + +SANDBOX_INTERCEPT HANDLE g_shared_section; +SANDBOX_INTERCEPT size_t g_shared_IPC_size = 0; +SANDBOX_INTERCEPT size_t g_shared_policy_size = 0; + +void* volatile g_shared_policy_memory = nullptr; +void* volatile g_shared_IPC_memory = nullptr; + +// Both the IPC and the policy share a single region of memory in which the IPC +// memory is first and the policy memory is last. +bool MapGlobalMemory() { + if (!g_shared_IPC_memory) { + void* memory = nullptr; + SIZE_T size = 0; + // Map the entire shared section from the start. + NTSTATUS ret = + g_nt.MapViewOfSection(g_shared_section, NtCurrentProcess, &memory, 0, 0, + nullptr, &size, ViewUnmap, 0, PAGE_READWRITE); + + if (!NT_SUCCESS(ret) || !memory) { + NOTREACHED_NT(); + return false; + } + + if (_InterlockedCompareExchangePointer(&g_shared_IPC_memory, memory, + nullptr)) { + // Somebody beat us to the memory setup. + VERIFY_SUCCESS(g_nt.UnmapViewOfSection(NtCurrentProcess, memory)); + } + DCHECK_NT(g_shared_IPC_size > 0); + g_shared_policy_memory = + reinterpret_cast<char*>(g_shared_IPC_memory) + g_shared_IPC_size; + } + DCHECK_NT(g_shared_policy_memory); + DCHECK_NT(g_shared_policy_size > 0); + return true; +} + +void* GetGlobalIPCMemory() { + if (!MapGlobalMemory()) + return nullptr; + return g_shared_IPC_memory; +} + +void* GetGlobalPolicyMemory() { + if (!MapGlobalMemory()) + return nullptr; + return g_shared_policy_memory; +} + +bool InitHeap() { + if (!g_heap) { + // Create a new heap using default values for everything. + void* heap = + g_nt.RtlCreateHeap(HEAP_GROWABLE, nullptr, 0, 0, nullptr, nullptr); + if (!heap) + return false; + + if (_InterlockedCompareExchangePointer(&g_heap, heap, nullptr)) { + // Somebody beat us to the memory setup. + g_nt.RtlDestroyHeap(heap); + } + } + return !!g_heap; +} + +// Physically reads or writes from memory to verify that (at this time), it is +// valid. Returns a dummy value. +int TouchMemory(void* buffer, size_t size_bytes, RequiredAccess intent) { + const int kPageSize = 4096; + int dummy = 0; + volatile char* start = reinterpret_cast<char*>(buffer); + volatile char* end = start + size_bytes - 1; + + if (WRITE == intent) { + for (; start < end; start += kPageSize) { + *start = *start; + } + *end = *end; + } else { + for (; start < end; start += kPageSize) { + dummy += *start; + } + dummy += *end; + } + + return dummy; +} + +bool ValidParameter(void* buffer, size_t size, RequiredAccess intent) { + DCHECK_NT(size); + __try { + TouchMemory(buffer, size, intent); + } __except (EXCEPTION_EXECUTE_HANDLER) { + return false; + } + return true; +} + +NTSTATUS CopyData(void* destination, const void* source, size_t bytes) { + NTSTATUS ret = STATUS_SUCCESS; + __try { + g_nt.memcpy(destination, source, bytes); + } __except (EXCEPTION_EXECUTE_HANDLER) { + ret = GetExceptionCode(); + } + return ret; +} + +NTSTATUS AllocAndGetFullPath( + HANDLE root, + const wchar_t* path, + std::unique_ptr<wchar_t, NtAllocDeleter>* full_path) { + if (!InitHeap()) + return STATUS_NO_MEMORY; + + DCHECK_NT(full_path); + DCHECK_NT(path); + NTSTATUS ret = STATUS_UNSUCCESSFUL; + __try { + do { + static NtQueryObjectFunction NtQueryObject = nullptr; + if (!NtQueryObject) + ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject); + + ULONG size = 0; + // Query the name information a first time to get the size of the name. + ret = NtQueryObject(root, ObjectNameInformation, nullptr, 0, &size); + + std::unique_ptr<OBJECT_NAME_INFORMATION, NtAllocDeleter> handle_name; + if (size) { + handle_name.reset(reinterpret_cast<OBJECT_NAME_INFORMATION*>( + new (NT_ALLOC) BYTE[size])); + + // Query the name information a second time to get the name of the + // object referenced by the handle. + ret = NtQueryObject(root, ObjectNameInformation, handle_name.get(), + size, &size); + } + + if (STATUS_SUCCESS != ret) + break; + + // Space for path + '\' + name + '\0'. + size_t name_length = + handle_name->ObjectName.Length + (wcslen(path) + 2) * sizeof(wchar_t); + full_path->reset(new (NT_ALLOC) wchar_t[name_length / sizeof(wchar_t)]); + if (!*full_path) + break; + wchar_t* off = full_path->get(); + ret = CopyData(off, handle_name->ObjectName.Buffer, + handle_name->ObjectName.Length); + if (!NT_SUCCESS(ret)) + break; + off += handle_name->ObjectName.Length / sizeof(wchar_t); + *off = L'\\'; + off += 1; + ret = CopyData(off, path, wcslen(path) * sizeof(wchar_t)); + if (!NT_SUCCESS(ret)) + break; + off += wcslen(path); + *off = L'\0'; + } while (false); + } __except (EXCEPTION_EXECUTE_HANDLER) { + ret = GetExceptionCode(); + } + + if (!NT_SUCCESS(ret) && *full_path) + full_path->reset(nullptr); + + return ret; +} + +// Hacky code... replace with AllocAndCopyObjectAttributes. +NTSTATUS AllocAndCopyName(const OBJECT_ATTRIBUTES* in_object, + std::unique_ptr<wchar_t, NtAllocDeleter>* out_name, + uint32_t* attributes, + HANDLE* root) { + if (!InitHeap()) + return STATUS_NO_MEMORY; + + DCHECK_NT(out_name); + NTSTATUS ret = STATUS_UNSUCCESSFUL; + __try { + do { + if (in_object->RootDirectory != static_cast<HANDLE>(0) && !root) + break; + if (!in_object->ObjectName) + break; + if (!in_object->ObjectName->Buffer) + break; + + size_t size = in_object->ObjectName->Length + sizeof(wchar_t); + out_name->reset(new (NT_ALLOC) wchar_t[size / sizeof(wchar_t)]); + if (!*out_name) + break; + + ret = CopyData(out_name->get(), in_object->ObjectName->Buffer, + size - sizeof(wchar_t)); + if (!NT_SUCCESS(ret)) + break; + + out_name->get()[size / sizeof(wchar_t) - 1] = L'\0'; + + if (attributes) + *attributes = in_object->Attributes; + + if (root) + *root = in_object->RootDirectory; + ret = STATUS_SUCCESS; + } while (false); + } __except (EXCEPTION_EXECUTE_HANDLER) { + ret = GetExceptionCode(); + } + + if (!NT_SUCCESS(ret) && *out_name) + out_name->reset(nullptr); + + return ret; +} + +NTSTATUS GetProcessId(HANDLE process, DWORD* process_id) { + PROCESS_BASIC_INFORMATION proc_info; + ULONG bytes_returned; + + NTSTATUS ret = + g_nt.QueryInformationProcess(process, ProcessBasicInformation, &proc_info, + sizeof(proc_info), &bytes_returned); + if (!NT_SUCCESS(ret) || sizeof(proc_info) != bytes_returned) + return ret; + + *process_id = proc_info.UniqueProcessId; + return STATUS_SUCCESS; +} + +bool IsSameProcess(HANDLE process) { + if (NtCurrentProcess == process) + return true; + + static DWORD s_process_id = 0; + + if (!s_process_id) { + NTSTATUS ret = GetProcessId(NtCurrentProcess, &s_process_id); + if (!NT_SUCCESS(ret)) + return false; + } + + DWORD process_id; + NTSTATUS ret = GetProcessId(process, &process_id); + if (!NT_SUCCESS(ret)) + return false; + + return (process_id == s_process_id); +} + +bool IsValidImageSection(HANDLE section, + PVOID* base, + PLARGE_INTEGER offset, + PSIZE_T view_size) { + if (!section || !base || !view_size || offset) + return false; + + HANDLE query_section; + + NTSTATUS ret = + g_nt.DuplicateObject(NtCurrentProcess, section, NtCurrentProcess, + &query_section, SECTION_QUERY, 0, 0); + if (!NT_SUCCESS(ret)) + return false; + + SECTION_BASIC_INFORMATION basic_info; + SIZE_T bytes_returned; + ret = g_nt.QuerySection(query_section, SectionBasicInformation, &basic_info, + sizeof(basic_info), &bytes_returned); + + VERIFY_SUCCESS(g_nt.Close(query_section)); + + if (!NT_SUCCESS(ret) || sizeof(basic_info) != bytes_returned) + return false; + + if (!(basic_info.Attributes & SEC_IMAGE)) + return false; + + // Windows 10 2009+ may open PEs as SEC_IMAGE_NO_EXECUTE in non-dll-loading + // paths which looks identical to dll-loading unless we check if the section + // handle has execute rights. + // Avoid memset inserted by -ftrivial-auto-var-init=pattern. + STACK_UNINITIALIZED OBJECT_BASIC_INFORMATION obj_info; + ULONG obj_size_returned; + ret = g_nt.QueryObject(section, ObjectBasicInformation, &obj_info, + sizeof(obj_info), &obj_size_returned); + + if (!NT_SUCCESS(ret) || sizeof(obj_info) != obj_size_returned) + return false; + + if (!(obj_info.GrantedAccess & SECTION_MAP_EXECUTE)) + return false; + + return true; +} + +UNICODE_STRING* AnsiToUnicode(const char* string) { + ANSI_STRING ansi_string; + ansi_string.Length = static_cast<USHORT>(g_nt.strlen(string)); + ansi_string.MaximumLength = ansi_string.Length + 1; + ansi_string.Buffer = const_cast<char*>(string); + + if (ansi_string.Length > ansi_string.MaximumLength) + return nullptr; + + size_t name_bytes = + ansi_string.MaximumLength * sizeof(wchar_t) + sizeof(UNICODE_STRING); + + UNICODE_STRING* out_string = + reinterpret_cast<UNICODE_STRING*>(new (NT_ALLOC) char[name_bytes]); + if (!out_string) + return nullptr; + + out_string->MaximumLength = ansi_string.MaximumLength * sizeof(wchar_t); + out_string->Buffer = reinterpret_cast<wchar_t*>(&out_string[1]); + + BOOLEAN alloc_destination = false; + NTSTATUS ret = g_nt.RtlAnsiStringToUnicodeString(out_string, &ansi_string, + alloc_destination); + DCHECK_NT(STATUS_BUFFER_OVERFLOW != ret); + if (!NT_SUCCESS(ret)) { + operator delete(out_string, NT_ALLOC); + return nullptr; + } + + return out_string; +} + +UNICODE_STRING* GetImageInfoFromModule(HMODULE module, uint32_t* flags) { +// PEImage's dtor won't be run during SEH unwinding, but that's OK. +#pragma warning(push) +#pragma warning(disable : 4509) + UNICODE_STRING* out_name = nullptr; + __try { + do { + *flags = 0; + base::win::PEImage pe(module); + + if (!pe.VerifyMagic()) + break; + *flags |= MODULE_IS_PE_IMAGE; + + PIMAGE_EXPORT_DIRECTORY exports = pe.GetExportDirectory(); + if (exports) { + char* name = reinterpret_cast<char*>(pe.RVAToAddr(exports->Name)); + out_name = AnsiToUnicode(name); + } + + PIMAGE_NT_HEADERS headers = pe.GetNTHeaders(); + if (headers) { + if (headers->OptionalHeader.AddressOfEntryPoint) + *flags |= MODULE_HAS_ENTRY_POINT; + if (headers->OptionalHeader.SizeOfCode) + *flags |= MODULE_HAS_CODE; + } + } while (false); + } __except (EXCEPTION_EXECUTE_HANDLER) { + } + + return out_name; +#pragma warning(pop) +} + +const char* GetAnsiImageInfoFromModule(HMODULE module) { +// PEImage's dtor won't be run during SEH unwinding, but that's OK. +#pragma warning(push) +#pragma warning(disable : 4509) + const char* out_name = nullptr; + __try { + do { + base::win::PEImage pe(module); + + if (!pe.VerifyMagic()) + break; + + PIMAGE_EXPORT_DIRECTORY exports = pe.GetExportDirectory(); + if (exports) + out_name = static_cast<const char*>(pe.RVAToAddr(exports->Name)); + } while (false); + } __except (EXCEPTION_EXECUTE_HANDLER) { + } + + return out_name; +#pragma warning(pop) +} + +UNICODE_STRING* GetBackingFilePath(PVOID address) { + // We'll start with something close to max_path charactes for the name. + SIZE_T buffer_bytes = MAX_PATH * 2; + + for (;;) { + MEMORY_SECTION_NAME* section_name = reinterpret_cast<MEMORY_SECTION_NAME*>( + new (NT_ALLOC) char[buffer_bytes]); + + if (!section_name) + return nullptr; + + SIZE_T returned_bytes; + NTSTATUS ret = + g_nt.QueryVirtualMemory(NtCurrentProcess, address, MemorySectionName, + section_name, buffer_bytes, &returned_bytes); + + if (STATUS_BUFFER_OVERFLOW == ret) { + // Retry the call with the given buffer size. + operator delete(section_name, NT_ALLOC); + section_name = nullptr; + buffer_bytes = returned_bytes; + continue; + } + if (!NT_SUCCESS(ret)) { + operator delete(section_name, NT_ALLOC); + return nullptr; + } + + return reinterpret_cast<UNICODE_STRING*>(section_name); + } +} + +UNICODE_STRING* ExtractModuleName(const UNICODE_STRING* module_path) { + if ((!module_path) || (!module_path->Buffer)) + return nullptr; + + wchar_t* sep = nullptr; + int start_pos = module_path->Length / sizeof(wchar_t) - 1; + int ix = start_pos; + + for (; ix >= 0; --ix) { + if (module_path->Buffer[ix] == L'\\') { + sep = &module_path->Buffer[ix]; + break; + } + } + + // Ends with path separator. Not a valid module name. + if ((ix == start_pos) && sep) + return nullptr; + + // No path separator found. Use the entire name. + if (!sep) { + sep = &module_path->Buffer[-1]; + } + + // Add one to the size so we can null terminate the string. + size_t size_bytes = (start_pos - ix + 1) * sizeof(wchar_t); + + // Based on the code above, size_bytes should always be small enough + // to make the static_cast below safe. + DCHECK_NT(UINT16_MAX > size_bytes); + char* str_buffer = new (NT_ALLOC) char[size_bytes + sizeof(UNICODE_STRING)]; + if (!str_buffer) + return nullptr; + + UNICODE_STRING* out_string = reinterpret_cast<UNICODE_STRING*>(str_buffer); + out_string->Buffer = reinterpret_cast<wchar_t*>(&out_string[1]); + out_string->Length = static_cast<USHORT>(size_bytes - sizeof(wchar_t)); + out_string->MaximumLength = static_cast<USHORT>(size_bytes); + + NTSTATUS ret = CopyData(out_string->Buffer, &sep[1], out_string->Length); + if (!NT_SUCCESS(ret)) { + operator delete(out_string, NT_ALLOC); + return nullptr; + } + + out_string->Buffer[out_string->Length / sizeof(wchar_t)] = L'\0'; + return out_string; +} + +NTSTATUS AutoProtectMemory::ChangeProtection(void* address, + size_t bytes, + ULONG protect) { + DCHECK_NT(!changed_); + SIZE_T new_bytes = bytes; + NTSTATUS ret = g_nt.ProtectVirtualMemory(NtCurrentProcess, &address, + &new_bytes, protect, &old_protect_); + if (NT_SUCCESS(ret)) { + changed_ = true; + address_ = address; + bytes_ = new_bytes; + } + + return ret; +} + +NTSTATUS AutoProtectMemory::RevertProtection() { + if (!changed_) + return STATUS_SUCCESS; + + DCHECK_NT(address_); + DCHECK_NT(bytes_); + + SIZE_T new_bytes = bytes_; + NTSTATUS ret = g_nt.ProtectVirtualMemory( + NtCurrentProcess, &address_, &new_bytes, old_protect_, &old_protect_); + DCHECK_NT(NT_SUCCESS(ret)); + + changed_ = false; + address_ = nullptr; + bytes_ = 0; + old_protect_ = 0; + + return ret; +} + +bool IsSupportedRenameCall(FILE_RENAME_INFORMATION* file_info, + DWORD length, + uint32_t file_info_class) { + if (FileRenameInformation != file_info_class) + return false; + + if (length < sizeof(FILE_RENAME_INFORMATION)) + return false; + + // Make sure file name length doesn't exceed the message length + if (length - offsetof(FILE_RENAME_INFORMATION, FileName) < + file_info->FileNameLength) + return false; + + // We don't support a root directory. + if (file_info->RootDirectory) + return false; + + static const wchar_t kPathPrefix[] = {L'\\', L'?', L'?', L'\\'}; + + // Check if it starts with \\??\\. We don't support relative paths. + if (file_info->FileNameLength < sizeof(kPathPrefix) || + file_info->FileNameLength > UINT16_MAX) + return false; + + if (file_info->FileName[0] != kPathPrefix[0] || + file_info->FileName[1] != kPathPrefix[1] || + file_info->FileName[2] != kPathPrefix[2] || + file_info->FileName[3] != kPathPrefix[3]) + return false; + + return true; +} + +bool NtGetPathFromHandle(HANDLE handle, + std::unique_ptr<wchar_t, NtAllocDeleter>* path) { + OBJECT_NAME_INFORMATION initial_buffer; + OBJECT_NAME_INFORMATION* name; + ULONG size = 0; + // Query the name information a first time to get the size of the name. + NTSTATUS status = g_nt.QueryObject(handle, ObjectNameInformation, + &initial_buffer, size, &size); + + if (!NT_SUCCESS(status) && status != STATUS_INFO_LENGTH_MISMATCH) + return false; + + std::unique_ptr<BYTE[], NtAllocDeleter> name_ptr; + if (!size) + return false; + name_ptr.reset(new (NT_ALLOC) BYTE[size]); + name = reinterpret_cast<OBJECT_NAME_INFORMATION*>(name_ptr.get()); + + // Query the name information a second time to get the name of the + // object referenced by the handle. + status = g_nt.QueryObject(handle, ObjectNameInformation, name, size, &size); + + if (STATUS_SUCCESS != status) + return false; + size_t num_path_wchars = (name->ObjectName.Length / sizeof(wchar_t)) + 1; + path->reset(new (NT_ALLOC) wchar_t[num_path_wchars]); + status = + CopyData(path->get(), name->ObjectName.Buffer, name->ObjectName.Length); + path->get()[num_path_wchars - 1] = L'\0'; + if (STATUS_SUCCESS != status) + return false; + return true; +} + +} // namespace sandbox + +void* operator new(size_t size, sandbox::AllocationType type, void* near_to) { + void* result = nullptr; + if (type == sandbox::NT_ALLOC) { + if (sandbox::InitHeap()) { + // Use default flags for the allocation. + result = sandbox::g_nt.RtlAllocateHeap(sandbox::g_heap, 0, size); + } + } else if (type == sandbox::NT_PAGE) { + result = AllocateNearTo(near_to, size); + } else { + NOTREACHED_NT(); + } + + // TODO: Returning nullptr from operator new has undefined behavior, but + // the Allocate() functions called above can return nullptr. Consider checking + // for nullptr here and crashing or throwing. + + return result; +} + +void* operator new [](size_t size, sandbox::AllocationType type, + void* near_to) { + return operator new(size, type, near_to); +} + +void operator delete(void* memory, sandbox::AllocationType type) { + if (type == sandbox::NT_ALLOC) { + // Use default flags. + VERIFY(sandbox::g_nt.RtlFreeHeap(sandbox::g_heap, 0, memory)); + } else if (type == sandbox::NT_PAGE) { + void* base = memory; + SIZE_T size = 0; + VERIFY_SUCCESS(sandbox::g_nt.FreeVirtualMemory(NtCurrentProcess, &base, + &size, MEM_RELEASE)); + } else { + NOTREACHED_NT(); + } +} + +void operator delete(void* memory, + sandbox::AllocationType type, + void* near_to) { + operator delete(memory, type); +} + +void* __cdecl operator new(size_t size, + void* buffer, + sandbox::AllocationType type) { + return buffer; +} + +void __cdecl operator delete(void* memory, + void* buffer, + sandbox::AllocationType type) {} diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_nt_util.h b/security/sandbox/chromium/sandbox/win/src/sandbox_nt_util.h new file mode 100644 index 0000000000..70d011dfb4 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_nt_util.h @@ -0,0 +1,220 @@ +// Copyright (c) 2010 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. + +#ifndef SANDBOX_SRC_SANDBOX_NT_UTIL_H_ +#define SANDBOX_SRC_SANDBOX_NT_UTIL_H_ + +#include <intrin.h> +#include <stddef.h> +#include <stdint.h> +#include <memory> + +#include "base/macros.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_nt_types.h" + +// Placement new and delete to be used from ntdll interception code. +void* __cdecl operator new(size_t size, + sandbox::AllocationType type, + void* near_to = nullptr); +void* __cdecl operator new[](size_t size, + sandbox::AllocationType type, + void* near_to = nullptr); +void __cdecl operator delete(void* memory, sandbox::AllocationType type); +// Add operator delete that matches the placement form of the operator new +// above. This is required by compiler to generate code to call operator delete +// in case the object's constructor throws an exception. +// See http://msdn.microsoft.com/en-us/library/cxdxz3x6.aspx +void __cdecl operator delete(void* memory, + sandbox::AllocationType type, + void* near_to); + +// Regular placement new and delete +void* __cdecl operator new(size_t size, + void* buffer, + sandbox::AllocationType type); +void __cdecl operator delete(void* memory, + void* buffer, + sandbox::AllocationType type); + +// DCHECK_NT is defined to be pretty much an assert at this time because we +// don't have logging from the ntdll layer on the child. +// +// VERIFY_NT and VERIFY_SUCCESS are the standard asserts on debug, but +// execute the actual argument on release builds. VERIFY_NT expects an action +// returning a bool, while VERIFY_SUCCESS expects an action returning +// NTSTATUS. +#ifndef NDEBUG +#define DCHECK_NT(condition) \ + { (condition) ? (void)0 : __debugbreak(); } +#define VERIFY(action) DCHECK_NT(action) +#define VERIFY_SUCCESS(action) DCHECK_NT(NT_SUCCESS(action)) +#else +#define DCHECK_NT(condition) +#define VERIFY(action) (action) +#define VERIFY_SUCCESS(action) (action) +#endif + +#define CHECK_NT(condition) \ + { (condition) ? (void)0 : __debugbreak(); } + +#define NOTREACHED_NT() DCHECK_NT(false) + +namespace sandbox { + +#if defined(_M_X64) || defined(_M_ARM64) +#pragma intrinsic(_InterlockedCompareExchange) +#pragma intrinsic(_InterlockedCompareExchangePointer) + +#elif defined(_M_IX86) +extern "C" long _InterlockedCompareExchange(long volatile* destination, + long exchange, + long comperand); + +#pragma intrinsic(_InterlockedCompareExchange) + +// We want to make sure that we use an intrinsic version of the function, not +// the one provided by kernel32. +__forceinline void* _InterlockedCompareExchangePointer( + void* volatile* destination, + void* exchange, + void* comperand) { + long ret = _InterlockedCompareExchange( + reinterpret_cast<long volatile*>(destination), + static_cast<long>(reinterpret_cast<size_t>(exchange)), + static_cast<long>(reinterpret_cast<size_t>(comperand))); + + return reinterpret_cast<void*>(static_cast<size_t>(ret)); +} + +#else +#error Architecture not supported. + +#endif + +struct NtAllocDeleter { + inline void operator()(void* ptr) const { + operator delete(ptr, AllocationType::NT_ALLOC); + } +}; + +// Returns a pointer to the IPC shared memory. +void* GetGlobalIPCMemory(); + +// Returns a pointer to the Policy shared memory. +void* GetGlobalPolicyMemory(); + +enum RequiredAccess { READ, WRITE }; + +// Performs basic user mode buffer validation. In any case, buffers access must +// be protected by SEH. intent specifies if the buffer should be tested for read +// or write. +bool ValidParameter(void* buffer, size_t size, RequiredAccess intent); + +// Copies data from a user buffer to our buffer. Returns the operation status. +NTSTATUS CopyData(void* destination, const void* source, size_t bytes); + +// Copies the name from an object attributes. +NTSTATUS AllocAndCopyName(const OBJECT_ATTRIBUTES* in_object, + std::unique_ptr<wchar_t, NtAllocDeleter>* out_name, + uint32_t* attributes, + HANDLE* root); + +// Determine full path name from object root and path. +NTSTATUS AllocAndGetFullPath( + HANDLE root, + const wchar_t* path, + std::unique_ptr<wchar_t, NtAllocDeleter>* full_path); + +// Initializes our ntdll level heap +bool InitHeap(); + +// Returns true if the provided handle refers to the current process. +bool IsSameProcess(HANDLE process); + +enum MappedModuleFlags { + MODULE_IS_PE_IMAGE = 1, // Module is an executable. + MODULE_HAS_ENTRY_POINT = 2, // Execution entry point found. + MODULE_HAS_CODE = 4 // Non zero size of executable sections. +}; + +// Returns the name and characteristics for a given PE module. The return +// value is the name as defined by the export table and the flags is any +// combination of the MappedModuleFlags enumeration. +// +// The returned buffer must be freed with a placement delete from the ntdll +// level allocator: +// +// UNICODE_STRING* name = GetPEImageInfoFromModule(HMODULE module, &flags); +// if (!name) { +// // probably not a valid dll +// return; +// } +// InsertYourLogicHere(name); +// operator delete(name, NT_ALLOC); +UNICODE_STRING* GetImageInfoFromModule(HMODULE module, uint32_t* flags); + +// Returns the name and characteristics for a given PE module. The return +// value is the name as defined by the export table. +// +// The returned buffer is within the PE module and must not be freed. +const char* GetAnsiImageInfoFromModule(HMODULE module); + +// Returns the full path and filename for a given dll. +// May return nullptr if the provided address is not backed by a named section, +// or if the current OS version doesn't support the call. The returned buffer +// must be freed with a placement delete (see GetImageNameFromModule example). +UNICODE_STRING* GetBackingFilePath(PVOID address); + +// Returns the last component of a path that contains the module name. +// It will return nullptr if the path ends with the path separator. The returned +// buffer must be freed with a placement delete (see GetImageNameFromModule +// example). +UNICODE_STRING* ExtractModuleName(const UNICODE_STRING* module_path); + +// Returns true if the parameters correspond to a dll mapped as code. +bool IsValidImageSection(HANDLE section, + PVOID* base, + PLARGE_INTEGER offset, + PSIZE_T view_size); + +// Converts an ansi string to an UNICODE_STRING. +UNICODE_STRING* AnsiToUnicode(const char* string); + +// Resolves a handle to an nt path. Returns true if the handle can be resolved. +bool NtGetPathFromHandle(HANDLE handle, + std::unique_ptr<wchar_t, NtAllocDeleter>* path); + +// Provides a simple way to temporarily change the protection of a memory page. +class AutoProtectMemory { + public: + AutoProtectMemory() + : changed_(false), address_(nullptr), bytes_(0), old_protect_(0) {} + + ~AutoProtectMemory() { RevertProtection(); } + + // Sets the desired protection of a given memory range. + NTSTATUS ChangeProtection(void* address, size_t bytes, ULONG protect); + + // Restores the original page protection. + NTSTATUS RevertProtection(); + + private: + bool changed_; + void* address_; + size_t bytes_; + ULONG old_protect_; + + DISALLOW_COPY_AND_ASSIGN(AutoProtectMemory); +}; + +// Returns true if the file_rename_information structure is supported by our +// rename handler. +bool IsSupportedRenameCall(FILE_RENAME_INFORMATION* file_info, + DWORD length, + uint32_t file_info_class); + +} // namespace sandbox + +#endif // SANDBOX_SRC_SANDBOX_NT_UTIL_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_policy.h b/security/sandbox/chromium/sandbox/win/src/sandbox_policy.h new file mode 100644 index 0000000000..75514ef595 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_policy.h @@ -0,0 +1,296 @@ +// 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. + +#ifndef SANDBOX_WIN_SRC_SANDBOX_POLICY_H_ +#define SANDBOX_WIN_SRC_SANDBOX_POLICY_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <string> + +#include "base/memory/scoped_refptr.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/security_level.h" + +namespace sandbox { + +class AppContainerProfile; + +class TargetPolicy { + public: + // Windows subsystems that can have specific rules. + // Note: The process subsystem(SUBSYS_PROCESS) does not evaluate the request + // exactly like the CreateProcess API does. See the comment at the top of + // process_thread_dispatcher.cc for more details. + enum SubSystem { + SUBSYS_FILES, // Creation and opening of files and pipes. + SUBSYS_NAMED_PIPES, // Creation of named pipes. + SUBSYS_PROCESS, // Creation of child processes. + SUBSYS_REGISTRY, // Creation and opening of registry keys. + SUBSYS_SYNC, // Creation of named sync objects. + SUBSYS_HANDLES, // Duplication of handles to other processes. + SUBSYS_WIN32K_LOCKDOWN, // Win32K Lockdown related policy. + SUBSYS_SIGNED_BINARY, // Signed binary policy. + SUBSYS_LINE_BREAK // Complex line break policy. + }; + + // Allowable semantics when a rule is matched. + enum Semantics { + FILES_ALLOW_ANY, // Allows open or create for any kind of access that + // the file system supports. + FILES_ALLOW_READONLY, // Allows open or create with read access only. + FILES_ALLOW_QUERY, // Allows access to query the attributes of a file. + FILES_ALLOW_DIR_ANY, // Allows open or create with directory semantics + // only. + HANDLES_DUP_ANY, // Allows duplicating handles opened with any + // access permissions. + HANDLES_DUP_BROKER, // Allows duplicating handles to the broker process. + NAMEDPIPES_ALLOW_ANY, // Allows creation of a named pipe. + PROCESS_MIN_EXEC, // Allows to create a process with minimal rights + // over the resulting process and thread handles. + // No other parameters besides the command line are + // passed to the child process. + PROCESS_ALL_EXEC, // Allows the creation of a process and return full + // access on the returned handles. + // This flag can be used only when the main token of + // the sandboxed application is at least INTERACTIVE. + EVENTS_ALLOW_ANY, // Allows the creation of an event with full access. + EVENTS_ALLOW_READONLY, // Allows opening an even with synchronize access. + REG_ALLOW_READONLY, // Allows readonly access to a registry key. + REG_ALLOW_ANY, // Allows read and write access to a registry key. + FAKE_USER_GDI_INIT, // Fakes user32 and gdi32 initialization. This can + // be used to allow the DLLs to load and initialize + // even if the process cannot access that subsystem. + IMPLEMENT_OPM_APIS, // Implements FAKE_USER_GDI_INIT and also exposes + // IPC calls to handle Output Protection Manager + // APIs. + SIGNED_ALLOW_LOAD, // Allows loading the module when CIG is enabled. + LINE_BREAK_ALLOW // Allow complex line break brokering. + }; + + // Increments the reference count of this object. The reference count must + // be incremented if this interface is given to another component. + virtual void AddRef() = 0; + + // Decrements the reference count of this object. When the reference count + // is zero the object is automatically destroyed. + // Indicates that the caller is done with this interface. After calling + // release no other method should be called. + virtual void Release() = 0; + + // Sets the security level for the target process' two tokens. + // This setting is permanent and cannot be changed once the target process is + // spawned. + // initial: the security level for the initial token. This is the token that + // is used by the process from the creation of the process until the moment + // the process calls TargetServices::LowerToken() or the process calls + // win32's RevertToSelf(). Once this happens the initial token is no longer + // available and the lockdown token is in effect. Using an initial token is + // not compatible with AppContainer, see SetAppContainer. + // lockdown: the security level for the token that comes into force after the + // process calls TargetServices::LowerToken() or the process calls + // RevertToSelf(). See the explanation of each level in the TokenLevel + // definition. + // Return value: SBOX_ALL_OK if the setting succeeds and false otherwise. + // Returns false if the lockdown value is more permissive than the initial + // value. + // + // Important: most of the sandbox-provided security relies on this single + // setting. The caller should strive to set the lockdown level as restricted + // as possible. + virtual ResultCode SetTokenLevel(TokenLevel initial, TokenLevel lockdown) = 0; + + // Returns the initial token level. + virtual TokenLevel GetInitialTokenLevel() const = 0; + + // Returns the lockdown token level. + virtual TokenLevel GetLockdownTokenLevel() const = 0; + + // Sets that we should not use restricting SIDs in the access tokens. We need + // to do this in some circumstances even though it weakens the sandbox. + // The default is to use them. + virtual void SetDoNotUseRestrictingSIDs() = 0; + + // Sets the security level of the Job Object to which the target process will + // belong. This setting is permanent and cannot be changed once the target + // process is spawned. The job controls the global security settings which + // can not be specified in the token security profile. + // job_level: the security level for the job. See the explanation of each + // level in the JobLevel definition. + // ui_exceptions: specify what specific rights that are disabled in the + // chosen job_level that need to be granted. Use this parameter to avoid + // selecting the next permissive job level unless you need all the rights + // that are granted in such level. + // The exceptions can be specified as a combination of the following + // constants: + // JOB_OBJECT_UILIMIT_HANDLES : grant access to all user-mode handles. These + // include windows, icons, menus and various GDI objects. In addition the + // target process can set hooks, and broadcast messages to other processes + // that belong to the same desktop. + // JOB_OBJECT_UILIMIT_READCLIPBOARD : grant read-only access to the clipboard. + // JOB_OBJECT_UILIMIT_WRITECLIPBOARD : grant write access to the clipboard. + // JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS : allow changes to the system-wide + // parameters as defined by the Win32 call SystemParametersInfo(). + // JOB_OBJECT_UILIMIT_DISPLAYSETTINGS : allow programmatic changes to the + // display settings. + // JOB_OBJECT_UILIMIT_GLOBALATOMS : allow access to the global atoms table. + // JOB_OBJECT_UILIMIT_DESKTOP : allow the creation of new desktops. + // JOB_OBJECT_UILIMIT_EXITWINDOWS : allow the call to ExitWindows(). + // + // Return value: SBOX_ALL_OK if the setting succeeds and false otherwise. + // + // Note: JOB_OBJECT_XXXX constants are defined in winnt.h and documented at + // length in: + // http://msdn2.microsoft.com/en-us/library/ms684152.aspx + // + // Note: the recommended level is JOB_RESTRICTED or JOB_LOCKDOWN. + virtual ResultCode SetJobLevel(JobLevel job_level, + uint32_t ui_exceptions) = 0; + + // Returns the job level. + virtual JobLevel GetJobLevel() const = 0; + + // Sets a hard limit on the size of the commit set for the sandboxed process. + // If the limit is reached, the process will be terminated with + // SBOX_FATAL_MEMORY_EXCEEDED (7012). + virtual ResultCode SetJobMemoryLimit(size_t memory_limit) = 0; + + // Specifies the desktop on which the application is going to run. If the + // desktop does not exist, it will be created. If alternate_winstation is + // set to true, the desktop will be created on an alternate window station. + virtual ResultCode SetAlternateDesktop(bool alternate_winstation) = 0; + + // Returns the name of the alternate desktop used. If an alternate window + // station is specified, the name is prepended by the window station name, + // followed by a backslash. + virtual std::wstring GetAlternateDesktop() const = 0; + + // Precreates the desktop and window station, if any. + virtual ResultCode CreateAlternateDesktop(bool alternate_winstation) = 0; + + // Destroys the desktop and windows station. + virtual void DestroyAlternateDesktop() = 0; + + // Sets the integrity level of the process in the sandbox. Both the initial + // token and the main token will be affected by this. If the integrity level + // is set to a level higher than the current level, the sandbox will fail + // to start. + virtual ResultCode SetIntegrityLevel(IntegrityLevel level) = 0; + + // Returns the initial integrity level used. + virtual IntegrityLevel GetIntegrityLevel() const = 0; + + // Sets the integrity level of the process in the sandbox. The integrity level + // will not take effect before you call LowerToken. User Interface Privilege + // Isolation is not affected by this setting and will remain off for the + // process in the sandbox. If the integrity level is set to a level higher + // than the current level, the sandbox will fail to start. + virtual ResultCode SetDelayedIntegrityLevel(IntegrityLevel level) = 0; + + // Sets the LowBox token for sandboxed process. This is mutually exclusive + // with SetAppContainer method. + virtual ResultCode SetLowBox(const wchar_t* sid) = 0; + + // Sets the mitigations enabled when the process is created. Most of these + // are implemented as attributes passed via STARTUPINFOEX. So they take + // effect before any thread in the target executes. The declaration of + // MitigationFlags is followed by a detailed description of each flag. + virtual ResultCode SetProcessMitigations(MitigationFlags flags) = 0; + + // Returns the currently set mitigation flags. + virtual MitigationFlags GetProcessMitigations() = 0; + + // Sets process mitigation flags that don't take effect before the call to + // LowerToken(). + virtual ResultCode SetDelayedProcessMitigations(MitigationFlags flags) = 0; + + // Returns the currently set delayed mitigation flags. + virtual MitigationFlags GetDelayedProcessMitigations() const = 0; + + // Disconnect the target from CSRSS when TargetServices::LowerToken() is + // called inside the target. + virtual ResultCode SetDisconnectCsrss() = 0; + + // Sets the interceptions to operate in strict mode. By default, interceptions + // are performed in "relaxed" mode, where if something inside NTDLL.DLL is + // already patched we attempt to intercept it anyway. Setting interceptions + // to strict mode means that when we detect that the function is patched we'll + // refuse to perform the interception. + virtual void SetStrictInterceptions() = 0; + + // Set the handles the target process should inherit for stdout and + // stderr. The handles the caller passes must remain valid for the + // lifetime of the policy object. This only has an effect on + // Windows Vista and later versions. These methods accept pipe and + // file handles, but not console handles. + virtual ResultCode SetStdoutHandle(HANDLE handle) = 0; + virtual ResultCode SetStderrHandle(HANDLE handle) = 0; + + // Adds a policy rule effective for processes spawned using this policy. + // subsystem: One of the above enumerated windows subsystems. + // semantics: One of the above enumerated FileSemantics. + // pattern: A specific full path or a full path with wildcard patterns. + // The valid wildcards are: + // '*' : Matches zero or more character. Only one in series allowed. + // '?' : Matches a single character. One or more in series are allowed. + // Examples: + // "c:\\documents and settings\\vince\\*.dmp" + // "c:\\documents and settings\\*\\crashdumps\\*.dmp" + // "c:\\temp\\app_log_?????_chrome.txt" + virtual ResultCode AddRule(SubSystem subsystem, + Semantics semantics, + const wchar_t* pattern) = 0; + + // Adds a dll that will be unloaded in the target process before it gets + // a chance to initialize itself. Typically, dlls that cause the target + // to crash go here. + virtual ResultCode AddDllToUnload(const wchar_t* dll_name) = 0; + + // Adds a handle that will be closed in the target process after lockdown. + // A nullptr value for handle_name indicates all handles of the specified + // type. An empty string for handle_name indicates the handle is unnamed. + virtual ResultCode AddKernelObjectToClose(const wchar_t* handle_type, + const wchar_t* handle_name) = 0; + + // Adds a handle that will be shared with the target process. Does not take + // ownership of the handle. + virtual void AddHandleToShare(HANDLE handle) = 0; + + // Locks down the default DACL of the created lockdown and initial tokens + // to restrict what other processes are allowed to access a process' kernel + // resources. + virtual void SetLockdownDefaultDacl() = 0; + + // Adds a restricting random SID to the restricted SIDs list as well as + // the default DACL. + virtual void AddRestrictingRandomSid() = 0; + + // Enable OPM API redirection when in Win32k lockdown. + virtual void SetEnableOPMRedirection() = 0; + // Enable OPM API emulation when in Win32k lockdown. + virtual bool GetEnableOPMRedirection() = 0; + + // Configure policy to use an AppContainer profile. |package_name| is the + // name of the profile to use. Specifying True for |create_profile| ensures + // the profile exists, if set to False process creation will fail if the + // profile has not already been created. + virtual ResultCode AddAppContainerProfile(const wchar_t* package_name, + bool create_profile) = 0; + + // Get the configured AppContainerProfile. + virtual scoped_refptr<AppContainerProfile> GetAppContainerProfile() = 0; + + // Set effective token that will be used for creating the initial and + // lockdown tokens. The token the caller passes must remain valid for the + // lifetime of the policy object. + virtual void SetEffectiveToken(HANDLE token) = 0; + + protected: + ~TargetPolicy() {} +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SANDBOX_POLICY_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_policy_base.cc b/security/sandbox/chromium/sandbox/win/src/sandbox_policy_base.cc new file mode 100644 index 0000000000..f228dbbc31 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_policy_base.cc @@ -0,0 +1,832 @@ +// 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 "sandbox/win/src/sandbox_policy_base.h" + +#include <sddl.h> +#include <stddef.h> +#include <stdint.h> + +#include "base/callback.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/stl_util.h" +#include "base/strings/stringprintf.h" +#include "base/win/win_util.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/acl.h" +#include "sandbox/win/src/filesystem_policy.h" +#include "sandbox/win/src/handle_policy.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/job.h" +#include "sandbox/win/src/line_break_policy.h" +#include "sandbox/win/src/named_pipe_policy.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_engine_processor.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/process_mitigations.h" +#include "sandbox/win/src/process_mitigations_win32k_policy.h" +#include "sandbox/win/src/process_thread_policy.h" +#include "sandbox/win/src/registry_policy.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/security_capabilities.h" +#include "sandbox/win/src/signed_policy.h" +#include "sandbox/win/src/sync_policy.h" +#include "sandbox/win/src/target_process.h" +#include "sandbox/win/src/top_level_dispatcher.h" +#include "sandbox/win/src/window.h" + +namespace { + +// The standard windows size for one memory page. +constexpr size_t kOneMemPage = 4096; +// The IPC and Policy shared memory sizes. +constexpr size_t kIPCMemSize = kOneMemPage * 2; +constexpr size_t kPolMemSize = kOneMemPage * 14; + +// Helper function to allocate space (on the heap) for policy. +sandbox::PolicyGlobal* MakeBrokerPolicyMemory() { + const size_t kTotalPolicySz = kPolMemSize; + sandbox::PolicyGlobal* policy = + static_cast<sandbox::PolicyGlobal*>(::operator new(kTotalPolicySz)); + DCHECK(policy); + memset(policy, 0, kTotalPolicySz); + policy->data_size = kTotalPolicySz - sizeof(sandbox::PolicyGlobal); + return policy; +} + +bool IsInheritableHandle(HANDLE handle) { + if (!handle) + return false; + if (handle == INVALID_HANDLE_VALUE) + return false; + // File handles (FILE_TYPE_DISK) and pipe handles are known to be + // inheritable. Console handles (FILE_TYPE_CHAR) are not + // inheritable via PROC_THREAD_ATTRIBUTE_HANDLE_LIST. + DWORD handle_type = GetFileType(handle); + return handle_type == FILE_TYPE_DISK || handle_type == FILE_TYPE_PIPE; +} + +} // namespace + +namespace sandbox { + +SANDBOX_INTERCEPT IntegrityLevel g_shared_delayed_integrity_level; +SANDBOX_INTERCEPT MitigationFlags g_shared_delayed_mitigations; + +// Initializes static members. alternate_desktop_handle_ is a desktop on +// alternate_winstation_handle_, alternate_desktop_local_winstation_handle_ is a +// desktop on the same winstation as the parent process. +HWINSTA PolicyBase::alternate_winstation_handle_ = nullptr; +HDESK PolicyBase::alternate_desktop_handle_ = nullptr; +HDESK PolicyBase::alternate_desktop_local_winstation_handle_ = nullptr; +IntegrityLevel PolicyBase::alternate_desktop_integrity_level_label_ = + INTEGRITY_LEVEL_SYSTEM; +IntegrityLevel + PolicyBase::alternate_desktop_local_winstation_integrity_level_label_ = + INTEGRITY_LEVEL_SYSTEM; + +PolicyBase::PolicyBase() + : ref_count(1), + lockdown_level_(USER_LOCKDOWN), + initial_level_(USER_LOCKDOWN), + job_level_(JOB_LOCKDOWN), + ui_exceptions_(0), + memory_limit_(0), + use_alternate_desktop_(false), + use_alternate_winstation_(false), + file_system_init_(false), + relaxed_interceptions_(true), + stdout_handle_(INVALID_HANDLE_VALUE), + stderr_handle_(INVALID_HANDLE_VALUE), + integrity_level_(INTEGRITY_LEVEL_LAST), + delayed_integrity_level_(INTEGRITY_LEVEL_LAST), + mitigations_(0), + delayed_mitigations_(0), + is_csrss_connected_(true), + policy_maker_(nullptr), + policy_(nullptr), + lowbox_sid_(nullptr), + lockdown_default_dacl_(false), + add_restricting_random_sid_(false), + enable_opm_redirection_(false), + effective_token_(nullptr) { + ::InitializeCriticalSection(&lock_); + dispatcher_.reset(new TopLevelDispatcher(this)); +} + +PolicyBase::~PolicyBase() { + TargetSet::iterator it; + for (it = targets_.begin(); it != targets_.end(); ++it) { + TargetProcess* target = (*it); + delete target; + } + delete policy_maker_; + delete policy_; + + if (lowbox_sid_) + ::LocalFree(lowbox_sid_); + + ::DeleteCriticalSection(&lock_); +} + +void PolicyBase::AddRef() { + ::InterlockedIncrement(&ref_count); +} + +void PolicyBase::Release() { + if (0 == ::InterlockedDecrement(&ref_count)) + delete this; +} + +ResultCode PolicyBase::SetTokenLevel(TokenLevel initial, TokenLevel lockdown) { + if (initial < lockdown) { + return SBOX_ERROR_BAD_PARAMS; + } + initial_level_ = initial; + lockdown_level_ = lockdown; + return SBOX_ALL_OK; +} + +TokenLevel PolicyBase::GetInitialTokenLevel() const { + return initial_level_; +} + +TokenLevel PolicyBase::GetLockdownTokenLevel() const { + return lockdown_level_; +} + +void PolicyBase::SetDoNotUseRestrictingSIDs() { + use_restricting_sids_ = false; +} + +ResultCode PolicyBase::SetJobLevel(JobLevel job_level, uint32_t ui_exceptions) { + if (memory_limit_ && job_level == JOB_NONE) { + return SBOX_ERROR_BAD_PARAMS; + } + job_level_ = job_level; + ui_exceptions_ = ui_exceptions; + return SBOX_ALL_OK; +} + +JobLevel PolicyBase::GetJobLevel() const { + return job_level_; +} + +ResultCode PolicyBase::SetJobMemoryLimit(size_t memory_limit) { + memory_limit_ = memory_limit; + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetAlternateDesktop(bool alternate_winstation) { + use_alternate_desktop_ = true; + use_alternate_winstation_ = alternate_winstation; + return CreateAlternateDesktop(alternate_winstation); +} + +std::wstring PolicyBase::GetAlternateDesktop() const { + // No alternate desktop or winstation. Return an empty string. + if (!use_alternate_desktop_ && !use_alternate_winstation_) { + return std::wstring(); + } + + if (use_alternate_winstation_) { + // The desktop and winstation should have been created by now. + // If we hit this scenario, it means that the user ignored the failure + // during SetAlternateDesktop, so we ignore it here too. + if (!alternate_desktop_handle_ || !alternate_winstation_handle_) + return std::wstring(); + + return GetFullDesktopName(alternate_winstation_handle_, + alternate_desktop_handle_); + } + + if (!alternate_desktop_local_winstation_handle_) + return std::wstring(); + + return GetFullDesktopName(nullptr, + alternate_desktop_local_winstation_handle_); +} + +ResultCode PolicyBase::CreateAlternateDesktop(bool alternate_winstation) { + if (alternate_winstation) { + // Check if it's already created. + if (alternate_winstation_handle_ && alternate_desktop_handle_) + return SBOX_ALL_OK; + + DCHECK(!alternate_winstation_handle_); + // Create the window station. + ResultCode result = CreateAltWindowStation(&alternate_winstation_handle_); + if (SBOX_ALL_OK != result) + return result; + + // Verify that everything is fine. + if (!alternate_winstation_handle_ || + base::win::GetWindowObjectName(alternate_winstation_handle_).empty()) + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; + + // Create the destkop. + result = CreateAltDesktop(alternate_winstation_handle_, + &alternate_desktop_handle_); + if (SBOX_ALL_OK != result) + return result; + + // Verify that everything is fine. + if (!alternate_desktop_handle_ || + base::win::GetWindowObjectName(alternate_desktop_handle_).empty()) { + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; + } + } else { + // Check if it already exists. + if (alternate_desktop_local_winstation_handle_) + return SBOX_ALL_OK; + + // Create the destkop. + ResultCode result = + CreateAltDesktop(nullptr, &alternate_desktop_local_winstation_handle_); + if (SBOX_ALL_OK != result) + return result; + + // Verify that everything is fine. + if (!alternate_desktop_local_winstation_handle_ || + base::win::GetWindowObjectName( + alternate_desktop_local_winstation_handle_) + .empty()) { + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; + } + } + + return SBOX_ALL_OK; +} + +void PolicyBase::DestroyAlternateDesktop() { + if (use_alternate_winstation_) { + if (alternate_desktop_handle_) { + ::CloseDesktop(alternate_desktop_handle_); + alternate_desktop_handle_ = nullptr; + } + + if (alternate_winstation_handle_) { + ::CloseWindowStation(alternate_winstation_handle_); + alternate_winstation_handle_ = nullptr; + } + } else { + if (alternate_desktop_local_winstation_handle_) { + ::CloseDesktop(alternate_desktop_local_winstation_handle_); + alternate_desktop_local_winstation_handle_ = nullptr; + } + } +} + +ResultCode PolicyBase::SetIntegrityLevel(IntegrityLevel integrity_level) { + if (app_container_profile_) + return SBOX_ERROR_BAD_PARAMS; + integrity_level_ = integrity_level; + return SBOX_ALL_OK; +} + +IntegrityLevel PolicyBase::GetIntegrityLevel() const { + return integrity_level_; +} + +ResultCode PolicyBase::SetDelayedIntegrityLevel( + IntegrityLevel integrity_level) { + delayed_integrity_level_ = integrity_level; + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetLowBox(const wchar_t* sid) { + if (base::win::GetVersion() < base::win::Version::WIN8) + return SBOX_ERROR_UNSUPPORTED; + + DCHECK(sid); + if (lowbox_sid_ || app_container_profile_) + return SBOX_ERROR_BAD_PARAMS; + + if (!ConvertStringSidToSid(sid, &lowbox_sid_)) + return SBOX_ERROR_INVALID_LOWBOX_SID; + + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetProcessMitigations(MitigationFlags flags) { + // Prior to Win10 RS5 CreateProcess fails when AppContainer and mitigation + // flags are enabled. Return an error on downlevel platforms if trying to + // set new mitigations. + if (app_container_profile_ && + base::win::GetVersion() < base::win::Version::WIN10_RS5) { + return SBOX_ERROR_BAD_PARAMS; + } + if (!CanSetProcessMitigationsPreStartup(flags)) + return SBOX_ERROR_BAD_PARAMS; + mitigations_ = flags; + return SBOX_ALL_OK; +} + +MitigationFlags PolicyBase::GetProcessMitigations() { + return mitigations_; +} + +ResultCode PolicyBase::SetDelayedProcessMitigations(MitigationFlags flags) { + if (!CanSetProcessMitigationsPostStartup(flags)) + return SBOX_ERROR_BAD_PARAMS; + delayed_mitigations_ = flags; + return SBOX_ALL_OK; +} + +MitigationFlags PolicyBase::GetDelayedProcessMitigations() const { + return delayed_mitigations_; +} + +void PolicyBase::SetStrictInterceptions() { + relaxed_interceptions_ = false; +} + +ResultCode PolicyBase::SetStdoutHandle(HANDLE handle) { + if (!IsInheritableHandle(handle)) + return SBOX_ERROR_BAD_PARAMS; + stdout_handle_ = handle; + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetStderrHandle(HANDLE handle) { + if (!IsInheritableHandle(handle)) + return SBOX_ERROR_BAD_PARAMS; + stderr_handle_ = handle; + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::AddRule(SubSystem subsystem, + Semantics semantics, + const wchar_t* pattern) { + ResultCode result = AddRuleInternal(subsystem, semantics, pattern); + LOG_IF(ERROR, result != SBOX_ALL_OK) + << "Failed to add sandbox rule." + << " error = " << result << ", subsystem = " << subsystem + << ", semantics = " << semantics << ", pattern = '" << pattern << "'"; + return result; +} + +ResultCode PolicyBase::AddDllToUnload(const wchar_t* dll_name) { + blocklisted_dlls_.push_back(dll_name); + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::AddKernelObjectToClose(const wchar_t* handle_type, + const wchar_t* handle_name) { + return handle_closer_.AddHandle(handle_type, handle_name); +} + +void PolicyBase::AddHandleToShare(HANDLE handle) { + CHECK(handle); + CHECK_NE(handle, INVALID_HANDLE_VALUE); + + // Ensure the handle can be inherited. + bool result = + SetHandleInformation(handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); + PCHECK(result); + + handles_to_share_.push_back(handle); +} + +void PolicyBase::SetLockdownDefaultDacl() { + lockdown_default_dacl_ = true; +} + +void PolicyBase::AddRestrictingRandomSid() { + add_restricting_random_sid_ = true; +} + +const base::HandlesToInheritVector& PolicyBase::GetHandlesBeingShared() { + return handles_to_share_; +} + +ResultCode PolicyBase::MakeJobObject(base::win::ScopedHandle* job) { + if (job_level_ == JOB_NONE) { + job->Close(); + return SBOX_ALL_OK; + } + + // Create the windows job object. + Job job_obj; + DWORD result = + job_obj.Init(job_level_, nullptr, ui_exceptions_, memory_limit_); + if (ERROR_SUCCESS != result) + return SBOX_ERROR_CANNOT_INIT_JOB; + + *job = job_obj.Take(); + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::MakeTokens(base::win::ScopedHandle* initial, + base::win::ScopedHandle* lockdown, + base::win::ScopedHandle* lowbox) { + Sid random_sid = Sid::GenerateRandomSid(); + PSID random_sid_ptr = nullptr; + if (add_restricting_random_sid_) + random_sid_ptr = random_sid.GetPSID(); + + // Create the 'naked' token. This will be the permanent token associated + // with the process and therefore with any thread that is not impersonating. + DWORD result = CreateRestrictedToken( + effective_token_, lockdown_level_, integrity_level_, PRIMARY, + lockdown_default_dacl_, random_sid_ptr, use_restricting_sids_, lockdown); + if (ERROR_SUCCESS != result) + return SBOX_ERROR_CANNOT_CREATE_RESTRICTED_TOKEN; + + // If we're launching on the alternate desktop we need to make sure the + // integrity label on the object is no higher than the sandboxed process's + // integrity level. So, we lower the label on the desktop process if it's + // not already low enough for our process. + if (use_alternate_desktop_ && integrity_level_ != INTEGRITY_LEVEL_LAST) { + // Integrity label enum is reversed (higher level is a lower value). + static_assert(INTEGRITY_LEVEL_SYSTEM < INTEGRITY_LEVEL_UNTRUSTED, + "Integrity level ordering reversed."); + HDESK desktop_handle = nullptr; + IntegrityLevel desktop_integrity_level_label; + if (use_alternate_winstation_) { + desktop_handle = alternate_desktop_handle_; + desktop_integrity_level_label = alternate_desktop_integrity_level_label_; + } else { + desktop_handle = alternate_desktop_local_winstation_handle_; + desktop_integrity_level_label = + alternate_desktop_local_winstation_integrity_level_label_; + } + // If the desktop_handle hasn't been created for any reason, skip this. + if (desktop_handle && desktop_integrity_level_label < integrity_level_) { + result = + SetObjectIntegrityLabel(desktop_handle, SE_WINDOW_OBJECT, L"", + GetIntegrityLevelString(integrity_level_)); + if (ERROR_SUCCESS != result) + return SBOX_ERROR_CANNOT_SET_DESKTOP_INTEGRITY; + + if (use_alternate_winstation_) { + alternate_desktop_integrity_level_label_ = integrity_level_; + } else { + alternate_desktop_local_winstation_integrity_level_label_ = + integrity_level_; + } + } + } + + if (lowbox_sid_) { + if (!lowbox_directory_.IsValid()) { + result = + CreateLowBoxObjectDirectory(lowbox_sid_, true, &lowbox_directory_); + DCHECK(result == ERROR_SUCCESS); + } + + // The order of handles isn't important in the CreateLowBoxToken call. + // The kernel will maintain a reference to the object directory handle. + HANDLE saved_handles[1] = {lowbox_directory_.Get()}; + DWORD saved_handles_count = lowbox_directory_.IsValid() ? 1 : 0; + + Sid package_sid(lowbox_sid_); + SecurityCapabilities caps(package_sid); + if (CreateLowBoxToken(lockdown->Get(), PRIMARY, &caps, saved_handles, + saved_handles_count, lowbox) != ERROR_SUCCESS) { + return SBOX_ERROR_CANNOT_CREATE_LOWBOX_TOKEN; + } + + if (!ReplacePackageSidInDacl(lowbox->Get(), SE_KERNEL_OBJECT, package_sid, + TOKEN_ALL_ACCESS)) { + return SBOX_ERROR_CANNOT_MODIFY_LOWBOX_TOKEN_DACL; + } + } + + // Create the 'better' token. We use this token as the one that the main + // thread uses when booting up the process. It should contain most of + // what we need (before reaching main( )) + result = CreateRestrictedToken( + effective_token_, initial_level_, integrity_level_, IMPERSONATION, + lockdown_default_dacl_, random_sid_ptr, use_restricting_sids_, initial); + if (ERROR_SUCCESS != result) + return SBOX_ERROR_CANNOT_CREATE_RESTRICTED_IMP_TOKEN; + + return SBOX_ALL_OK; +} + +PSID PolicyBase::GetLowBoxSid() const { + return lowbox_sid_; +} + +ResultCode PolicyBase::AddTarget(TargetProcess* target) { + if (policy_) + policy_maker_->Done(); + + if (!ApplyProcessMitigationsToSuspendedProcess(target->Process(), + mitigations_)) { + return SBOX_ERROR_APPLY_ASLR_MITIGATIONS; + } + + ResultCode ret = SetupAllInterceptions(target); + + if (ret != SBOX_ALL_OK) + return ret; + + if (!SetupHandleCloser(target)) + return SBOX_ERROR_SETUP_HANDLE_CLOSER; + + DWORD win_error = ERROR_SUCCESS; + // Initialize the sandbox infrastructure for the target. + // TODO(wfh) do something with win_error code here. + ret = target->Init(dispatcher_.get(), policy_, kIPCMemSize, kPolMemSize, + &win_error); + + if (ret != SBOX_ALL_OK) + return ret; + + g_shared_delayed_integrity_level = delayed_integrity_level_; + ret = target->TransferVariable("g_shared_delayed_integrity_level", + &g_shared_delayed_integrity_level, + sizeof(g_shared_delayed_integrity_level)); + g_shared_delayed_integrity_level = INTEGRITY_LEVEL_LAST; + if (SBOX_ALL_OK != ret) + return ret; + + // Add in delayed mitigations and pseudo-mitigations enforced at startup. + g_shared_delayed_mitigations = + delayed_mitigations_ | FilterPostStartupProcessMitigations(mitigations_); + if (!CanSetProcessMitigationsPostStartup(g_shared_delayed_mitigations)) + return SBOX_ERROR_BAD_PARAMS; + + ret = target->TransferVariable("g_shared_delayed_mitigations", + &g_shared_delayed_mitigations, + sizeof(g_shared_delayed_mitigations)); + g_shared_delayed_mitigations = 0; + if (SBOX_ALL_OK != ret) + return ret; + + AutoLock lock(&lock_); + targets_.push_back(target); + return SBOX_ALL_OK; +} + +bool PolicyBase::OnJobEmpty(HANDLE job) { + AutoLock lock(&lock_); + TargetSet::iterator it; + for (it = targets_.begin(); it != targets_.end(); ++it) { + if ((*it)->Job() == job) + break; + } + if (it == targets_.end()) { + return false; + } + TargetProcess* target = *it; + targets_.erase(it); + delete target; + return true; +} + +ResultCode PolicyBase::SetDisconnectCsrss() { +// Does not work on 32-bit, and the ASAN runtime falls over with the +// CreateThread EAT patch used when this is enabled. +// See https://crbug.com/783296#c27. +#if defined(_WIN64) && !defined(ADDRESS_SANITIZER) + if (base::win::GetVersion() >= base::win::Version::WIN10) { + is_csrss_connected_ = false; + return AddKernelObjectToClose(L"ALPC Port", nullptr); + } +#endif // !defined(_WIN64) + return SBOX_ALL_OK; +} + +EvalResult PolicyBase::EvalPolicy(IpcTag service, + CountedParameterSetBase* params) { + if (policy_) { + if (!policy_->entry[static_cast<size_t>(service)]) { + // There is no policy for this particular service. This is not a big + // deal. + return DENY_ACCESS; + } + for (size_t i = 0; i < params->count; i++) { + if (!params->parameters[i].IsValid()) { + NOTREACHED(); + return SIGNAL_ALARM; + } + } + PolicyProcessor pol_evaluator(policy_->entry[static_cast<size_t>(service)]); + PolicyResult result = + pol_evaluator.Evaluate(kShortEval, params->parameters, params->count); + if (POLICY_MATCH == result) + return pol_evaluator.GetAction(); + + DCHECK(POLICY_ERROR != result); + } + + return DENY_ACCESS; +} + +HANDLE PolicyBase::GetStdoutHandle() { + return stdout_handle_; +} + +HANDLE PolicyBase::GetStderrHandle() { + return stderr_handle_; +} + +void PolicyBase::SetEnableOPMRedirection() { + enable_opm_redirection_ = true; +} + +bool PolicyBase::GetEnableOPMRedirection() { + return enable_opm_redirection_; +} + +ResultCode PolicyBase::AddAppContainerProfile(const wchar_t* package_name, + bool create_profile) { + if (base::win::GetVersion() < base::win::Version::WIN8) + return SBOX_ERROR_UNSUPPORTED; + + DCHECK(package_name); + if (lowbox_sid_ || app_container_profile_ || + integrity_level_ != INTEGRITY_LEVEL_LAST) { + return SBOX_ERROR_BAD_PARAMS; + } + + if (create_profile) { + app_container_profile_ = AppContainerProfileBase::Create( + package_name, L"Chrome Sandbox", L"Profile for Chrome Sandbox"); + } else { + app_container_profile_ = AppContainerProfileBase::Open(package_name); + } + if (!app_container_profile_) + return SBOX_ERROR_CREATE_APPCONTAINER_PROFILE; + + // A bug exists in CreateProcess where enabling an AppContainer profile and + // passing a set of mitigation flags will generate ERROR_INVALID_PARAMETER. + // Apply best efforts here and convert set mitigations to delayed mitigations. + // This bug looks to have been fixed in Win10 RS5, so exit early if possible. + if (base::win::GetVersion() >= base::win::Version::WIN10_RS5) + return SBOX_ALL_OK; + + delayed_mitigations_ = + mitigations_ & GetAllowedPostStartupProcessMitigations(); + DCHECK(delayed_mitigations_ == + (mitigations_ & ~(MITIGATION_SEHOP | + MITIGATION_RESTRICT_INDIRECT_BRANCH_PREDICTION))); + mitigations_ = 0; + return SBOX_ALL_OK; +} + +scoped_refptr<AppContainerProfile> PolicyBase::GetAppContainerProfile() { + return GetAppContainerProfileBase(); +} + +void PolicyBase::SetEffectiveToken(HANDLE token) { + CHECK(token); + effective_token_ = token; +} + +scoped_refptr<AppContainerProfileBase> +PolicyBase::GetAppContainerProfileBase() { + return app_container_profile_; +} + +ResultCode PolicyBase::SetupAllInterceptions(TargetProcess* target) { + InterceptionManager manager(target, relaxed_interceptions_); + + if (policy_) { + for (size_t i = 0; i < kMaxIpcTag; i++) { + if (policy_->entry[i] && + !dispatcher_->SetupService(&manager, static_cast<IpcTag>(i))) + return SBOX_ERROR_SETUP_INTERCEPTION_SERVICE; + } + } + + for (const std::wstring& dll : blocklisted_dlls_) + manager.AddToUnloadModules(dll.c_str()); + + if (!SetupBasicInterceptions(&manager, is_csrss_connected_)) + return SBOX_ERROR_SETUP_BASIC_INTERCEPTIONS; + + ResultCode rc = manager.InitializeInterceptions(); + if (rc != SBOX_ALL_OK) + return rc; + + // Finally, setup imports on the target so the interceptions can work. + if (!SetupNtdllImports(target)) + return SBOX_ERROR_SETUP_NTDLL_IMPORTS; + + return SBOX_ALL_OK; +} + +bool PolicyBase::SetupHandleCloser(TargetProcess* target) { + return handle_closer_.InitializeTargetHandles(target); +} + +ResultCode PolicyBase::AddRuleInternal(SubSystem subsystem, + Semantics semantics, + const wchar_t* pattern) { + if (!policy_) { + policy_ = MakeBrokerPolicyMemory(); + DCHECK(policy_); + policy_maker_ = new LowLevelPolicy(policy_); + DCHECK(policy_maker_); + } + + switch (subsystem) { + case SUBSYS_FILES: { + if (!file_system_init_) { + if (!FileSystemPolicy::SetInitialRules(policy_maker_)) + return SBOX_ERROR_BAD_PARAMS; + file_system_init_ = true; + } + if (!FileSystemPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + case SUBSYS_SYNC: { + if (!SyncPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + case SUBSYS_PROCESS: { + if (lockdown_level_ < USER_INTERACTIVE && + TargetPolicy::PROCESS_ALL_EXEC == semantics) { + // This is unsupported. This is a huge security risk to give full access + // to a process handle. + return SBOX_ERROR_UNSUPPORTED; + } + if (!ProcessPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + case SUBSYS_NAMED_PIPES: { + if (!NamedPipePolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + case SUBSYS_REGISTRY: { + if (!RegistryPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + case SUBSYS_HANDLES: { + if (!HandlePolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + + case SUBSYS_WIN32K_LOCKDOWN: { + // Win32k intercept rules only supported on Windows 8 and above. This must + // match the version checks in process_mitigations.cc for consistency. + if (base::win::GetVersion() >= base::win::Version::WIN8) { + DCHECK_EQ(MITIGATION_WIN32K_DISABLE, + mitigations_ & MITIGATION_WIN32K_DISABLE) + << "Enable MITIGATION_WIN32K_DISABLE before adding win32k policy " + "rules."; + if (!ProcessMitigationsWin32KLockdownPolicy::GenerateRules( + pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + } + break; + } + case SUBSYS_SIGNED_BINARY: { + // Signed intercept rules only supported on Windows 10 TH2 and above. This + // must match the version checks in process_mitigations.cc for + // consistency. + if (base::win::GetVersion() >= base::win::Version::WIN10_TH2) { + DCHECK_EQ(MITIGATION_FORCE_MS_SIGNED_BINS, + (mitigations_ & MITIGATION_FORCE_MS_SIGNED_BINS) | (delayed_mitigations_ & MITIGATION_FORCE_MS_SIGNED_BINS)) + << "Enable MITIGATION_FORCE_MS_SIGNED_BINS before adding signed " + "policy rules."; + if (!SignedPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + } + break; + } + case SUBSYS_LINE_BREAK: { + if (!LineBreakPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + + default: { return SBOX_ERROR_UNSUPPORTED; } + } + + return SBOX_ALL_OK; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_policy_base.h b/security/sandbox/chromium/sandbox/win/src/sandbox_policy_base.h new file mode 100644 index 0000000000..18268e230d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_policy_base.h @@ -0,0 +1,198 @@ +// Copyright (c) 2011 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. + +#ifndef SANDBOX_WIN_SRC_SANDBOX_POLICY_BASE_H_ +#define SANDBOX_WIN_SRC_SANDBOX_POLICY_BASE_H_ + +#include <windows.h> + +#include <stddef.h> +#include <stdint.h> + +#include <list> +#include <memory> +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/scoped_refptr.h" +#include "base/process/launch.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/app_container_profile_base.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/handle_closer.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_engine_params.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +class LowLevelPolicy; +class PolicyDiagnostic; +class TargetProcess; +struct PolicyGlobal; + +class PolicyBase final : public TargetPolicy { + public: + PolicyBase(); + + // TargetPolicy: + void AddRef() override; + void Release() override; + ResultCode SetTokenLevel(TokenLevel initial, TokenLevel lockdown) override; + TokenLevel GetInitialTokenLevel() const override; + TokenLevel GetLockdownTokenLevel() const override; + void SetDoNotUseRestrictingSIDs() final; + ResultCode SetJobLevel(JobLevel job_level, uint32_t ui_exceptions) override; + JobLevel GetJobLevel() const override; + ResultCode SetJobMemoryLimit(size_t memory_limit) override; + ResultCode SetAlternateDesktop(bool alternate_winstation) override; + std::wstring GetAlternateDesktop() const override; + ResultCode CreateAlternateDesktop(bool alternate_winstation) override; + void DestroyAlternateDesktop() override; + ResultCode SetIntegrityLevel(IntegrityLevel integrity_level) override; + IntegrityLevel GetIntegrityLevel() const override; + ResultCode SetDelayedIntegrityLevel(IntegrityLevel integrity_level) override; + ResultCode SetLowBox(const wchar_t* sid) override; + ResultCode SetProcessMitigations(MitigationFlags flags) override; + MitigationFlags GetProcessMitigations() override; + ResultCode SetDelayedProcessMitigations(MitigationFlags flags) override; + MitigationFlags GetDelayedProcessMitigations() const override; + ResultCode SetDisconnectCsrss() override; + void SetStrictInterceptions() override; + ResultCode SetStdoutHandle(HANDLE handle) override; + ResultCode SetStderrHandle(HANDLE handle) override; + ResultCode AddRule(SubSystem subsystem, + Semantics semantics, + const wchar_t* pattern) override; + ResultCode AddDllToUnload(const wchar_t* dll_name) override; + ResultCode AddKernelObjectToClose(const wchar_t* handle_type, + const wchar_t* handle_name) override; + void AddHandleToShare(HANDLE handle) override; + void SetLockdownDefaultDacl() override; + void AddRestrictingRandomSid() override; + void SetEnableOPMRedirection() override; + bool GetEnableOPMRedirection() override; + ResultCode AddAppContainerProfile(const wchar_t* package_name, + bool create_profile) override; + scoped_refptr<AppContainerProfile> GetAppContainerProfile() override; + void SetEffectiveToken(HANDLE token) override; + + // Get the AppContainer profile as its internal type. + scoped_refptr<AppContainerProfileBase> GetAppContainerProfileBase(); + + // Creates a Job object with the level specified in a previous call to + // SetJobLevel(). + ResultCode MakeJobObject(base::win::ScopedHandle* job); + + // Creates the two tokens with the levels specified in a previous call to + // SetTokenLevel(). Also creates a lowbox token if specified based on the + // lowbox SID. + ResultCode MakeTokens(base::win::ScopedHandle* initial, + base::win::ScopedHandle* lockdown, + base::win::ScopedHandle* lowbox); + + PSID GetLowBoxSid() const; + + // Adds a target process to the internal list of targets. Internally a + // call to TargetProcess::Init() is issued. + ResultCode AddTarget(TargetProcess* target); + + // Called when there are no more active processes in a Job. + // Removes a Job object associated with this policy and the target associated + // with the job. + bool OnJobEmpty(HANDLE job); + + EvalResult EvalPolicy(IpcTag service, CountedParameterSetBase* params); + + HANDLE GetStdoutHandle(); + HANDLE GetStderrHandle(); + + // Returns the list of handles being shared with the target process. + const base::HandlesToInheritVector& GetHandlesBeingShared(); + + private: + // Allow PolicyInfo to snapshot PolicyBase for diagnostics. + friend class PolicyDiagnostic; + ~PolicyBase(); + + // Sets up interceptions for a new target. + ResultCode SetupAllInterceptions(TargetProcess* target); + + // Sets up the handle closer for a new target. + bool SetupHandleCloser(TargetProcess* target); + + ResultCode AddRuleInternal(SubSystem subsystem, + Semantics semantics, + const wchar_t* pattern); + + // This lock synchronizes operations on the targets_ collection. + CRITICAL_SECTION lock_; + // Maintains the list of target process associated with this policy. + // The policy takes ownership of them. + typedef std::list<TargetProcess*> TargetSet; + TargetSet targets_; + // Standard object-lifetime reference counter. + volatile LONG ref_count; + // The user-defined global policy settings. + TokenLevel lockdown_level_; + TokenLevel initial_level_; + bool use_restricting_sids_ = true; + JobLevel job_level_; + uint32_t ui_exceptions_; + size_t memory_limit_; + bool use_alternate_desktop_; + bool use_alternate_winstation_; + // Helps the file system policy initialization. + bool file_system_init_; + bool relaxed_interceptions_; + HANDLE stdout_handle_; + HANDLE stderr_handle_; + IntegrityLevel integrity_level_; + IntegrityLevel delayed_integrity_level_; + MitigationFlags mitigations_; + MitigationFlags delayed_mitigations_; + bool is_csrss_connected_; + // Object in charge of generating the low level policy. + LowLevelPolicy* policy_maker_; + // Memory structure that stores the low level policy. + PolicyGlobal* policy_; + // The list of dlls to unload in the target process. + std::vector<std::wstring> blocklisted_dlls_; + // This is a map of handle-types to names that we need to close in the + // target process. A null set means we need to close all handles of the + // given type. + HandleCloser handle_closer_; + PSID lowbox_sid_; + base::win::ScopedHandle lowbox_directory_; + std::unique_ptr<Dispatcher> dispatcher_; + bool lockdown_default_dacl_; + bool add_restricting_random_sid_; + + static HDESK alternate_desktop_handle_; + static HWINSTA alternate_winstation_handle_; + static HDESK alternate_desktop_local_winstation_handle_; + static IntegrityLevel alternate_desktop_integrity_level_label_; + static IntegrityLevel + alternate_desktop_local_winstation_integrity_level_label_; + + // Contains the list of handles being shared with the target process. + // This list contains handles other than the stderr/stdout handles which are + // shared with the target at times. + base::HandlesToInheritVector handles_to_share_; + bool enable_opm_redirection_; + + scoped_refptr<AppContainerProfileBase> app_container_profile_; + + HANDLE effective_token_; + + DISALLOW_COPY_AND_ASSIGN(PolicyBase); +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SANDBOX_POLICY_BASE_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_rand.cc b/security/sandbox/chromium/sandbox/win/src/sandbox_rand.cc new file mode 100644 index 0000000000..9bc8634ec6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_rand.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2015 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/win/src/sandbox_rand.h" + +#include <windows.h> + +// #define needed to link in RtlGenRandom(), a.k.a. SystemFunction036. See the +// "Community Additions" comment on MSDN here: +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa387694.aspx +#define SystemFunction036 NTAPI SystemFunction036 +#include <ntsecapi.h> +#undef SystemFunction036 + +namespace sandbox { + +bool GetRandom(unsigned int* random_value) { + return RtlGenRandom(random_value, sizeof(unsigned int)) != false; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_rand.h b/security/sandbox/chromium/sandbox/win/src/sandbox_rand.h new file mode 100644 index 0000000000..f4662f9a84 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_rand.h @@ -0,0 +1,15 @@ +// Copyright (c) 2015 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. + +#ifndef SANDBOX_SRC_SANDBOX_RAND_H_ +#define SANDBOX_SRC_SANDBOX_RAND_H_ + +namespace sandbox { + +// Generate a random value in |random_value|. Returns true on success. +bool GetRandom(unsigned int* random_value); + +} // namespace sandbox + +#endif // SANDBOX_SRC_SANDBOX_RAND_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_types.h b/security/sandbox/chromium/sandbox/win/src/sandbox_types.h new file mode 100644 index 0000000000..782541863d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_types.h @@ -0,0 +1,199 @@ +// 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. + +#ifndef SANDBOX_WIN_SRC_SANDBOX_TYPES_H_ +#define SANDBOX_WIN_SRC_SANDBOX_TYPES_H_ + +#include "base/process/kill.h" +#include "base/process/launch.h" + +namespace sandbox { + +#ifdef __MINGW32__ +// Map Microsoft's proprietary more-safe version of copy() back to +// the std::basic_string method +#define _Copy_s copy +#endif + +// Operation result codes returned by the sandbox API. +// +// Note: These codes are listed in a histogram and any new codes should be added +// at the end. If the underlying type is changed then the forward declaration in +// sandbox_init.h must be updated. +// +enum ResultCode : int { + SBOX_ALL_OK = 0, + // Error is originating on the win32 layer. Call GetlastError() for more + // information. + SBOX_ERROR_GENERIC = 1, + // An invalid combination of parameters was given to the API. + SBOX_ERROR_BAD_PARAMS = 2, + // The desired operation is not supported at this time. + SBOX_ERROR_UNSUPPORTED = 3, + // The request requires more memory that allocated or available. + SBOX_ERROR_NO_SPACE = 4, + // The ipc service requested does not exist. + SBOX_ERROR_INVALID_IPC = 5, + // The ipc service did not complete. + SBOX_ERROR_FAILED_IPC = 6, + // The requested handle was not found. + SBOX_ERROR_NO_HANDLE = 7, + // This function was not expected to be called at this time. + SBOX_ERROR_UNEXPECTED_CALL = 8, + // WaitForAllTargets is already called. + SBOX_ERROR_WAIT_ALREADY_CALLED = 9, + // A channel error prevented DoCall from executing. + SBOX_ERROR_CHANNEL_ERROR = 10, + // Failed to create the alternate desktop. + SBOX_ERROR_CANNOT_CREATE_DESKTOP = 11, + // Failed to create the alternate window station. + SBOX_ERROR_CANNOT_CREATE_WINSTATION = 12, + // Failed to switch back to the interactive window station. + SBOX_ERROR_FAILED_TO_SWITCH_BACK_WINSTATION = 13, + // The supplied AppContainer is not valid. + SBOX_ERROR_INVALID_APP_CONTAINER = 14, + // The supplied capability is not valid. + SBOX_ERROR_INVALID_CAPABILITY = 15, + // There is a failure initializing the AppContainer. + SBOX_ERROR_CANNOT_INIT_APPCONTAINER = 16, + // Initializing or updating ProcThreadAttributes failed. + SBOX_ERROR_PROC_THREAD_ATTRIBUTES = 17, + // Error in creating process. + SBOX_ERROR_CREATE_PROCESS = 18, + // Failure calling delegate PreSpawnTarget. + SBOX_ERROR_DELEGATE_PRE_SPAWN = 19, + // Could not assign process to job object. + SBOX_ERROR_ASSIGN_PROCESS_TO_JOB_OBJECT = 20, + // Could not assign process to job object. + SBOX_ERROR_SET_THREAD_TOKEN = 21, + // Could not get thread context of new process. + SBOX_ERROR_GET_THREAD_CONTEXT = 22, + // Could not duplicate target info of new process. + SBOX_ERROR_DUPLICATE_TARGET_INFO = 23, + // Could not set low box token. + SBOX_ERROR_SET_LOW_BOX_TOKEN = 24, + // Could not create file mapping for IPC dispatcher. + SBOX_ERROR_CREATE_FILE_MAPPING = 25, + // Could not duplicate shared section into target process for IPC dispatcher. + SBOX_ERROR_DUPLICATE_SHARED_SECTION = 26, + // Could not map view of shared memory in broker. + SBOX_ERROR_MAP_VIEW_OF_SHARED_SECTION = 27, + // Could not apply ASLR mitigations to target process. + SBOX_ERROR_APPLY_ASLR_MITIGATIONS = 28, + // Could not setup one of the required interception services. + SBOX_ERROR_SETUP_BASIC_INTERCEPTIONS = 29, + // Could not setup basic interceptions. + SBOX_ERROR_SETUP_INTERCEPTION_SERVICE = 30, + // Could not initialize interceptions. This usually means 3rd party software + // is stomping on our hooks, or can sometimes mean the syscall format has + // changed. + SBOX_ERROR_INITIALIZE_INTERCEPTIONS = 31, + // Could not setup the imports for ntdll in target process. + SBOX_ERROR_SETUP_NTDLL_IMPORTS = 32, + // Could not setup the handle closer in target process. + SBOX_ERROR_SETUP_HANDLE_CLOSER = 33, + // Cannot get the current Window Station. + SBOX_ERROR_CANNOT_GET_WINSTATION = 34, + // Cannot query the security attributes of the current Window Station. + SBOX_ERROR_CANNOT_QUERY_WINSTATION_SECURITY = 35, + // Cannot get the current Desktop. + SBOX_ERROR_CANNOT_GET_DESKTOP = 36, + // Cannot query the security attributes of the current Desktop. + SBOX_ERROR_CANNOT_QUERY_DESKTOP_SECURITY = 37, + // Cannot setup the interception manager config buffer. + SBOX_ERROR_CANNOT_SETUP_INTERCEPTION_CONFIG_BUFFER = 38, + // Cannot copy data to the child process. + SBOX_ERROR_CANNOT_COPY_DATA_TO_CHILD = 39, + // Cannot setup the interception thunk. + SBOX_ERROR_CANNOT_SETUP_INTERCEPTION_THUNK = 40, + // Cannot resolve the interception thunk. + SBOX_ERROR_CANNOT_RESOLVE_INTERCEPTION_THUNK = 41, + // Cannot write interception thunk to child process. + SBOX_ERROR_CANNOT_WRITE_INTERCEPTION_THUNK = 42, + // Cannot find the base address of the new process. + SBOX_ERROR_CANNOT_FIND_BASE_ADDRESS = 43, + // Cannot create the AppContainer profile. + SBOX_ERROR_CREATE_APPCONTAINER_PROFILE = 44, + // Cannot create the AppContainer as the main executable can't be accessed. + SBOX_ERROR_CREATE_APPCONTAINER_PROFILE_ACCESS_CHECK = 45, + // Cannot create the AppContainer as adding a capability failed. + SBOX_ERROR_CREATE_APPCONTAINER_PROFILE_CAPABILITY = 46, + // Cannot initialize a job object. + SBOX_ERROR_CANNOT_INIT_JOB = 47, + // Invalid LowBox SID string. + SBOX_ERROR_INVALID_LOWBOX_SID = 48, + // Cannot create restricted token. + SBOX_ERROR_CANNOT_CREATE_RESTRICTED_TOKEN = 49, + // Cannot set the integrity level on a desktop object. + SBOX_ERROR_CANNOT_SET_DESKTOP_INTEGRITY = 50, + // Cannot create a LowBox token. + SBOX_ERROR_CANNOT_CREATE_LOWBOX_TOKEN = 51, + // Cannot modify LowBox token's DACL. + SBOX_ERROR_CANNOT_MODIFY_LOWBOX_TOKEN_DACL = 52, + // Cannot create restricted impersonation token. + SBOX_ERROR_CANNOT_CREATE_RESTRICTED_IMP_TOKEN = 53, + // Cannot duplicate target process handle. + SBOX_ERROR_CANNOT_DUPLICATE_PROCESS_HANDLE = 54, + // Cannot load executable for variable transfer. + SBOX_ERROR_CANNOT_LOADLIBRARY_EXECUTABLE = 55, + // Cannot find variable address for transfer. + SBOX_ERROR_CANNOT_FIND_VARIABLE_ADDRESS = 56, + // Cannot write variable value. + SBOX_ERROR_CANNOT_WRITE_VARIABLE_VALUE = 57, + // Short write to variable. + SBOX_ERROR_INVALID_WRITE_VARIABLE_SIZE = 58, + // Cannot initialize BrokerServices. + SBOX_ERROR_CANNOT_INIT_BROKERSERVICES = 59, + // Placeholder for last item of the enum. + SBOX_ERROR_LAST +}; + +// If the sandbox cannot create a secure environment for the target, the +// target will be forcibly terminated. These are the process exit codes. +enum TerminationCodes { + SBOX_FATAL_INTEGRITY = 7006, // Could not set the integrity level. + SBOX_FATAL_DROPTOKEN = 7007, // Could not lower the token. + SBOX_FATAL_FLUSHANDLES = 7008, // Failed to flush registry handles. + SBOX_FATAL_CACHEDISABLE = 7009, // Failed to forbid HCKU caching. + SBOX_FATAL_CLOSEHANDLES = 7010, // Failed to close pending handles. + SBOX_FATAL_MITIGATION = 7011, // Could not set the mitigation policy. + SBOX_FATAL_MEMORY_EXCEEDED = 7012, // Exceeded the job memory limit. + SBOX_FATAL_WARMUP = 7013, // Failed to warmup. + SBOX_FATAL_LAST +}; + +#if !defined(SANDBOX_FUZZ_TARGET) +static_assert(SBOX_FATAL_MEMORY_EXCEEDED == + base::win::kSandboxFatalMemoryExceeded, + "Value for SBOX_FATAL_MEMORY_EXCEEDED must match base."); +#endif // !defined(SANDBOX_FUZZ_TARGET) + +class BrokerServices; +class TargetServices; + +// Contains the pointer to a target or broker service. +struct SandboxInterfaceInfo { + BrokerServices* broker_services; + TargetServices* target_services; +}; + +#if SANDBOX_EXPORTS +#define SANDBOX_INTERCEPT extern "C" __declspec(dllexport) +#else +#define SANDBOX_INTERCEPT extern "C" +#endif + +enum InterceptionType { + INTERCEPTION_INVALID = 0, + INTERCEPTION_SERVICE_CALL, // Trampoline of an NT native call + INTERCEPTION_EAT, + INTERCEPTION_SIDESTEP, // Preamble patch + INTERCEPTION_SMART_SIDESTEP, // Preamble patch but bypass internal calls + INTERCEPTION_UNLOAD_MODULE, // Unload the module (don't patch) + INTERCEPTION_LAST // Placeholder for last item in the enumeration +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SANDBOX_TYPES_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_utils.cc b/security/sandbox/chromium/sandbox/win/src/sandbox_utils.cc new file mode 100644 index 0000000000..f34daa7d3e --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_utils.cc @@ -0,0 +1,32 @@ +// Copyright (c) 2011 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/win/src/sandbox_utils.h" + +#include <windows.h> + +#include "base/logging.h" +#include "sandbox/win/src/internal_types.h" + +namespace sandbox { + +void InitObjectAttribs(const std::wstring& name, + ULONG attributes, + HANDLE root, + OBJECT_ATTRIBUTES* obj_attr, + UNICODE_STRING* uni_name, + SECURITY_QUALITY_OF_SERVICE* security_qos) { + static RtlInitUnicodeStringFunction RtlInitUnicodeString; + if (!RtlInitUnicodeString) { + HMODULE ntdll = ::GetModuleHandle(kNtdllName); + RtlInitUnicodeString = reinterpret_cast<RtlInitUnicodeStringFunction>( + GetProcAddress(ntdll, "RtlInitUnicodeString")); + DCHECK(RtlInitUnicodeString); + } + RtlInitUnicodeString(uni_name, name.c_str()); + InitializeObjectAttributes(obj_attr, uni_name, attributes, root, nullptr); + obj_attr->SecurityQualityOfService = security_qos; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_utils.h b/security/sandbox/chromium/sandbox/win/src/sandbox_utils.h new file mode 100644 index 0000000000..580d1298f6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_utils.h @@ -0,0 +1,24 @@ +// Copyright (c) 2011 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. + +#ifndef SANDBOX_SRC_SANDBOX_UTILS_H_ +#define SANDBOX_SRC_SANDBOX_UTILS_H_ + +#include <windows.h> +#include <string> + +#include "sandbox/win/src/nt_internals.h" + +namespace sandbox { + +void InitObjectAttribs(const std::wstring& name, + ULONG attributes, + HANDLE root, + OBJECT_ATTRIBUTES* obj_attr, + UNICODE_STRING* uni_name, + SECURITY_QUALITY_OF_SERVICE* security_qos); + +} // namespace sandbox + +#endif // SANDBOX_SRC_SANDBOX_UTILS_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/security_capabilities.cc b/security/sandbox/chromium/sandbox/win/src/security_capabilities.cc new file mode 100644 index 0000000000..0df446b5be --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/security_capabilities.cc @@ -0,0 +1,33 @@ +// Copyright 2017 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/win/src/security_capabilities.h" +#include "base/numerics/safe_conversions.h" + +namespace sandbox { + +SecurityCapabilities::SecurityCapabilities(const Sid& package_sid, + const std::vector<Sid>& capabilities) + : SECURITY_CAPABILITIES(), + capabilities_(capabilities), + package_sid_(package_sid) { + AppContainerSid = package_sid_.GetPSID(); + if (capabilities_.empty()) + return; + + capability_sids_.resize(capabilities_.size()); + for (size_t index = 0; index < capabilities_.size(); ++index) { + capability_sids_[index].Sid = capabilities_[index].GetPSID(); + capability_sids_[index].Attributes = SE_GROUP_ENABLED; + } + CapabilityCount = base::checked_cast<DWORD>(capability_sids_.size()); + Capabilities = capability_sids_.data(); +} + +SecurityCapabilities::SecurityCapabilities(const Sid& package_sid) + : SecurityCapabilities(package_sid, std::vector<Sid>()) {} + +SecurityCapabilities::~SecurityCapabilities() {} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/security_capabilities.h b/security/sandbox/chromium/sandbox/win/src/security_capabilities.h new file mode 100644 index 0000000000..7a66e59743 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/security_capabilities.h @@ -0,0 +1,34 @@ +// Copyright 2017 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. + +#ifndef SANDBOX_SRC_SECURITY_CAPABILITIES_H_ +#define SANDBOX_SRC_SECURITY_CAPABILITIES_H_ + +#include <windows.h> + +#include <vector> + +#include "base/macros.h" +#include "sandbox/win/src/sid.h" + +namespace sandbox { + +class SecurityCapabilities final : public SECURITY_CAPABILITIES { + public: + explicit SecurityCapabilities(const Sid& package_sid); + SecurityCapabilities(const Sid& package_sid, + const std::vector<Sid>& capabilities); + ~SecurityCapabilities(); + + private: + std::vector<Sid> capabilities_; + std::vector<SID_AND_ATTRIBUTES> capability_sids_; + Sid package_sid_; + + DISALLOW_COPY_AND_ASSIGN(SecurityCapabilities); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SECURITY_CAPABILITIES_H_
\ No newline at end of file diff --git a/security/sandbox/chromium/sandbox/win/src/security_level.h b/security/sandbox/chromium/sandbox/win/src/security_level.h new file mode 100644 index 0000000000..8e81cf21c9 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/security_level.h @@ -0,0 +1,300 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_SRC_SECURITY_LEVEL_H_ +#define SANDBOX_SRC_SECURITY_LEVEL_H_ + +#include <stdint.h> + +namespace sandbox { + +// List of all the integrity levels supported in the sandbox. +// The integrity level of the sandboxed process can't be set to a level higher +// than the broker process. +// +// Note: These levels map to SIDs under the hood. +// INTEGRITY_LEVEL_SYSTEM: "S-1-16-16384" System Mandatory Level +// INTEGRITY_LEVEL_HIGH: "S-1-16-12288" High Mandatory Level +// INTEGRITY_LEVEL_MEDIUM: "S-1-16-8192" Medium Mandatory Level +// INTEGRITY_LEVEL_MEDIUM_LOW: "S-1-16-6144" +// INTEGRITY_LEVEL_LOW: "S-1-16-4096" Low Mandatory Level +// INTEGRITY_LEVEL_BELOW_LOW: "S-1-16-2048" +// INTEGRITY_LEVEL_UNTRUSTED: "S-1-16-0" Untrusted Mandatory Level +// +// Not defined: "S-1-16-20480" Protected Process Mandatory Level +// Not defined: "S-1-16-28672" Secure Process Mandatory Level +enum IntegrityLevel { + INTEGRITY_LEVEL_SYSTEM, + INTEGRITY_LEVEL_HIGH, + INTEGRITY_LEVEL_MEDIUM, + INTEGRITY_LEVEL_MEDIUM_LOW, + INTEGRITY_LEVEL_LOW, + INTEGRITY_LEVEL_BELOW_LOW, + INTEGRITY_LEVEL_UNTRUSTED, + INTEGRITY_LEVEL_LAST +}; + +// The Token level specifies a set of security profiles designed to +// provide the bulk of the security of sandbox. +// +// TokenLevel |Restricting |Deny Only |Privileges| +// |Sids |Sids | | +// ----------------------------|--------------|----------------|----------| +// USER_LOCKDOWN | Null Sid | All | None | +// ----------------------------|--------------|----------------|----------| +// USER_RESTRICTED | RESTRICTED | All | Traverse | +// ----------------------------|--------------|----------------|----------| +// USER_LIMITED | Users | All except: | Traverse | +// | Everyone | Users | | +// | RESTRICTED | Everyone | | +// | | Interactive | | +// ----------------------------|--------------|----------------|----------| +// USER_INTERACTIVE | Users | All except: | Traverse | +// | Everyone | Users | | +// | RESTRICTED | Everyone | | +// | Owner | Interactive | | +// | | Local | | +// | | Authent-users | | +// | | User | | +// ----------------------------|--------------|----------------|----------| +// USER_RESTRICTED_NON_ADMIN | Users | All except: | Traverse | +// | Everyone | Users | | +// | Interactive | Everyone | | +// | Local | Interactive | | +// | Authent-users| Local | | +// | User | Authent-users | | +// | | User | | +// ----------------------------|--------------|----------------|----------| +// USER_NON_ADMIN | None | All except: | Traverse | +// | | Users | | +// | | Everyone | | +// | | Interactive | | +// | | Local | | +// | | Authent-users | | +// | | User | | +// ----------------------------|--------------|----------------|----------| +// USER_RESTRICTED_SAME_ACCESS | All | None | All | +// ----------------------------|--------------|----------------|----------| +// USER_UNPROTECTED | None | None | All | +// ----------------------------|--------------|----------------|----------| +// +// The above restrictions are actually a transformation that is applied to +// the existing broker process token. The resulting token that will be +// applied to the target process depends both on the token level selected +// and on the broker token itself. +// +// The LOCKDOWN and RESTRICTED are designed to allow access to almost +// nothing that has security associated with and they are the recommended +// levels to run sandboxed code specially if there is a chance that the +// broker is process might be started by a user that belongs to the Admins +// or power users groups. +enum TokenLevel { + USER_LOCKDOWN = 0, + USER_RESTRICTED, + USER_LIMITED, + USER_INTERACTIVE, + USER_RESTRICTED_NON_ADMIN, + USER_NON_ADMIN, + USER_RESTRICTED_SAME_ACCESS, + USER_UNPROTECTED, + USER_LAST +}; + +// The Job level specifies a set of decreasing security profiles for the +// Job object that the target process will be placed into. +// This table summarizes the security associated with each level: +// +// JobLevel |General |Quota | +// |restrictions |restrictions | +// -----------------|---------------------------------- |--------------------| +// JOB_NONE | No job is assigned to the | None | +// | sandboxed process. | | +// -----------------|---------------------------------- |--------------------| +// JOB_UNPROTECTED | None | *Kill on Job close.| +// -----------------|---------------------------------- |--------------------| +// JOB_INTERACTIVE | *Forbid system-wide changes using | | +// | SystemParametersInfo(). | *Kill on Job close.| +// | *Forbid the creation/switch of | | +// | Desktops. | | +// | *Forbids calls to ExitWindows(). | | +// -----------------|---------------------------------- |--------------------| +// JOB_LIMITED_USER | Same as INTERACTIVE_USER plus: | *One active process| +// | *Forbid changes to the display | limit. | +// | settings. | *Kill on Job close.| +// -----------------|---------------------------------- |--------------------| +// JOB_RESTRICTED | Same as LIMITED_USER plus: | *One active process| +// | * No read/write to the clipboard. | limit. | +// | * No access to User Handles that | *Kill on Job close.| +// | belong to other processes. | | +// | * Forbid message broadcasts. | | +// | * Forbid setting global hooks. | | +// | * No access to the global atoms | | +// | table. | | +// -----------------|-----------------------------------|--------------------| +// JOB_LOCKDOWN | Same as RESTRICTED | *One active process| +// | | limit. | +// | | *Kill on Job close.| +// | | *Kill on unhandled | +// | | exception. | +// | | | +// In the context of the above table, 'user handles' refers to the handles of +// windows, bitmaps, menus, etc. Files, treads and registry handles are kernel +// handles and are not affected by the job level settings. +enum JobLevel { + JOB_LOCKDOWN = 0, + JOB_RESTRICTED, + JOB_LIMITED_USER, + JOB_INTERACTIVE, + JOB_UNPROTECTED, + JOB_NONE +}; + +// These flags correspond to various process-level mitigations (eg. ASLR and +// DEP). Most are implemented via UpdateProcThreadAttribute() plus flags for +// the PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY attribute argument; documented +// here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686880 +// Some mitigations are implemented directly by the sandbox or emulated to +// the greatest extent possible when not directly supported by the OS. +// Flags that are unsupported for the target OS will be silently ignored. +// Flags that are invalid for their application (pre or post startup) will +// return SBOX_ERROR_BAD_PARAMS. +typedef uint64_t MitigationFlags; + +// Permanently enables DEP for the target process. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_DEP_ENABLE. +const MitigationFlags MITIGATION_DEP = 0x00000001; + +// Permanently Disables ATL thunk emulation when DEP is enabled. Valid +// only when MITIGATION_DEP is passed. Corresponds to not passing +// PROCESS_CREATION_MITIGATION_POLICY_DEP_ATL_THUNK_ENABLE. +const MitigationFlags MITIGATION_DEP_NO_ATL_THUNK = 0x00000002; + +// Enables Structured exception handling override prevention. Must be +// enabled prior to process start. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_SEHOP_ENABLE. +const MitigationFlags MITIGATION_SEHOP = 0x00000004; + +// Forces ASLR on all images in the child process. In debug builds, must be +// enabled after startup. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON . +const MitigationFlags MITIGATION_RELOCATE_IMAGE = 0x00000008; + +// Refuses to load DLLs that cannot support ASLR. In debug builds, must be +// enabled after startup. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON_REQ_RELOCS. +const MitigationFlags MITIGATION_RELOCATE_IMAGE_REQUIRED = 0x00000010; + +// Terminates the process on Windows heap corruption. Coresponds to +// PROCESS_CREATION_MITIGATION_POLICY_HEAP_TERMINATE_ALWAYS_ON. +const MitigationFlags MITIGATION_HEAP_TERMINATE = 0x00000020; + +// Sets a random lower bound as the minimum user address. Must be +// enabled prior to process start. On 32-bit processes this is +// emulated to a much smaller degree. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_BOTTOM_UP_ASLR_ALWAYS_ON. +const MitigationFlags MITIGATION_BOTTOM_UP_ASLR = 0x00000040; + +// Increases the randomness range of bottom-up ASLR to up to 1TB. Must be +// enabled prior to process start and with MITIGATION_BOTTOM_UP_ASLR. +// Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_HIGH_ENTROPY_ASLR_ALWAYS_ON +const MitigationFlags MITIGATION_HIGH_ENTROPY_ASLR = 0x00000080; + +// Immediately raises an exception on a bad handle reference. Must be +// enabled after startup. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_STRICT_HANDLE_CHECKS_ALWAYS_ON. +const MitigationFlags MITIGATION_STRICT_HANDLE_CHECKS = 0x00000100; + +// Sets the DLL search order to LOAD_LIBRARY_SEARCH_DEFAULT_DIRS. Additional +// directories can be added via the Windows AddDllDirectory() function. +// http://msdn.microsoft.com/en-us/library/windows/desktop/hh310515 +// Must be enabled after startup. +const MitigationFlags MITIGATION_DLL_SEARCH_ORDER = 0x00000200; + +// Changes the mandatory integrity level policy on the current process' token +// to enable no-read and no-execute up. This prevents a lower IL process from +// opening the process token for impersonate/duplicate/assignment. +const MitigationFlags MITIGATION_HARDEN_TOKEN_IL_POLICY = 0x00000400; + +// Prevents the process from making Win32k calls. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON. +// +// Applications linked to user32.dll or gdi32.dll make Win32k calls during +// setup, even if Win32k is not otherwise used. So they also need to add a rule +// with SUBSYS_WIN32K_LOCKDOWN and semantics FAKE_USER_GDI_INIT to allow the +// initialization to succeed. +const MitigationFlags MITIGATION_WIN32K_DISABLE = 0x00000800; + +// Prevents certain built-in third party extension points from being used. +// - App_Init DLLs +// - Winsock Layered Service Providers (LSPs) +// - Global Windows Hooks (NOT thread-targeted hooks) +// - Legacy Input Method Editors (IMEs) +// I.e.: Disable legacy hooking mechanisms. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_EXTENSION_POINT_DISABLE_ALWAYS_ON. +const MitigationFlags MITIGATION_EXTENSION_POINT_DISABLE = 0x00001000; + +// Prevents the process from generating dynamic code or modifying executable +// code. Second option to allow thread-specific opt-out. +// - VirtualAlloc with PAGE_EXECUTE_* +// - VirtualProtect with PAGE_EXECUTE_* +// - MapViewOfFile with FILE_MAP_EXECUTE | FILE_MAP_WRITE +// - SetProcessValidCallTargets for CFG +// Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON and +// PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON_ALLOW_OPT_OUT. +const MitigationFlags MITIGATION_DYNAMIC_CODE_DISABLE = 0x00002000; +const MitigationFlags MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT = 0x00004000; +// The following per-thread flag can be used with the +// ApplyMitigationsToCurrentThread API. Requires the above process mitigation +// to be set on the current process. +const MitigationFlags MITIGATION_DYNAMIC_CODE_OPT_OUT_THIS_THREAD = 0x00008000; + +// Prevents the process from loading non-system fonts into GDI. +// Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_FONT_DISABLE_ALWAYS_ON +const MitigationFlags MITIGATION_NONSYSTEM_FONT_DISABLE = 0x00010000; + +// Prevents the process from loading binaries NOT signed by MS. +// Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON +const MitigationFlags MITIGATION_FORCE_MS_SIGNED_BINS = 0x00020000; + +// Blocks mapping of images from remote devices. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_NO_REMOTE_ALWAYS_ON. +const MitigationFlags MITIGATION_IMAGE_LOAD_NO_REMOTE = 0x00040000; + +// Blocks mapping of images that have the low manditory label. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_NO_LOW_LABEL_ALWAYS_ON. +const MitigationFlags MITIGATION_IMAGE_LOAD_NO_LOW_LABEL = 0x00080000; + +// Forces image load preference to prioritize the Windows install System32 +// folder before dll load dir, application dir and any user dirs set. +// - Affects IAT resolution standard search path only, NOT direct LoadLibrary or +// executable search path. +// PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON. +const MitigationFlags MITIGATION_IMAGE_LOAD_PREFER_SYS32 = 0x00100000; + +// Prevents hyperthreads from interfering with indirect branch predictions. +// (SPECTRE Variant 2 mitigation.) Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY2_RESTRICT_INDIRECT_BRANCH_PREDICTION_ALWAYS_ON. +const MitigationFlags MITIGATION_RESTRICT_INDIRECT_BRANCH_PREDICTION = + 0x00200000; + +// Begin Mozilla-added flags. +// Working down from the high bit to avoid conflict with new upstream flags. + +// Disable Control Flow Guard. This may seem more like an anti-mitigation, but +// this flag allows code to make targeted changes to CFG to avoid bugs, while +// leaving it enabled in the common case. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_ON. +const MitigationFlags MITIGATION_CONTROL_FLOW_GUARD_DISABLE = 0x80000000; + +// This enables CET User Shadow Stack for compatible modules and corresponds to +// PROCESS_CREATION_MITIGATION_POLICY2_CET_USER_SHADOW_STACKS_ALWAYS_ON. +const MitigationFlags MITIGATION_CET_COMPAT_MODE = 0x40000000; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SECURITY_LEVEL_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/service_resolver.cc b/security/sandbox/chromium/sandbox/win/src/service_resolver.cc new file mode 100644 index 0000000000..f26322dd63 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/service_resolver.cc @@ -0,0 +1,47 @@ +// Copyright 2013 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/win/src/service_resolver.h" + +#include "base/win/pe_image.h" +#include "sandbox/win/src/internal_types.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +NTSTATUS ServiceResolverThunk::ResolveInterceptor( + const void* interceptor_module, + const char* interceptor_name, + const void** address) { + // After all, we are using a locally mapped version of the exe, so the + // action is the same as for a target function. + return ResolveTarget(interceptor_module, interceptor_name, + const_cast<void**>(address)); +} + +// In this case all the work is done from the parent, so resolve is +// just a simple GetProcAddress. +NTSTATUS ServiceResolverThunk::ResolveTarget(const void* module, + const char* function_name, + void** address) { + if (!module) + return STATUS_UNSUCCESSFUL; + + base::win::PEImage module_image(module); + *address = + reinterpret_cast<void*>(module_image.GetProcAddress(function_name)); + + if (!*address) { + NOTREACHED_NT(); + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +} + +void ServiceResolverThunk::AllowLocalPatches() { + ntdll_base_ = ::GetModuleHandle(kNtdllName); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/service_resolver.h b/security/sandbox/chromium/sandbox/win/src/service_resolver.h new file mode 100644 index 0000000000..ee292f6a37 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/service_resolver.h @@ -0,0 +1,158 @@ +// 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. + +#ifndef SANDBOX_SRC_SERVICE_RESOLVER_H__ +#define SANDBOX_SRC_SERVICE_RESOLVER_H__ + +#include <stddef.h> + +#include "base/macros.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/resolver.h" + +namespace sandbox { + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll. +class ServiceResolverThunk : public ResolverThunk { + public: + // The service resolver needs a child process to write to. + ServiceResolverThunk(HANDLE process, bool relaxed) + : ntdll_base_(nullptr), + process_(process), + relaxed_(relaxed), + relative_jump_(0) {} + ~ServiceResolverThunk() override {} + + // Implementation of Resolver::Setup. + NTSTATUS Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) override; + + // Implementation of Resolver::ResolveInterceptor. + NTSTATUS ResolveInterceptor(const void* module, + const char* function_name, + const void** address) override; + + // Implementation of Resolver::ResolveTarget. + NTSTATUS ResolveTarget(const void* module, + const char* function_name, + void** address) override; + + // Implementation of Resolver::GetThunkSize. + size_t GetThunkSize() const override; + + // Call this to set up ntdll_base_ which will allow for local patches. + virtual void AllowLocalPatches(); + + // Verifies that the function specified by |target_name| in |target_module| is + // a service and copies the data from that function into |thunk_storage|. If + // |storage_bytes| is too small, then the method fails. + virtual NTSTATUS CopyThunk(const void* target_module, + const char* target_name, + BYTE* thunk_storage, + size_t storage_bytes, + size_t* storage_used); + + protected: + // The unit test will use this member to allow local patch on a buffer. + HMODULE ntdll_base_; + + // Handle of the child process. + HANDLE process_; + + private: + // Returns true if the code pointer by target_ corresponds to the expected + // type of function. Saves that code on the first part of the thunk pointed + // by local_thunk (should be directly accessible from the parent). + virtual bool IsFunctionAService(void* local_thunk) const; + + // Performs the actual patch of target_. + // local_thunk must be already fully initialized, and the first part must + // contain the original code. The real type of this buffer is ServiceFullThunk + // (yes, private). remote_thunk (real type ServiceFullThunk), must be + // allocated on the child, and will contain the thunk data, after this call. + // Returns the apropriate status code. + virtual NTSTATUS PerformPatch(void* local_thunk, void* remote_thunk); + + // Provides basically the same functionality as IsFunctionAService but it + // continues even if it does not recognize the function code. remote_thunk + // is the address of our memory on the child. + bool SaveOriginalFunction(void* local_thunk, void* remote_thunk); + + // true if we are allowed to patch already-patched functions. + bool relaxed_; + ULONG relative_jump_; + + DISALLOW_COPY_AND_ASSIGN(ServiceResolverThunk); +}; + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll on WOW64 (32 bit ntdll on 64 bit Vista). +class Wow64ResolverThunk : public ServiceResolverThunk { + public: + // The service resolver needs a child process to write to. + Wow64ResolverThunk(HANDLE process, bool relaxed) + : ServiceResolverThunk(process, relaxed) {} + ~Wow64ResolverThunk() override {} + + private: + bool IsFunctionAService(void* local_thunk) const override; + + DISALLOW_COPY_AND_ASSIGN(Wow64ResolverThunk); +}; + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll on WOW64 for Windows 8. +class Wow64W8ResolverThunk : public ServiceResolverThunk { + public: + // The service resolver needs a child process to write to. + Wow64W8ResolverThunk(HANDLE process, bool relaxed) + : ServiceResolverThunk(process, relaxed) {} + ~Wow64W8ResolverThunk() override {} + + private: + bool IsFunctionAService(void* local_thunk) const override; + + DISALLOW_COPY_AND_ASSIGN(Wow64W8ResolverThunk); +}; + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll on Windows 8. +class Win8ResolverThunk : public ServiceResolverThunk { + public: + // The service resolver needs a child process to write to. + Win8ResolverThunk(HANDLE process, bool relaxed) + : ServiceResolverThunk(process, relaxed) {} + ~Win8ResolverThunk() override {} + + private: + bool IsFunctionAService(void* local_thunk) const override; + + DISALLOW_COPY_AND_ASSIGN(Win8ResolverThunk); +}; + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll on WOW64 for Windows 10. +class Wow64W10ResolverThunk : public ServiceResolverThunk { + public: + // The service resolver needs a child process to write to. + Wow64W10ResolverThunk(HANDLE process, bool relaxed) + : ServiceResolverThunk(process, relaxed) {} + ~Wow64W10ResolverThunk() override {} + + private: + bool IsFunctionAService(void* local_thunk) const override; + + DISALLOW_COPY_AND_ASSIGN(Wow64W10ResolverThunk); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SERVICE_RESOLVER_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/service_resolver_32.cc b/security/sandbox/chromium/sandbox/win/src/service_resolver_32.cc new file mode 100644 index 0000000000..d95ce0086b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/service_resolver_32.cc @@ -0,0 +1,476 @@ +// 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 "sandbox/win/src/service_resolver.h" + +#include <stddef.h> + +#include <memory> + +#include "base/bit_cast.h" +#include "sandbox/win/src/win_utils.h" + +namespace { +#pragma pack(push, 1) + +const BYTE kMovEax = 0xB8; +const BYTE kMovEdx = 0xBA; +const USHORT kMovEdxEsp = 0xD48B; +const USHORT kCallPtrEdx = 0x12FF; +const USHORT kCallEdx = 0xD2FF; +const BYTE kCallEip = 0xE8; +const BYTE kRet = 0xC2; +const BYTE kRet2 = 0xC3; +const USHORT kJmpEdx = 0xE2FF; +const USHORT kXorEcx = 0xC933; +const ULONG kLeaEdx = 0x0424548D; +const ULONG kCallFs1 = 0xC015FF64; +const USHORT kCallFs2 = 0; +const BYTE kCallFs3 = 0; +const BYTE kAddEsp1 = 0x83; +const USHORT kAddEsp2 = 0x4C4; +const BYTE kJmp32 = 0xE9; +const USHORT kSysenter = 0x340F; + +// Service code for 32 bit systems. +// NOTE: on win2003 "call dword ptr [edx]" is "call edx". +struct ServiceEntry { + // This struct contains roughly the following code: + // 00 mov eax,25h + // 05 mov edx,offset SharedUserData!SystemCallStub (7ffe0300) + // 0a call dword ptr [edx] + // 0c ret 2Ch + // 0f nop + BYTE mov_eax; // = B8 + ULONG service_id; + BYTE mov_edx; // = BA + ULONG stub; + USHORT call_ptr_edx; // = FF 12 + BYTE ret; // = C2 + USHORT num_params; + BYTE nop; +}; + +// Service code for 32 bit Windows 8. +struct ServiceEntryW8 { + // This struct contains the following code: + // 00 b825000000 mov eax,25h + // 05 e803000000 call eip+3 + // 0a c22c00 ret 2Ch + // 0d 8bd4 mov edx,esp + // 0f 0f34 sysenter + // 11 c3 ret + // 12 8bff mov edi,edi + BYTE mov_eax; // = B8 + ULONG service_id; + BYTE call_eip; // = E8 + ULONG call_offset; + BYTE ret_p; // = C2 + USHORT num_params; + USHORT mov_edx_esp; // = BD D4 + USHORT sysenter; // = 0F 34 + BYTE ret; // = C3 + USHORT nop; +}; + +// Service code for a 32 bit process running on a 64 bit os. +struct Wow64Entry { + // This struct may contain one of two versions of code: + // 1. For XP, Vista and 2K3: + // 00 b825000000 mov eax, 25h + // 05 33c9 xor ecx, ecx + // 07 8d542404 lea edx, [esp + 4] + // 0b 64ff15c0000000 call dword ptr fs:[0C0h] + // 12 c22c00 ret 2Ch + // + // 2. For Windows 7: + // 00 b825000000 mov eax, 25h + // 05 33c9 xor ecx, ecx + // 07 8d542404 lea edx, [esp + 4] + // 0b 64ff15c0000000 call dword ptr fs:[0C0h] + // 12 83c404 add esp, 4 + // 15 c22c00 ret 2Ch + // + // So we base the structure on the bigger one: + BYTE mov_eax; // = B8 + ULONG service_id; + USHORT xor_ecx; // = 33 C9 + ULONG lea_edx; // = 8D 54 24 04 + ULONG call_fs1; // = 64 FF 15 C0 + USHORT call_fs2; // = 00 00 + BYTE call_fs3; // = 00 + BYTE add_esp1; // = 83 or ret + USHORT add_esp2; // = C4 04 or num_params + BYTE ret; // = C2 + USHORT num_params; +}; + +// Service code for a 32 bit process running on 64 bit Windows 8. +struct Wow64EntryW8 { + // 00 b825000000 mov eax, 25h + // 05 64ff15c0000000 call dword ptr fs:[0C0h] + // 0b c22c00 ret 2Ch + // 0f 90 nop + BYTE mov_eax; // = B8 + ULONG service_id; + ULONG call_fs1; // = 64 FF 15 C0 + USHORT call_fs2; // = 00 00 + BYTE call_fs3; // = 00 + BYTE ret; // = C2 + USHORT num_params; + BYTE nop; +}; + +// Service code for a 32 bit process running on 64 bit Windows 10. +struct Wow64EntryW10 { + // 00 b828000000 mov eax, 28h + // 05 bab0d54877 mov edx, 7748D5B0h + // 09 ffd2 call edx + // 0b c22800 ret 28h + BYTE mov_eax; // = B8 + ULONG service_id; + BYTE mov_edx; // = BA + ULONG mov_edx_param; + USHORT call_edx; // = FF D2 + BYTE ret; // = C2 + USHORT num_params; +}; + +// Make sure that relaxed patching works as expected. +const size_t kMinServiceSize = offsetof(ServiceEntry, ret); +static_assert(sizeof(ServiceEntryW8) >= kMinServiceSize, + "wrong service length"); +static_assert(sizeof(Wow64Entry) >= kMinServiceSize, "wrong service length"); +static_assert(sizeof(Wow64EntryW8) >= kMinServiceSize, "wrong service length"); + +struct ServiceFullThunk { + union { + ServiceEntry original; + ServiceEntryW8 original_w8; + Wow64Entry wow_64; + Wow64EntryW8 wow_64_w8; + }; + int internal_thunk; // Dummy member to the beginning of the internal thunk. +}; + +#pragma pack(pop) + +} // namespace + +namespace sandbox { + +NTSTATUS ServiceResolverThunk::Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = + Init(target_module, interceptor_module, target_name, interceptor_name, + interceptor_entry_point, thunk_storage, storage_bytes); + if (!NT_SUCCESS(ret)) + return ret; + + relative_jump_ = 0; + size_t thunk_bytes = GetThunkSize(); + std::unique_ptr<char[]> thunk_buffer(new char[thunk_bytes]); + ServiceFullThunk* thunk = + reinterpret_cast<ServiceFullThunk*>(thunk_buffer.get()); + + if (!IsFunctionAService(&thunk->original) && + (!relaxed_ || !SaveOriginalFunction(&thunk->original, thunk_storage))) { + return STATUS_OBJECT_NAME_COLLISION; + } + + ret = PerformPatch(thunk, thunk_storage); + + if (storage_used) + *storage_used = thunk_bytes; + + return ret; +} + +size_t ServiceResolverThunk::GetThunkSize() const { + return offsetof(ServiceFullThunk, internal_thunk) + GetInternalThunkSize(); +} + +NTSTATUS ServiceResolverThunk::CopyThunk(const void* target_module, + const char* target_name, + BYTE* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = ResolveTarget(target_module, target_name, &target_); + if (!NT_SUCCESS(ret)) + return ret; + + size_t thunk_bytes = GetThunkSize(); + if (storage_bytes < thunk_bytes) + return STATUS_UNSUCCESSFUL; + + ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>(thunk_storage); + + if (!IsFunctionAService(&thunk->original) && + (!relaxed_ || !SaveOriginalFunction(&thunk->original, thunk_storage))) { + return STATUS_OBJECT_NAME_COLLISION; + } + + if (storage_used) + *storage_used = thunk_bytes; + + return ret; +} + +bool ServiceResolverThunk::IsFunctionAService(void* local_thunk) const { + ServiceEntry function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) { + return false; + } + + if (sizeof(function_code) != read) + return false; + + if (kMovEax != function_code.mov_eax || kMovEdx != function_code.mov_edx || + (kCallPtrEdx != function_code.call_ptr_edx && + kCallEdx != function_code.call_ptr_edx) || + kRet != function_code.ret) { + return false; + } + + // Find the system call pointer if we don't already have it. + if (kCallEdx != function_code.call_ptr_edx) { + DWORD ki_system_call; + if (!::ReadProcessMemory(process_, + bit_cast<const void*>(function_code.stub), + &ki_system_call, sizeof(ki_system_call), &read)) { + return false; + } + + if (sizeof(ki_system_call) != read) + return false; + + HMODULE module_1, module_2; + // last check, call_stub should point to a KiXXSystemCall function on ntdll + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + bit_cast<const wchar_t*>(ki_system_call), + &module_1)) { + return false; + } + + if (ntdll_base_) { + // This path is only taken when running the unit tests. We want to be + // able to patch a buffer in memory, so target_ is not inside ntdll. + module_2 = ntdll_base_; + } else { + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<const wchar_t*>(target_), + &module_2)) + return false; + } + + if (module_1 != module_2) + return false; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +NTSTATUS ServiceResolverThunk::PerformPatch(void* local_thunk, + void* remote_thunk) { + ServiceEntry intercepted_code; + size_t bytes_to_write = sizeof(intercepted_code); + ServiceFullThunk* full_local_thunk = + reinterpret_cast<ServiceFullThunk*>(local_thunk); + ServiceFullThunk* full_remote_thunk = + reinterpret_cast<ServiceFullThunk*>(remote_thunk); + + // patch the original code + memcpy(&intercepted_code, &full_local_thunk->original, + sizeof(intercepted_code)); + intercepted_code.mov_eax = kMovEax; + intercepted_code.service_id = full_local_thunk->original.service_id; + intercepted_code.mov_edx = kMovEdx; + intercepted_code.stub = bit_cast<ULONG>(&full_remote_thunk->internal_thunk); + intercepted_code.call_ptr_edx = kJmpEdx; + bytes_to_write = kMinServiceSize; + + if (relative_jump_) { + intercepted_code.mov_eax = kJmp32; + intercepted_code.service_id = relative_jump_; + bytes_to_write = offsetof(ServiceEntry, mov_edx); + } + + // setup the thunk + SetInternalThunk(&full_local_thunk->internal_thunk, GetInternalThunkSize(), + remote_thunk, interceptor_); + + size_t thunk_size = GetThunkSize(); + + // copy the local thunk buffer to the child + SIZE_T written; + if (!::WriteProcessMemory(process_, remote_thunk, local_thunk, thunk_size, + &written)) { + return STATUS_UNSUCCESSFUL; + } + + if (thunk_size != written) + return STATUS_UNSUCCESSFUL; + + // and now change the function to intercept, on the child + if (ntdll_base_) { + // running a unit test + if (!::WriteProcessMemory(process_, target_, &intercepted_code, + bytes_to_write, &written)) + return STATUS_UNSUCCESSFUL; + } else { + if (!WriteProtectedChildMemory(process_, target_, &intercepted_code, + bytes_to_write)) + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +} + +bool ServiceResolverThunk::SaveOriginalFunction(void* local_thunk, + void* remote_thunk) { + ServiceEntry function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) { + return false; + } + + if (sizeof(function_code) != read) + return false; + + if (kJmp32 == function_code.mov_eax) { + // Plain old entry point patch. The relative jump address follows it. + ULONG relative = function_code.service_id; + + // First, fix our copy of their patch. + relative += bit_cast<ULONG>(target_) - bit_cast<ULONG>(remote_thunk); + + function_code.service_id = relative; + + // And now, remember how to re-patch it. + ServiceFullThunk* full_thunk = + reinterpret_cast<ServiceFullThunk*>(remote_thunk); + + const ULONG kJmp32Size = 5; + + relative_jump_ = bit_cast<ULONG>(&full_thunk->internal_thunk) - + bit_cast<ULONG>(target_) - kJmp32Size; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +bool Wow64ResolverThunk::IsFunctionAService(void* local_thunk) const { + Wow64Entry function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) { + return false; + } + + if (sizeof(function_code) != read) + return false; + + if (kMovEax != function_code.mov_eax || kXorEcx != function_code.xor_ecx || + kLeaEdx != function_code.lea_edx || kCallFs1 != function_code.call_fs1 || + kCallFs2 != function_code.call_fs2 || + kCallFs3 != function_code.call_fs3) { + return false; + } + + if ((kAddEsp1 == function_code.add_esp1 && + kAddEsp2 == function_code.add_esp2 && kRet == function_code.ret) || + kRet == function_code.add_esp1) { + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + return true; + } + + return false; +} + +bool Wow64W8ResolverThunk::IsFunctionAService(void* local_thunk) const { + Wow64EntryW8 function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) { + return false; + } + + if (sizeof(function_code) != read) + return false; + + if (kMovEax != function_code.mov_eax || kCallFs1 != function_code.call_fs1 || + kCallFs2 != function_code.call_fs2 || + kCallFs3 != function_code.call_fs3 || kRet != function_code.ret) { + return false; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + return true; +} + +bool Win8ResolverThunk::IsFunctionAService(void* local_thunk) const { + ServiceEntryW8 function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) { + return false; + } + + if (sizeof(function_code) != read) + return false; + + if (kMovEax != function_code.mov_eax || kCallEip != function_code.call_eip || + function_code.call_offset != 3 || kRet != function_code.ret_p || + kMovEdxEsp != function_code.mov_edx_esp || + kSysenter != function_code.sysenter || kRet2 != function_code.ret) { + return false; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +bool Wow64W10ResolverThunk::IsFunctionAService(void* local_thunk) const { + Wow64EntryW10 function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) { + return false; + } + + if (sizeof(function_code) != read) + return false; + + if (kMovEax != function_code.mov_eax || kMovEdx != function_code.mov_edx || + kCallEdx != function_code.call_edx || kRet != function_code.ret) { + return false; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/service_resolver_64.cc b/security/sandbox/chromium/sandbox/win/src/service_resolver_64.cc new file mode 100644 index 0000000000..f692d0ea4f --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/service_resolver_64.cc @@ -0,0 +1,290 @@ +// 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 "sandbox/win/src/service_resolver.h" + +#include <stddef.h> + +#include <memory> + +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/win_utils.h" + +namespace { +#if defined(_M_X64) +#pragma pack(push, 1) + +const ULONG kMmovR10EcxMovEax = 0xB8D18B4C; +const USHORT kSyscall = 0x050F; +const BYTE kRetNp = 0xC3; +const ULONG64 kMov1 = 0x54894808244C8948; +const ULONG64 kMov2 = 0x4C182444894C1024; +const ULONG kMov3 = 0x20244C89; +const USHORT kTestByte = 0x04F6; +const BYTE kPtr = 0x25; +const BYTE kRet = 0xC3; +const USHORT kJne = 0x0375; + +// Service code for 64 bit systems. +struct ServiceEntry { + // This struct contains roughly the following code: + // 00 mov r10,rcx + // 03 mov eax,52h + // 08 syscall + // 0a ret + // 0b xchg ax,ax + // 0e xchg ax,ax + + ULONG mov_r10_rcx_mov_eax; // = 4C 8B D1 B8 + ULONG service_id; + USHORT syscall; // = 0F 05 + BYTE ret; // = C3 + BYTE pad; // = 66 + USHORT xchg_ax_ax1; // = 66 90 + USHORT xchg_ax_ax2; // = 66 90 +}; + +// Service code for 64 bit Windows 8. +struct ServiceEntryW8 { + // This struct contains the following code: + // 00 48894c2408 mov [rsp+8], rcx + // 05 4889542410 mov [rsp+10], rdx + // 0a 4c89442418 mov [rsp+18], r8 + // 0f 4c894c2420 mov [rsp+20], r9 + // 14 4c8bd1 mov r10,rcx + // 17 b825000000 mov eax,25h + // 1c 0f05 syscall + // 1e c3 ret + // 1f 90 nop + + ULONG64 mov_1; // = 48 89 4C 24 08 48 89 54 + ULONG64 mov_2; // = 24 10 4C 89 44 24 18 4C + ULONG mov_3; // = 89 4C 24 20 + ULONG mov_r10_rcx_mov_eax; // = 4C 8B D1 B8 + ULONG service_id; + USHORT syscall; // = 0F 05 + BYTE ret; // = C3 + BYTE nop; // = 90 +}; + +// Service code for 64 bit systems with int 2e fallback. +struct ServiceEntryWithInt2E { + // This struct contains roughly the following code: + // 00 4c8bd1 mov r10,rcx + // 03 b855000000 mov eax,52h + // 08 f604250803fe7f01 test byte ptr SharedUserData!308, 1 + // 10 7503 jne [over syscall] + // 12 0f05 syscall + // 14 c3 ret + // 15 cd2e int 2e + // 17 c3 ret + + ULONG mov_r10_rcx_mov_eax; // = 4C 8B D1 B8 + ULONG service_id; + USHORT test_byte; // = F6 04 + BYTE ptr; // = 25 + ULONG user_shared_data_ptr; + BYTE one; // = 01 + USHORT jne_over_syscall; // = 75 03 + USHORT syscall; // = 0F 05 + BYTE ret; // = C3 + USHORT int2e; // = CD 2E + BYTE ret2; // = C3 +}; + +// We don't have an internal thunk for x64. +struct ServiceFullThunk { + union { + ServiceEntry original; + ServiceEntryW8 original_w8; + ServiceEntryWithInt2E original_int2e_fallback; + }; +}; + +#pragma pack(pop) + +bool IsService(const void* source) { + const ServiceEntry* service = reinterpret_cast<const ServiceEntry*>(source); + + return (kMmovR10EcxMovEax == service->mov_r10_rcx_mov_eax && + kSyscall == service->syscall && kRetNp == service->ret); +} + +bool IsServiceW8(const void* source) { + const ServiceEntryW8* service = + reinterpret_cast<const ServiceEntryW8*>(source); + + return (kMmovR10EcxMovEax == service->mov_r10_rcx_mov_eax && + kMov1 == service->mov_1 && kMov2 == service->mov_2 && + kMov3 == service->mov_3); +} + +bool IsServiceWithInt2E(const void* source) { + const ServiceEntryWithInt2E* service = + reinterpret_cast<const ServiceEntryWithInt2E*>(source); + + return (kMmovR10EcxMovEax == service->mov_r10_rcx_mov_eax && + kTestByte == service->test_byte && kPtr == service->ptr && + kJne == service->jne_over_syscall && kSyscall == service->syscall && + kRet == service->ret && kRet == service->ret2); +} + +bool IsAnyService(const void* source) { + return IsService(source) || IsServiceW8(source) || IsServiceWithInt2E(source); +} + +#elif defined(_M_ARM64) +#pragma pack(push, 4) + +const ULONG kSvc = 0xD4000001; +const ULONG kRetNp = 0xD65F03C0; +const ULONG kServiceIdMask = 0x001FFFE0; + +struct ServiceEntry { + ULONG svc; + ULONG ret; + ULONG64 unused; +}; + +struct ServiceFullThunk { + ServiceEntry original; +}; + +#pragma pack(pop) + +bool IsService(const void* source) { + const ServiceEntry* service = reinterpret_cast<const ServiceEntry*>(source); + + return (kSvc == (service->svc & ~kServiceIdMask) && kRetNp == service->ret && + 0 == service->unused); +} + +bool IsAnyService(const void* source) { + return IsService(source); +} + +#else +#error "Unsupported Windows 64-bit Arch" +#endif + +} // namespace + +namespace sandbox { + +NTSTATUS ServiceResolverThunk::Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = + Init(target_module, interceptor_module, target_name, interceptor_name, + interceptor_entry_point, thunk_storage, storage_bytes); + if (!NT_SUCCESS(ret)) + return ret; + + size_t thunk_bytes = GetThunkSize(); + std::unique_ptr<char[]> thunk_buffer(new char[thunk_bytes]); + ServiceFullThunk* thunk = + reinterpret_cast<ServiceFullThunk*>(thunk_buffer.get()); + + if (!IsFunctionAService(&thunk->original)) + return STATUS_OBJECT_NAME_COLLISION; + + ret = PerformPatch(thunk, thunk_storage); + + if (storage_used) + *storage_used = thunk_bytes; + + return ret; +} + +size_t ServiceResolverThunk::GetThunkSize() const { + return sizeof(ServiceFullThunk); +} + +NTSTATUS ServiceResolverThunk::CopyThunk(const void* target_module, + const char* target_name, + BYTE* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = ResolveTarget(target_module, target_name, &target_); + if (!NT_SUCCESS(ret)) + return ret; + + size_t thunk_bytes = GetThunkSize(); + if (storage_bytes < thunk_bytes) + return STATUS_UNSUCCESSFUL; + + ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>(thunk_storage); + + if (!IsFunctionAService(&thunk->original)) + return STATUS_OBJECT_NAME_COLLISION; + + if (storage_used) + *storage_used = thunk_bytes; + + return ret; +} + +bool ServiceResolverThunk::IsFunctionAService(void* local_thunk) const { + ServiceFullThunk function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) + return false; + + if (sizeof(function_code) != read) + return false; + + if (!IsAnyService(&function_code)) + return false; + + // Save the verified code. + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +NTSTATUS ServiceResolverThunk::PerformPatch(void* local_thunk, + void* remote_thunk) { + // Patch the original code. + ServiceEntry local_service; + DCHECK_NT(GetInternalThunkSize() <= sizeof(local_service)); + if (!SetInternalThunk(&local_service, sizeof(local_service), nullptr, + interceptor_)) + return STATUS_UNSUCCESSFUL; + + // Copy the local thunk buffer to the child. + SIZE_T actual; + if (!::WriteProcessMemory(process_, remote_thunk, local_thunk, + sizeof(ServiceFullThunk), &actual)) + return STATUS_UNSUCCESSFUL; + + if (sizeof(ServiceFullThunk) != actual) + return STATUS_UNSUCCESSFUL; + + // And now change the function to intercept, on the child. + if (ntdll_base_) { + // Running a unit test. + if (!::WriteProcessMemory(process_, target_, &local_service, + sizeof(local_service), &actual)) + return STATUS_UNSUCCESSFUL; + } else { + if (!WriteProtectedChildMemory(process_, target_, &local_service, + sizeof(local_service))) + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +} + +bool Wow64ResolverThunk::IsFunctionAService(void* local_thunk) const { + NOTREACHED_NT(); + return false; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/service_resolver_unittest.cc b/security/sandbox/chromium/sandbox/win/src/service_resolver_unittest.cc new file mode 100644 index 0000000000..63ea26dcbf --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/service_resolver_unittest.cc @@ -0,0 +1,278 @@ +// 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. + +// This file contains unit tests for ServiceResolverThunk. + +#include "sandbox/win/src/service_resolver.h" + +#include <stddef.h> + +#include <memory> + +#include "base/bit_cast.h" +#include "base/macros.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/resolver.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class ResolverThunkTest { + public: + virtual ~ResolverThunkTest() {} + + virtual sandbox::ServiceResolverThunk* resolver() = 0; + + // Sets the interception target to the desired address. + void set_target(void* target) { fake_target_ = target; } + + protected: + // Holds the address of the fake target. + void* fake_target_; +}; + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll. +template <typename T> +class ResolverThunkTestImpl : public T, public ResolverThunkTest { + public: + // The service resolver needs a child process to write to. + explicit ResolverThunkTestImpl(bool relaxed) + : T(::GetCurrentProcess(), relaxed) {} + + sandbox::ServiceResolverThunk* resolver() { return this; } + + protected: + // Overrides Resolver::Init + virtual NTSTATUS Init(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes) { + NTSTATUS ret = STATUS_SUCCESS; + ret = T::Init(target_module, interceptor_module, target_name, + interceptor_name, interceptor_entry_point, thunk_storage, + storage_bytes); + EXPECT_EQ(STATUS_SUCCESS, ret); + + this->target_ = fake_target_; + + return ret; + } + + DISALLOW_COPY_AND_ASSIGN(ResolverThunkTestImpl); +}; + +typedef ResolverThunkTestImpl<sandbox::ServiceResolverThunk> WinXpResolverTest; + +#if !defined(_WIN64) +typedef ResolverThunkTestImpl<sandbox::Win8ResolverThunk> Win8ResolverTest; +typedef ResolverThunkTestImpl<sandbox::Wow64ResolverThunk> Wow64ResolverTest; +typedef ResolverThunkTestImpl<sandbox::Wow64W8ResolverThunk> + Wow64W8ResolverTest; +typedef ResolverThunkTestImpl<sandbox::Wow64W10ResolverThunk> + Wow64W10ResolverTest; +#endif + +const BYTE kJump32 = 0xE9; + +void CheckJump(void* source, void* target) { +#pragma pack(push) +#pragma pack(1) + struct Code { + BYTE jump; + ULONG delta; + }; +#pragma pack(pop) + +#if defined(_WIN64) + FAIL() << "Running 32-bit codepath"; +#else + Code* patched = reinterpret_cast<Code*>(source); + EXPECT_EQ(kJump32, patched->jump); + + ULONG source_addr = bit_cast<ULONG>(source); + ULONG target_addr = bit_cast<ULONG>(target); + EXPECT_EQ(target_addr + 19 - source_addr, patched->delta); +#endif +} + +NTSTATUS PatchNtdllWithResolver(const char* function, + bool relaxed, + ResolverThunkTest* thunk_test) { + HMODULE ntdll_base = ::GetModuleHandle(L"ntdll.dll"); + EXPECT_TRUE(ntdll_base); + + void* target = + reinterpret_cast<void*>(::GetProcAddress(ntdll_base, function)); + EXPECT_TRUE(target); + if (!target) + return STATUS_UNSUCCESSFUL; + + BYTE service[50]; + memcpy(service, target, sizeof(service)); + + thunk_test->set_target(service); + + sandbox::ServiceResolverThunk* resolver = thunk_test->resolver(); + // Any pointer will do as an interception_entry_point + void* function_entry = resolver; + size_t thunk_size = resolver->GetThunkSize(); + std::unique_ptr<char[]> thunk(new char[thunk_size]); + size_t used; + + resolver->AllowLocalPatches(); + + NTSTATUS ret = + resolver->Setup(ntdll_base, nullptr, function, nullptr, function_entry, + thunk.get(), thunk_size, &used); + if (NT_SUCCESS(ret)) { + EXPECT_EQ(thunk_size, used); + EXPECT_NE(0, memcmp(service, target, sizeof(service))); + EXPECT_NE(kJump32, service[0]); + + if (relaxed) { + // It's already patched, let's patch again, and simulate a direct patch. + service[0] = kJump32; + ret = resolver->Setup(ntdll_base, nullptr, function, nullptr, + function_entry, thunk.get(), thunk_size, &used); + CheckJump(service, thunk.get()); + } + } + + return ret; +} + +std::unique_ptr<ResolverThunkTest> GetTestResolver(bool relaxed) { +#if defined(_WIN64) + return std::make_unique<WinXpResolverTest>(relaxed); +#else + base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); + if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) { + if (os_info->version() >= base::win::Version::WIN10) + return std::make_unique<Wow64W10ResolverTest>(relaxed); + if (os_info->version() >= base::win::Version::WIN8) + return std::make_unique<Wow64W8ResolverTest>(relaxed); + return std::make_unique<Wow64ResolverTest>(relaxed); + } + + if (os_info->version() >= base::win::Version::WIN8) + return std::make_unique<Win8ResolverTest>(relaxed); + + return std::make_unique<WinXpResolverTest>(relaxed); +#endif +} + +NTSTATUS PatchNtdll(const char* function, bool relaxed) { + std::unique_ptr<ResolverThunkTest> thunk_test = GetTestResolver(relaxed); + return PatchNtdllWithResolver(function, relaxed, thunk_test.get()); +} + +TEST(ServiceResolverTest, PatchesServices) { + NTSTATUS ret = PatchNtdll("NtClose", false); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError(); + + ret = PatchNtdll("NtCreateFile", false); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtCreateFile, last error: " << ::GetLastError(); + + ret = PatchNtdll("NtCreateMutant", false); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtCreateMutant, last error: " << ::GetLastError(); + + ret = PatchNtdll("NtMapViewOfSection", false); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtMapViewOfSection, last error: " << ::GetLastError(); +} + +TEST(ServiceResolverTest, FailsIfNotService) { +#if !defined(_WIN64) + EXPECT_NE(STATUS_SUCCESS, PatchNtdll("RtlUlongByteSwap", false)); +#endif + + EXPECT_NE(STATUS_SUCCESS, PatchNtdll("LdrLoadDll", false)); +} + +TEST(ServiceResolverTest, PatchesPatchedServices) { +// We don't support "relaxed mode" for Win64 apps. +#if !defined(_WIN64) + NTSTATUS ret = PatchNtdll("NtClose", true); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError(); + + ret = PatchNtdll("NtCreateFile", true); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtCreateFile, last error: " << ::GetLastError(); + + ret = PatchNtdll("NtCreateMutant", true); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtCreateMutant, last error: " << ::GetLastError(); + + ret = PatchNtdll("NtMapViewOfSection", true); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtMapViewOfSection, last error: " << ::GetLastError(); +#endif +} + +TEST(ServiceResolverTest, MultiplePatchedServices) { +// We don't support "relaxed mode" for Win64 apps. +#if !defined(_WIN64) + std::unique_ptr<ResolverThunkTest> thunk_test = GetTestResolver(true); + NTSTATUS ret = PatchNtdllWithResolver("NtClose", true, thunk_test.get()); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError(); + + ret = PatchNtdllWithResolver("NtCreateFile", true, thunk_test.get()); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtCreateFile, last error: " << ::GetLastError(); + + ret = PatchNtdllWithResolver("NtCreateMutant", true, thunk_test.get()); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtCreateMutant, last error: " << ::GetLastError(); + + ret = PatchNtdllWithResolver("NtMapViewOfSection", true, thunk_test.get()); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtMapViewOfSection, last error: " << ::GetLastError(); +#endif +} + +TEST(ServiceResolverTest, LocalPatchesAllowed) { + std::unique_ptr<ResolverThunkTest> thunk_test = GetTestResolver(true); + + HMODULE ntdll_base = ::GetModuleHandle(L"ntdll.dll"); + ASSERT_TRUE(ntdll_base); + + const char kFunctionName[] = "NtClose"; + + void* target = + reinterpret_cast<void*>(::GetProcAddress(ntdll_base, kFunctionName)); + ASSERT_TRUE(target); + + BYTE service[50]; + memcpy(service, target, sizeof(service)); + thunk_test->set_target(service); + + sandbox::ServiceResolverThunk* resolver = thunk_test->resolver(); + // Any pointer will do as an interception_entry_point + void* function_entry = resolver; + size_t thunk_size = resolver->GetThunkSize(); + std::unique_ptr<char[]> thunk(new char[thunk_size]); + size_t used; + + NTSTATUS ret = STATUS_UNSUCCESSFUL; + + // First try patching without having allowed local patches. + ret = resolver->Setup(ntdll_base, nullptr, kFunctionName, nullptr, + function_entry, thunk.get(), thunk_size, &used); + EXPECT_FALSE(NT_SUCCESS(ret)); + + // Now allow local patches and check that things work. + resolver->AllowLocalPatches(); + ret = resolver->Setup(ntdll_base, nullptr, kFunctionName, nullptr, + function_entry, thunk.get(), thunk_size, &used); + EXPECT_EQ(STATUS_SUCCESS, ret); +} + +} // namespace diff --git a/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_client.cc b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_client.cc new file mode 100644 index 0000000000..3ddea76350 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_client.cc @@ -0,0 +1,193 @@ +// Copyright (c) 2006-2008 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/win/src/sharedmem_ipc_client.h" + +#include <stddef.h> +#include <string.h> + +#include "base/logging.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +SANDBOX_INTERCEPT NtExports g_nt; + +namespace { + +DWORD SignalObjectAndWaitWrapper(HANDLE object_to_signal, + HANDLE object_to_wait_on, + DWORD millis, + BOOL alertable) { + // Not running in a sandboxed process so can call directly. + if (!g_nt.SignalAndWaitForSingleObject) + return SignalObjectAndWait(object_to_signal, object_to_wait_on, millis, + alertable); + // Don't support alertable. + CHECK_NT(!alertable); + LARGE_INTEGER timeout; + timeout.QuadPart = millis * -10000LL; + NTSTATUS status = g_nt.SignalAndWaitForSingleObject( + object_to_signal, object_to_wait_on, alertable, + millis == INFINITE ? nullptr : &timeout); + if (!NT_SUCCESS(status)) + return WAIT_FAILED; + return status; +} + +DWORD WaitForSingleObjectWrapper(HANDLE handle, DWORD millis) { + // Not running in a sandboxed process so can call directly. + if (!g_nt.WaitForSingleObject) + return WaitForSingleObject(handle, millis); + LARGE_INTEGER timeout; + timeout.QuadPart = millis * -10000LL; + NTSTATUS status = g_nt.WaitForSingleObject( + handle, FALSE, millis == INFINITE ? nullptr : &timeout); + if (!NT_SUCCESS(status)) + return WAIT_FAILED; + return status; +} + +} // namespace + +// Get the base of the data buffer of the channel; this is where the input +// parameters get serialized. Since they get serialized directly into the +// channel we avoid one copy. +void* SharedMemIPCClient::GetBuffer() { + bool failure = false; + size_t ix = LockFreeChannel(&failure); + if (failure) + return nullptr; + return reinterpret_cast<char*>(control_) + + control_->channels[ix].channel_base; +} + +// If we need to cancel an IPC before issuing DoCall +// our client should call FreeBuffer with the same pointer +// returned by GetBuffer. +void SharedMemIPCClient::FreeBuffer(void* buffer) { + size_t num = ChannelIndexFromBuffer(buffer); + ChannelControl* channel = control_->channels; + LONG result = ::InterlockedExchange(&channel[num].state, kFreeChannel); + DCHECK_NE(kFreeChannel, static_cast<ChannelState>(result)); +} + +// The constructor simply casts the shared memory to the internal +// structures. This is a cheap step that is why this IPC object can +// and should be constructed per call. +SharedMemIPCClient::SharedMemIPCClient(void* shared_mem) + : control_(reinterpret_cast<IPCControl*>(shared_mem)) { + first_base_ = + reinterpret_cast<char*>(shared_mem) + control_->channels[0].channel_base; + // There must be at least one channel. + DCHECK(0 != control_->channels_count); +} + +// Do the IPC. At this point the channel should have already been +// filled with the serialized input parameters. +// We follow the pattern explained in the header file. +ResultCode SharedMemIPCClient::DoCall(CrossCallParams* params, + CrossCallReturn* answer) { + if (!control_->server_alive) + return SBOX_ERROR_CHANNEL_ERROR; + + size_t num = ChannelIndexFromBuffer(params->GetBuffer()); + ChannelControl* channel = control_->channels; + // Note that the IPC tag goes outside the buffer as well inside + // the buffer. This should enable the server to prioritize based on + // IPC tags without having to de-serialize the entire message. + channel[num].ipc_tag = params->GetTag(); + + // Wait for the server to service this IPC call. After kIPCWaitTimeOut1 + // we check if the server_alive mutex was abandoned which will indicate + // that the server has died. + + // While the atomic signaling and waiting is not a requirement, it + // is nice because we save a trip to kernel. + DWORD wait = SignalObjectAndWaitWrapper(channel[num].ping_event, + channel[num].pong_event, + kIPCWaitTimeOut1, false); + if (WAIT_TIMEOUT == wait) { + // The server is taking too long. Enter a loop were we check if the + // server_alive mutex has been abandoned which would signal a server crash + // or else we keep waiting for a response. + while (true) { + wait = WaitForSingleObjectWrapper(control_->server_alive, 0); + if (WAIT_TIMEOUT == wait) { + // Server seems still alive. We already signaled so here we just wait. + wait = WaitForSingleObjectWrapper(channel[num].pong_event, + kIPCWaitTimeOut1); + if (WAIT_OBJECT_0 == wait) { + // The server took a long time but responded. + break; + } else if (WAIT_TIMEOUT == wait) { + continue; + } else { + return SBOX_ERROR_CHANNEL_ERROR; + } + } else { + // The server has crashed and windows has signaled the mutex as + // abandoned. + ::InterlockedExchange(&channel[num].state, kAbandonedChannel); + control_->server_alive = 0; + return SBOX_ERROR_CHANNEL_ERROR; + } + } + } else if (WAIT_OBJECT_0 != wait) { + // Probably the server crashed before the kIPCWaitTimeOut1 occurred. + return SBOX_ERROR_CHANNEL_ERROR; + } + + // The server has returned an answer, copy it and free the channel. + memcpy_wrapper(answer, params->GetCallReturn(), sizeof(CrossCallReturn)); + + // Return the IPC state It can indicate that while the IPC has + // completed some error in the Broker has caused to not return valid + // results. + return answer->call_outcome; +} + +// Locking a channel is a simple as looping over all the channels +// looking for one that is has state = kFreeChannel and atomically +// swapping it to kBusyChannel. +// If there is no free channel, then we must back off so some other +// thread makes progress and frees a channel. To back off we sleep. +size_t SharedMemIPCClient::LockFreeChannel(bool* severe_failure) { + if (0 == control_->channels_count) { + *severe_failure = true; + return 0; + } + ChannelControl* channel = control_->channels; + do { + for (size_t ix = 0; ix != control_->channels_count; ++ix) { + if (kFreeChannel == ::InterlockedCompareExchange( + &channel[ix].state, kBusyChannel, kFreeChannel)) { + *severe_failure = false; + return ix; + } + } + // We did not find any available channel, maybe the server is dead. + DWORD wait = + WaitForSingleObjectWrapper(control_->server_alive, kIPCWaitTimeOut2); + if (WAIT_TIMEOUT != wait) { + // The server is dead and we outlive it enough to get in trouble. + *severe_failure = true; + return 0; + } + } while (true); +} + +// Find out which channel we are from the pointer returned by GetBuffer. +size_t SharedMemIPCClient::ChannelIndexFromBuffer(const void* buffer) { + ptrdiff_t d = reinterpret_cast<const char*>(buffer) - first_base_; + size_t num = d / kIPCChannelSize; + DCHECK_LT(num, control_->channels_count); + return (num); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_client.h b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_client.h new file mode 100644 index 0000000000..73f739dece --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_client.h @@ -0,0 +1,140 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_SRC_SHAREDMEM_IPC_CLIENT_H__ +#define SANDBOX_SRC_SHAREDMEM_IPC_CLIENT_H__ + +#include <stddef.h> +#include <stdint.h> + +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox.h" + +// IPC transport implementation that uses shared memory. +// This is the client side +// +// The shared memory is divided on blocks called channels, and potentially +// it can perform as many concurrent IPC calls as channels. The IPC over +// each channel is strictly synchronous for the client. +// +// Each channel as a channel control section associated with. Each control +// section has two kernel events (known as ping and pong) and a integer +// variable that maintains a state +// +// this is the state diagram of a channel: +// +// locked in service +// kFreeChannel---------->BusyChannel-------------->kAckChannel +// ^ | +// |_________________________________________________| +// answer ready +// +// The protocol is as follows: +// 1) client finds a free channel: state = kFreeChannel +// 2) does an atomic compare-and-swap, now state = BusyChannel +// 3) client writes the data into the channel buffer +// 4) client signals the ping event and waits (blocks) on the pong event +// 5) eventually the server signals the pong event +// 6) the client awakes and reads the answer from the same channel +// 7) the client updates its InOut parameters with the new data from the +// shared memory section. +// 8) the client atomically sets the state = kFreeChannel +// +// In the shared memory the layout is as follows: +// +// [ channel count ] +// [ channel control 0] +// [ channel control 1] +// [ channel control N] +// [ channel buffer 0 ] 1024 bytes +// [ channel buffer 1 ] 1024 bytes +// [ channel buffer N ] 1024 bytes +// +// By default each channel buffer is 1024 bytes +namespace sandbox { + +// the possible channel states as described above +enum ChannelState { + // channel is free + kFreeChannel = 1, + // IPC in progress client side + kBusyChannel, + // IPC in progress server side + kAckChannel, + // not used right now + kReadyChannel, + // IPC abandoned by client side + kAbandonedChannel +}; + +// The next two constants control the time outs for the IPC. +const DWORD kIPCWaitTimeOut1 = 1000; // Milliseconds. +const DWORD kIPCWaitTimeOut2 = 50; // Milliseconds. + +// the channel control structure +struct ChannelControl { + // points to be beginning of the channel buffer, where data goes + size_t channel_base; + // maintains the state from the ChannelState enumeration + volatile LONG state; + // the ping event is signaled by the client when the IPC data is ready on + // the buffer + HANDLE ping_event; + // the client waits on the pong event for the IPC answer back + HANDLE pong_event; + // the IPC unique identifier + IpcTag ipc_tag; +}; + +struct IPCControl { + // total number of channels available, some might be busy at a given time + size_t channels_count; + // handle to a shared mutex to detect when the server is dead + HANDLE server_alive; + // array of channel control structures + ChannelControl channels[1]; +}; + +// the actual shared memory IPC implementation class. This object is designed +// to be lightweight so it can be constructed on-site (at the calling place) +// wherever an IPC call is needed. +class SharedMemIPCClient { + public: + // Creates the IPC client. + // as parameter it takes the base address of the shared memory + explicit SharedMemIPCClient(void* shared_mem); + + // locks a free channel and returns the channel buffer memory base. This call + // blocks until there is a free channel + void* GetBuffer(); + + // releases the lock on the channel, for other to use. call this if you have + // called GetBuffer and you want to abort but have not called yet DoCall() + void FreeBuffer(void* buffer); + + // Performs the actual IPC call. + // params: The blob of packed input parameters. + // answer: upon IPC completion, it contains the server answer to the IPC. + // If the return value is not SBOX_ERROR_CHANNEL_ERROR, the caller has to free + // the channel. + // returns ALL_OK if the IPC mechanism successfully delivered. You still need + // to check on the answer structure to see the actual IPC result. + ResultCode DoCall(CrossCallParams* params, CrossCallReturn* answer); + + private: + // Returns the index of the first free channel. It sets 'severe_failure' + // to true if there is an unrecoverable error that does not allow to + // find a channel. + size_t LockFreeChannel(bool* severe_failure); + // Return the channel index given the address of the buffer. + size_t ChannelIndexFromBuffer(const void* buffer); + IPCControl* control_; + // point to the first channel base + char* first_base_; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SHAREDMEM_IPC_CLIENT_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_server.cc b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_server.cc new file mode 100644 index 0000000000..74e5c171e0 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_server.cc @@ -0,0 +1,346 @@ +// 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 "sandbox/win/src/sharedmem_ipc_server.h" + +#include <stddef.h> +#include <stdint.h> + +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/ipc_args.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" + +namespace { +// This handle must not be closed. +volatile HANDLE g_alive_mutex = nullptr; +} // namespace + +namespace sandbox { + +SharedMemIPCServer::ServerControl::ServerControl() {} + +SharedMemIPCServer::ServerControl::~ServerControl() {} + +SharedMemIPCServer::SharedMemIPCServer(HANDLE target_process, + DWORD target_process_id, + ThreadProvider* thread_provider, + Dispatcher* dispatcher) + : client_control_(nullptr), + thread_provider_(thread_provider), + target_process_(target_process), + target_process_id_(target_process_id), + call_dispatcher_(dispatcher) { + // We create a initially owned mutex. If the server dies unexpectedly, + // the thread that owns it will fail to release the lock and windows will + // report to the target (when it tries to acquire it) that the wait was + // abandoned. Note: We purposely leak the local handle because we want it to + // be closed by Windows itself so it is properly marked as abandoned if the + // server dies. + if (!g_alive_mutex) { + HANDLE mutex = ::CreateMutexW(nullptr, true, nullptr); + if (::InterlockedCompareExchangePointer(&g_alive_mutex, mutex, nullptr)) { + // We lost the race to create the mutex. + ::CloseHandle(mutex); + } + } +} + +SharedMemIPCServer::~SharedMemIPCServer() { + // Free the wait handles associated with the thread pool. + if (!thread_provider_->UnRegisterWaits(this)) { + // Better to leak than to crash. + return; + } + server_contexts_.clear(); + + if (client_control_) + ::UnmapViewOfFile(client_control_); +} + +bool SharedMemIPCServer::Init(void* shared_mem, + uint32_t shared_size, + uint32_t channel_size) { + // The shared memory needs to be at least as big as a channel. + if (shared_size < channel_size) { + return false; + } + // The channel size should be aligned. + if (0 != (channel_size % 32)) { + return false; + } + + // Calculate how many channels we can fit in the shared memory. + shared_size -= offsetof(IPCControl, channels); + size_t channel_count = shared_size / (sizeof(ChannelControl) + channel_size); + + // If we cannot fit even one channel we bail out. + if (0 == channel_count) { + return false; + } + // Calculate the start of the first channel. + size_t base_start = + (sizeof(ChannelControl) * channel_count) + offsetof(IPCControl, channels); + + client_control_ = reinterpret_cast<IPCControl*>(shared_mem); + client_control_->channels_count = 0; + + // This is the initialization that we do per-channel. Basically: + // 1) make two events (ping & pong) + // 2) create handles to the events for the client and the server. + // 3) initialize the channel (client_context) with the state. + // 4) initialize the server side of the channel (service_context). + // 5) call the thread provider RegisterWait to register the ping events. + for (size_t ix = 0; ix != channel_count; ++ix) { + ChannelControl* client_context = &client_control_->channels[ix]; + ServerControl* service_context = new ServerControl; + server_contexts_.push_back(base::WrapUnique(service_context)); + + if (!MakeEvents(&service_context->ping_event, &service_context->pong_event, + &client_context->ping_event, &client_context->pong_event)) { + return false; + } + + client_context->channel_base = base_start; + client_context->state = kFreeChannel; + + // Note that some of these values are available as members of this object + // but we put them again into the service_context because we will be called + // on a static method (ThreadPingEventReady). In particular, target_process_ + // is a raw handle that is not owned by this object (it's owned by the + // owner of this object), and we are storing it in multiple places. + service_context->shared_base = reinterpret_cast<char*>(shared_mem); + service_context->channel_size = channel_size; + service_context->channel = client_context; + service_context->channel_buffer = + service_context->shared_base + client_context->channel_base; + service_context->dispatcher = call_dispatcher_; + service_context->target_info.process = target_process_; + service_context->target_info.process_id = target_process_id_; + // Advance to the next channel. + base_start += channel_size; + // Register the ping event with the threadpool. + thread_provider_->RegisterWait(this, service_context->ping_event.Get(), + ThreadPingEventReady, service_context); + } + if (!::DuplicateHandle(::GetCurrentProcess(), g_alive_mutex, target_process_, + &client_control_->server_alive, + SYNCHRONIZE | EVENT_MODIFY_STATE, false, 0)) { + return false; + } + // This last setting indicates to the client all is setup. + client_control_->channels_count = channel_count; + return true; +} + +bool SharedMemIPCServer::InvokeCallback(const ServerControl* service_context, + void* ipc_buffer, + CrossCallReturn* call_result) { + // Set the default error code; + SetCallError(SBOX_ERROR_INVALID_IPC, call_result); + uint32_t output_size = 0; + // Parse, verify and copy the message. The handler operates on a copy + // of the message so the client cannot play dirty tricks by changing the + // data in the channel while the IPC is being processed. + std::unique_ptr<CrossCallParamsEx> params(CrossCallParamsEx::CreateFromBuffer( + ipc_buffer, service_context->channel_size, &output_size)); + if (!params.get()) + return false; + + IpcTag tag = params->GetTag(); + static_assert(0 == INVALID_TYPE, "incorrect type enum"); + IPCParams ipc_params = {tag}; + + void* args[kMaxIpcParams]; + if (!GetArgs(params.get(), &ipc_params, args)) + return false; + + IPCInfo ipc_info = {tag}; + ipc_info.client_info = &service_context->target_info; + Dispatcher* dispatcher = service_context->dispatcher; + DCHECK(dispatcher); + bool error = true; + Dispatcher* handler = nullptr; + + Dispatcher::CallbackGeneric callback_generic; + handler = dispatcher->OnMessageReady(&ipc_params, &callback_generic); + if (handler) { + switch (params->GetParamsCount()) { + case 0: { + // Ask the IPC dispatcher if it can service this IPC. + Dispatcher::Callback0 callback = + reinterpret_cast<Dispatcher::Callback0>(callback_generic); + if (!(handler->*callback)(&ipc_info)) + break; + error = false; + break; + } + case 1: { + Dispatcher::Callback1 callback = + reinterpret_cast<Dispatcher::Callback1>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0])) + break; + error = false; + break; + } + case 2: { + Dispatcher::Callback2 callback = + reinterpret_cast<Dispatcher::Callback2>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1])) + break; + error = false; + break; + } + case 3: { + Dispatcher::Callback3 callback = + reinterpret_cast<Dispatcher::Callback3>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2])) + break; + error = false; + break; + } + case 4: { + Dispatcher::Callback4 callback = + reinterpret_cast<Dispatcher::Callback4>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], + args[3])) + break; + error = false; + break; + } + case 5: { + Dispatcher::Callback5 callback = + reinterpret_cast<Dispatcher::Callback5>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], args[3], + args[4])) + break; + error = false; + break; + } + case 6: { + Dispatcher::Callback6 callback = + reinterpret_cast<Dispatcher::Callback6>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], args[3], + args[4], args[5])) + break; + error = false; + break; + } + case 7: { + Dispatcher::Callback7 callback = + reinterpret_cast<Dispatcher::Callback7>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], args[3], + args[4], args[5], args[6])) + break; + error = false; + break; + } + case 8: { + Dispatcher::Callback8 callback = + reinterpret_cast<Dispatcher::Callback8>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], args[3], + args[4], args[5], args[6], args[7])) + break; + error = false; + break; + } + case 9: { + Dispatcher::Callback9 callback = + reinterpret_cast<Dispatcher::Callback9>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], args[3], + args[4], args[5], args[6], args[7], args[8])) + break; + error = false; + break; + } + default: { + NOTREACHED(); + break; + } + } + } + + if (error) { + if (handler) + SetCallError(SBOX_ERROR_FAILED_IPC, call_result); + } else { + memcpy(call_result, &ipc_info.return_info, sizeof(*call_result)); + SetCallSuccess(call_result); + if (params->IsInOut()) { + // Maybe the params got changed by the broker. We need to upadte the + // memory section. + memcpy(ipc_buffer, params.get(), output_size); + } + } + + ReleaseArgs(&ipc_params, args); + + return !error; +} + +// This function gets called by a thread from the thread pool when a +// ping event fires. The context is the same as passed in the RegisterWait() +// call above. +void __stdcall SharedMemIPCServer::ThreadPingEventReady(void* context, + unsigned char) { + if (!context) { + DCHECK(false); + return; + } + ServerControl* service_context = reinterpret_cast<ServerControl*>(context); + // Since the event fired, the channel *must* be busy. Change to kAckChannel + // while we service it. + LONG last_state = ::InterlockedCompareExchange( + &service_context->channel->state, kAckChannel, kBusyChannel); + if (kBusyChannel != last_state) { + DCHECK(false); + return; + } + + // Prepare the result structure. At this point we will return some result + // even if the IPC is invalid, malformed or has no handler. + CrossCallReturn call_result = {0}; + void* buffer = service_context->channel_buffer; + + InvokeCallback(service_context, buffer, &call_result); + + // Copy the answer back into the channel and signal the pong event. This + // should wake up the client so it can finish the ipc cycle. + CrossCallParams* call_params = reinterpret_cast<CrossCallParams*>(buffer); + memcpy(call_params->GetCallReturn(), &call_result, sizeof(call_result)); + ::InterlockedExchange(&service_context->channel->state, kAckChannel); + ::SetEvent(service_context->pong_event.Get()); +} + +bool SharedMemIPCServer::MakeEvents(base::win::ScopedHandle* server_ping, + base::win::ScopedHandle* server_pong, + HANDLE* client_ping, + HANDLE* client_pong) { + // Note that the IPC client has no right to delete the events. That would + // cause problems. The server *owns* the events. + const DWORD kDesiredAccess = SYNCHRONIZE | EVENT_MODIFY_STATE; + + // The events are auto reset, and start not signaled. + server_ping->Set(::CreateEventW(nullptr, false, false, nullptr)); + if (!::DuplicateHandle(::GetCurrentProcess(), server_ping->Get(), + target_process_, client_ping, kDesiredAccess, false, + 0)) { + return false; + } + + server_pong->Set(::CreateEventW(nullptr, false, false, nullptr)); + if (!::DuplicateHandle(::GetCurrentProcess(), server_pong->Get(), + target_process_, client_pong, kDesiredAccess, false, + 0)) { + return false; + } + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_server.h b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_server.h new file mode 100644 index 0000000000..fce52c6ad0 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_server.h @@ -0,0 +1,137 @@ +// 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. + +#ifndef SANDBOX_SRC_SHAREDMEM_IPC_SERVER_H_ +#define SANDBOX_SRC_SHAREDMEM_IPC_SERVER_H_ + +#include <stdint.h> + +#include <list> +#include <memory> + +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" + +// IPC transport implementation that uses shared memory. +// This is the server side +// +// The server side has knowledge about the layout of the shared memory +// and the state transitions. Both are explained in sharedmem_ipc_client.h +// +// As opposed to SharedMemIPClient, the Server object should be one for the +// entire lifetime of the target process. The server is in charge of creating +// the events (ping, pong) both for the client and for the target that are used +// to signal the IPC and also in charge of setting the initial state of the +// channels. +// +// When an IPC is ready, the server relies on being called by on the +// ThreadPingEventReady callback. The IPC server then retrieves the buffer, +// marshals it into a CrossCallParam object and calls the Dispatcher, who is in +// charge of fulfilling the IPC request. +namespace sandbox { + +// the shared memory implementation of the IPC server. There should be one +// of these objects per target (IPC client) process +class SharedMemIPCServer { + public: + // Creates the IPC server. + // target_process: handle to the target process. It must be suspended. It is + // unfortunate to receive a raw handle (and store it inside this object) as + // that dilutes ownership of the process, but in practice a SharedMemIPCServer + // is owned by TargetProcess, which calls this method, and owns the handle, so + // everything is safe. If that changes, we should break this dependency and + // duplicate the handle instead. + // target_process_id: process id of the target process. + // thread_provider: a thread provider object. + // dispatcher: an object that can service IPC calls. + SharedMemIPCServer(HANDLE target_process, + DWORD target_process_id, + ThreadProvider* thread_provider, + Dispatcher* dispatcher); + + ~SharedMemIPCServer(); + + // Initializes the server structures, shared memory structures and + // creates the kernels events used to signal the IPC. + bool Init(void* shared_mem, uint32_t shared_size, uint32_t channel_size); + + private: + // Allow tests to be marked DISABLED_. Note that FLAKY_ and FAILS_ prefixes + // do not work with sandbox tests. + FRIEND_TEST_ALL_PREFIXES(IPCTest, SharedMemServerTests); + // When an event fires (IPC request). A thread from the ThreadProvider + // will call this function. The context parameter should be the same as + // provided when ThreadProvider::RegisterWait was called. + static void __stdcall ThreadPingEventReady(void* context, unsigned char); + + // Makes the client and server events. This function is called once + // per channel. + bool MakeEvents(base::win::ScopedHandle* server_ping, + base::win::ScopedHandle* server_pong, + HANDLE* client_ping, + HANDLE* client_pong); + + // A copy this structure is maintained per channel. + // Note that a lot of the fields are just the same of what we have in the IPC + // object itself. It is better to have the copies since we can dispatch in the + // static method without worrying about converting back to a member function + // call or about threading issues. + struct ServerControl { + ServerControl(); + ~ServerControl(); + + // This channel server ping event. + base::win::ScopedHandle ping_event; + // This channel server pong event. + base::win::ScopedHandle pong_event; + // The size of this channel. + uint32_t channel_size; + // The pointer to the actual channel data. + char* channel_buffer; + // The pointer to the base of the shared memory. + char* shared_base; + // A pointer to this channel's client-side control structure this structure + // lives in the shared memory. + ChannelControl* channel; + // the IPC dispatcher associated with this channel. + Dispatcher* dispatcher; + // The target process information associated with this channel. + ClientInfo target_info; + }; + + // Looks for the appropriate handler for this IPC and invokes it. + static bool InvokeCallback(const ServerControl* service_context, + void* ipc_buffer, + CrossCallReturn* call_result); + + // Points to the shared memory channel control which lives at + // the start of the shared section. + IPCControl* client_control_; + + // Keeps track of the server side objects that are used to answer an IPC. + std::list<std::unique_ptr<ServerControl>> server_contexts_; + + // The thread provider provides the threads that call back into this object + // when the IPC events fire. + ThreadProvider* thread_provider_; + + // The IPC object is associated with a target process. + HANDLE target_process_; + + // The target process id associated with the IPC object. + DWORD target_process_id_; + + // The dispatcher handles 'ready' IPC calls. + Dispatcher* call_dispatcher_; + + DISALLOW_COPY_AND_ASSIGN(SharedMemIPCServer); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SHAREDMEM_IPC_SERVER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sid.cc b/security/sandbox/chromium/sandbox/win/src/sid.cc new file mode 100644 index 0000000000..a97ca340ed --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sid.cc @@ -0,0 +1,163 @@ +// Copyright (c) 2006-2008 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/win/src/sid.h" + +#include <memory> + +#include <sddl.h> +#include <stdlib.h> + +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +namespace { + +DWORD WellKnownCapabilityToRid(WellKnownCapabilities capability) { + switch (capability) { + case kInternetClient: + return SECURITY_CAPABILITY_INTERNET_CLIENT; + case kInternetClientServer: + return SECURITY_CAPABILITY_INTERNET_CLIENT_SERVER; + case kPrivateNetworkClientServer: + return SECURITY_CAPABILITY_PRIVATE_NETWORK_CLIENT_SERVER; + case kPicturesLibrary: + return SECURITY_CAPABILITY_PICTURES_LIBRARY; + case kVideosLibrary: + return SECURITY_CAPABILITY_VIDEOS_LIBRARY; + case kMusicLibrary: + return SECURITY_CAPABILITY_MUSIC_LIBRARY; + case kDocumentsLibrary: + return SECURITY_CAPABILITY_DOCUMENTS_LIBRARY; + case kEnterpriseAuthentication: + return SECURITY_CAPABILITY_ENTERPRISE_AUTHENTICATION; + case kSharedUserCertificates: + return SECURITY_CAPABILITY_SHARED_USER_CERTIFICATES; + case kRemovableStorage: + return SECURITY_CAPABILITY_REMOVABLE_STORAGE; + case kAppointments: + return SECURITY_CAPABILITY_APPOINTMENTS; + case kContacts: + return SECURITY_CAPABILITY_CONTACTS; + default: + break; + } + return 0; +} + +} // namespace + +Sid::Sid() : sid_() {} + +Sid::Sid(PSID sid) : sid_() { + ::CopySid(SECURITY_MAX_SID_SIZE, sid_, sid); +} + +Sid::Sid(const SID* sid) : sid_() { + ::CopySid(SECURITY_MAX_SID_SIZE, sid_, const_cast<SID*>(sid)); +} + +Sid::Sid(WELL_KNOWN_SID_TYPE type) { + DWORD size_sid = SECURITY_MAX_SID_SIZE; + bool result = ::CreateWellKnownSid(type, nullptr, sid_, &size_sid); + DCHECK(result); + (void)result; +} + +Sid Sid::FromKnownCapability(WellKnownCapabilities capability) { + DWORD capability_rid = WellKnownCapabilityToRid(capability); + if (!capability_rid) + return Sid(); + SID_IDENTIFIER_AUTHORITY capability_authority = { + SECURITY_APP_PACKAGE_AUTHORITY}; + DWORD sub_authorities[] = {SECURITY_CAPABILITY_BASE_RID, capability_rid}; + return FromSubAuthorities(&capability_authority, 2, sub_authorities); +} + +Sid Sid::FromNamedCapability(const wchar_t* capability_name) { + RtlDeriveCapabilitySidsFromNameFunction derive_capability_sids = nullptr; + ResolveNTFunctionPtr("RtlDeriveCapabilitySidsFromName", + &derive_capability_sids); + RtlInitUnicodeStringFunction init_unicode_string = nullptr; + ResolveNTFunctionPtr("RtlInitUnicodeString", &init_unicode_string); + + if (!derive_capability_sids || !init_unicode_string) + return Sid(); + + if (!capability_name || ::wcslen(capability_name) == 0) + return Sid(); + + UNICODE_STRING name = {}; + init_unicode_string(&name, capability_name); + Sid capability_sid; + Sid group_sid; + + NTSTATUS status = + derive_capability_sids(&name, group_sid.sid_, capability_sid.sid_); + if (!NT_SUCCESS(status)) + return Sid(); + + return capability_sid; +} + +Sid Sid::FromSddlString(const wchar_t* sddl_sid) { + PSID converted_sid; + if (!::ConvertStringSidToSid(sddl_sid, &converted_sid)) + return Sid(); + + return Sid(converted_sid); +} + +Sid Sid::FromSubAuthorities(PSID_IDENTIFIER_AUTHORITY identifier_authority, + BYTE sub_authority_count, + PDWORD sub_authorities) { + Sid sid; + if (!::InitializeSid(sid.sid_, identifier_authority, sub_authority_count)) + return Sid(); + + for (DWORD index = 0; index < sub_authority_count; ++index) { + PDWORD sub_authority = GetSidSubAuthority(sid.sid_, index); + *sub_authority = sub_authorities[index]; + } + return sid; +} + +Sid Sid::AllRestrictedApplicationPackages() { + SID_IDENTIFIER_AUTHORITY package_authority = {SECURITY_APP_PACKAGE_AUTHORITY}; + DWORD sub_authorities[] = {SECURITY_APP_PACKAGE_BASE_RID, + SECURITY_BUILTIN_PACKAGE_ANY_RESTRICTED_PACKAGE}; + return FromSubAuthorities(&package_authority, 2, sub_authorities); +} + +Sid Sid::GenerateRandomSid() { + SID_IDENTIFIER_AUTHORITY package_authority = {SECURITY_NULL_SID_AUTHORITY}; + DWORD sub_authorities[4] = {}; + base::RandBytes(&sub_authorities, sizeof(sub_authorities)); + return FromSubAuthorities(&package_authority, _countof(sub_authorities), + sub_authorities); +} + +PSID Sid::GetPSID() const { + return const_cast<BYTE*>(sid_); +} + +bool Sid::IsValid() const { + return !!::IsValidSid(GetPSID()); +} + +// Converts the SID to an SDDL format string. +bool Sid::ToSddlString(std::wstring* sddl_string) const { + LPWSTR sid = nullptr; + if (!::ConvertSidToStringSid(GetPSID(), &sid)) + return false; + std::unique_ptr<void, LocalFreeDeleter> sid_ptr(sid); + *sddl_string = sid; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sid.h b/security/sandbox/chromium/sandbox/win/src/sid.h new file mode 100644 index 0000000000..745f471054 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sid.h @@ -0,0 +1,74 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_SRC_SID_H_ +#define SANDBOX_SRC_SID_H_ + +#include <windows.h> + +#include <string> + +namespace sandbox { + +// Known capabilities defined in Windows 8. +enum WellKnownCapabilities { + kInternetClient, + kInternetClientServer, + kPrivateNetworkClientServer, + kPicturesLibrary, + kVideosLibrary, + kMusicLibrary, + kDocumentsLibrary, + kEnterpriseAuthentication, + kSharedUserCertificates, + kRemovableStorage, + kAppointments, + kContacts, + kMaxWellKnownCapability +}; + +// This class is used to hold and generate SIDS. +class Sid { + public: + // As PSID is just a void* make it explicit. + explicit Sid(PSID sid); + // Constructors initializing the object with the SID passed. + // This is a converting constructor. It is not explicit. + Sid(const SID* sid); + Sid(WELL_KNOWN_SID_TYPE type); + + // Create a Sid from an AppContainer capability name. The name can be + // completely arbitrary. + static Sid FromNamedCapability(const wchar_t* capability_name); + // Create a Sid from a known capability enumeration value. The Sids + // match with the list defined in Windows 8. + static Sid FromKnownCapability(WellKnownCapabilities capability); + // Create a Sid from a SDDL format string, such as S-1-1-0. + static Sid FromSddlString(const wchar_t* sddl_sid); + // Create a Sid from a set of sub authorities. + static Sid FromSubAuthorities(PSID_IDENTIFIER_AUTHORITY identifier_authority, + BYTE sub_authority_count, + PDWORD sub_authorities); + // Create the restricted all application packages sid. + static Sid AllRestrictedApplicationPackages(); + // Generate a random SID value. + static Sid GenerateRandomSid(); + + // Returns sid_. + PSID GetPSID() const; + + // Gets whether the sid is valid. + bool IsValid() const; + + // Converts the SID to a SDDL format string. + bool ToSddlString(std::wstring* sddl_string) const; + + private: + Sid(); + BYTE sid_[SECURITY_MAX_SID_SIZE]; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SID_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sid_unittest.cc b/security/sandbox/chromium/sandbox/win/src/sid_unittest.cc new file mode 100644 index 0000000000..81186cd0be --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sid_unittest.cc @@ -0,0 +1,182 @@ +// Copyright (c) 2006-2008 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. + +// This file contains unit tests for the sid class. + +#include "sandbox/win/src/sid.h" + +#include <sddl.h> + +#include "base/win/atl.h" +#include "base/win/windows_version.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +bool EqualSid(const Sid& sid, const ATL::CSid& compare_sid) { + if (!sid.IsValid()) + return false; + return !!::EqualSid(sid.GetPSID(), const_cast<SID*>(compare_sid.GetPSID())); +} + +bool EqualSid(const Sid& sid, const wchar_t* sddl_sid) { + PSID compare_sid; + if (!sid.IsValid()) + return false; + if (!::ConvertStringSidToSid(sddl_sid, &compare_sid)) + return false; + bool equal = !!::EqualSid(sid.GetPSID(), compare_sid); + ::LocalFree(compare_sid); + return equal; +} + +struct KnownCapabilityTestEntry { + WellKnownCapabilities capability; + const wchar_t* sddl_sid; +}; + +struct NamedCapabilityTestEntry { + const wchar_t* capability_name; + const wchar_t* sddl_sid; +}; + +} // namespace + +// Tests the creation of a Sid. +TEST(SidTest, Constructors) { + ATL::CSid sid_world = ATL::Sids::World(); + PSID sid_world_pointer = const_cast<SID*>(sid_world.GetPSID()); + + // Check the SID* constructor. + Sid sid_sid_star(sid_world_pointer); + ASSERT_TRUE(EqualSid(sid_sid_star, sid_world)); + + // Check the copy constructor. + Sid sid_copy(sid_sid_star); + ASSERT_TRUE(EqualSid(sid_copy, sid_world)); + + Sid sid_sddl = Sid::FromSddlString(L"S-1-1-0"); + ASSERT_TRUE(sid_sddl.IsValid()); + ASSERT_TRUE(EqualSid(sid_sddl, sid_world)); + + Sid sid_sddl_invalid = Sid::FromSddlString(L"X-1-1-0"); + ASSERT_FALSE(sid_sddl_invalid.IsValid()); + + Sid sid_sddl_empty = Sid::FromSddlString(L""); + ASSERT_FALSE(sid_sddl_empty.IsValid()); + + // Note that the WELL_KNOWN_SID_TYPE constructor is tested in the GetPSID + // test. AppContainer related constructors are tested in AppContainer. +} + +// Tests the method GetPSID +TEST(SidTest, GetPSID) { + // Check for non-null result; + ASSERT_NE(nullptr, Sid(::WinLocalSid).GetPSID()); + ASSERT_NE(nullptr, Sid(::WinCreatorOwnerSid).GetPSID()); + ASSERT_NE(nullptr, Sid(::WinBatchSid).GetPSID()); + + ASSERT_TRUE(EqualSid(Sid(::WinNullSid), ATL::Sids::Null())); + + ASSERT_TRUE(EqualSid(Sid(::WinWorldSid), ATL::Sids::World())); + + ASSERT_TRUE(EqualSid(Sid(::WinDialupSid), ATL::Sids::Dialup())); + + ASSERT_TRUE(EqualSid(Sid(::WinNetworkSid), ATL::Sids::Network())); + + ASSERT_TRUE( + EqualSid(Sid(::WinBuiltinAdministratorsSid), ATL::Sids::Admins())); + + ASSERT_TRUE(EqualSid(Sid(::WinBuiltinUsersSid), ATL::Sids::Users())); + + ASSERT_TRUE(EqualSid(Sid(::WinBuiltinGuestsSid), ATL::Sids::Guests())); + + ASSERT_TRUE(EqualSid(Sid(::WinProxySid), ATL::Sids::Proxy())); +} + +TEST(SidTest, KnownCapability) { + if (base::win::GetVersion() < base::win::Version::WIN8) + return; + + Sid sid_invalid_well_known = + Sid::FromKnownCapability(kMaxWellKnownCapability); + EXPECT_FALSE(sid_invalid_well_known.IsValid()); + + const KnownCapabilityTestEntry capabilities[] = { + {kInternetClient, L"S-1-15-3-1"}, + {kInternetClientServer, L"S-1-15-3-2"}, + {kPrivateNetworkClientServer, L"S-1-15-3-3"}, + {kPicturesLibrary, L"S-1-15-3-4"}, + {kVideosLibrary, L"S-1-15-3-5"}, + {kMusicLibrary, L"S-1-15-3-6"}, + {kDocumentsLibrary, L"S-1-15-3-7"}, + {kEnterpriseAuthentication, L"S-1-15-3-8"}, + {kSharedUserCertificates, L"S-1-15-3-9"}, + {kRemovableStorage, L"S-1-15-3-10"}, + {kAppointments, L"S-1-15-3-11"}, + {kContacts, L"S-1-15-3-12"}, + }; + + for (auto capability : capabilities) { + EXPECT_TRUE(EqualSid(Sid::FromKnownCapability(capability.capability), + capability.sddl_sid)) + << "Known Capability: " << capability.sddl_sid; + } +} + +TEST(SidTest, NamedCapability) { + if (base::win::GetVersion() < base::win::Version::WIN10) + return; + + Sid sid_nullptr = Sid::FromNamedCapability(nullptr); + EXPECT_FALSE(sid_nullptr.IsValid()); + + Sid sid_empty = Sid::FromNamedCapability(L""); + EXPECT_FALSE(sid_empty.IsValid()); + + const NamedCapabilityTestEntry capabilities[] = { + {L"internetClient", L"S-1-15-3-1"}, + {L"internetClientServer", L"S-1-15-3-2"}, + {L"registryRead", + L"S-1-15-3-1024-1065365936-1281604716-3511738428-" + "1654721687-432734479-3232135806-4053264122-3456934681"}, + {L"lpacCryptoServices", + L"S-1-15-3-1024-3203351429-2120443784-2872670797-" + "1918958302-2829055647-4275794519-765664414-2751773334"}, + {L"enterpriseAuthentication", L"S-1-15-3-8"}, + {L"privateNetworkClientServer", L"S-1-15-3-3"}}; + + for (auto capability : capabilities) { + EXPECT_TRUE(EqualSid(Sid::FromNamedCapability(capability.capability_name), + capability.sddl_sid)) + << "Named Capability: " << capability.sddl_sid; + } +} + +TEST(SidTest, Sddl) { + Sid sid_sddl = Sid::FromSddlString(L"S-1-1-0"); + ASSERT_TRUE(sid_sddl.IsValid()); + std::wstring sddl_str; + ASSERT_TRUE(sid_sddl.ToSddlString(&sddl_str)); + ASSERT_EQ(L"S-1-1-0", sddl_str); +} + +TEST(SidTest, SubAuthorities) { + DWORD world_subauthorities[] = {0}; + SID_IDENTIFIER_AUTHORITY world_authority = {SECURITY_WORLD_SID_AUTHORITY}; + Sid sid_world = + Sid::FromSubAuthorities(&world_authority, 1, world_subauthorities); + ASSERT_TRUE(EqualSid(sid_world, ATL::Sids::World())); + ASSERT_TRUE(Sid::FromSubAuthorities(&world_authority, 0, nullptr).IsValid()); + + DWORD admin_subauthorities[] = {32, 544}; + SID_IDENTIFIER_AUTHORITY nt_authority = {SECURITY_NT_AUTHORITY}; + Sid sid_admin = + Sid::FromSubAuthorities(&nt_authority, 2, admin_subauthorities); + ASSERT_TRUE(EqualSid(sid_admin, ATL::Sids::Admins())); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/signed_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/signed_dispatcher.cc new file mode 100644 index 0000000000..49e7cbe946 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/signed_dispatcher.cc @@ -0,0 +1,68 @@ +// Copyright 2019 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/win/src/signed_dispatcher.h" + +#include <stdint.h> + +#include <string> + +#include "base/strings/string_util.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/signed_interception.h" +#include "sandbox/win/src/signed_policy.h" + +namespace sandbox { + +SignedDispatcher::SignedDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall create_params = { + {IpcTag::NTCREATESECTION, {VOIDPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>(&SignedDispatcher::CreateSection)}; + + ipc_calls_.push_back(create_params); +} + +bool SignedDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + if (service == IpcTag::NTCREATESECTION) + return INTERCEPT_NT(manager, NtCreateSection, CREATE_SECTION_ID, 32); + return false; +} + +bool SignedDispatcher::CreateSection(IPCInfo* ipc, HANDLE file_handle) { + // Duplicate input handle from target to broker. + HANDLE local_file_handle = nullptr; + if (!::DuplicateHandle((*ipc->client_info).process, file_handle, + ::GetCurrentProcess(), &local_file_handle, + FILE_MAP_EXECUTE, false, 0)) { + return false; + } + + base::win::ScopedHandle local_handle(local_file_handle); + std::wstring path; + if (!GetPathFromHandle(local_handle.Get(), &path)) + return false; + const wchar_t* module_name = path.c_str(); + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(module_name); + + EvalResult result = + policy_base_->EvalPolicy(IpcTag::NTCREATESECTION, params.GetBase()); + + // Return operation status on the IPC. + HANDLE section_handle = nullptr; + ipc->return_info.nt_status = SignedPolicy::CreateSectionAction( + result, *ipc->client_info, local_handle, §ion_handle); + ipc->return_info.handle = section_handle; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/signed_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/signed_dispatcher.h new file mode 100644 index 0000000000..38435481f6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/signed_dispatcher.h @@ -0,0 +1,37 @@ +// Copyright 2019 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. + +#ifndef SANDBOX_WIN_SRC_SIGNED_DISPATCHER_H_ +#define SANDBOX_WIN_SRC_SIGNED_DISPATCHER_H_ + +#include <stdint.h> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles signed-binary related IPC calls. +class SignedDispatcher : public Dispatcher { + public: + explicit SignedDispatcher(PolicyBase* policy_base); + ~SignedDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Processes IPC requests coming from calls to CreateSection in the target. + bool CreateSection(IPCInfo* ipc, HANDLE file_handle); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(SignedDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SIGNED_DISPATCHER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/signed_interception.cc b/security/sandbox/chromium/sandbox/win/src/signed_interception.cc new file mode 100644 index 0000000000..4f4310d605 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/signed_interception.cc @@ -0,0 +1,97 @@ +// Copyright 2019 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/win/src/signed_interception.h" + +#include <stdint.h> + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" +#include "mozilla/sandboxing/sandboxLogging.h" + +namespace sandbox { + +NTSTATUS WINAPI +TargetNtCreateSection(NtCreateSectionFunction orig_CreateSection, + PHANDLE section_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PLARGE_INTEGER maximum_size, + ULONG section_page_protection, + ULONG allocation_attributes, + HANDLE file_handle) { + do { + // The section only needs to have SECTION_MAP_EXECUTE, but the permissions + // vary depending on the OS. Windows 1903 and higher requests (SECTION_QUERY + // | SECTION_MAP_READ | SECTION_MAP_EXECUTE) while previous OS versions also + // request SECTION_MAP_WRITE. Just check for EXECUTE. + if (!(desired_access & SECTION_MAP_EXECUTE)) + break; + if (object_attributes) + break; + if (maximum_size) + break; + if (section_page_protection != PAGE_EXECUTE) + break; + if (allocation_attributes != SEC_IMAGE) + break; + + mozilla::sandboxing::LogBlocked("NtCreateSection"); + + // IPC must be fully started. + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + std::unique_ptr<wchar_t, NtAllocDeleter> path; + + if (!NtGetPathFromHandle(file_handle, &path)) + break; + + const wchar_t* const_name = path.get(); + + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(const_name); + + // Check if this will be sent to the broker. + if (!QueryBroker(IpcTag::NTCREATESECTION, params.GetBase())) + break; + + if (!ValidParameter(section_handle, sizeof(HANDLE), WRITE)) + break; + + CrossCallReturn answer = {0}; + answer.nt_status = STATUS_INVALID_IMAGE_HASH; + SharedMemIPCClient ipc(memory); + ResultCode code = + CrossCall(ipc, IpcTag::NTCREATESECTION, file_handle, &answer); + + if (code != SBOX_ALL_OK) + break; + + if (!NT_SUCCESS(answer.nt_status)) + break; + + __try { + *section_handle = answer.handle; + mozilla::sandboxing::LogAllowed("NtCreateSection"); + return answer.nt_status; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + } while (false); + + // Fall back to the original API in all failure cases. + return orig_CreateSection(section_handle, desired_access, object_attributes, + maximum_size, section_page_protection, + allocation_attributes, file_handle); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/signed_interception.h b/security/sandbox/chromium/sandbox/win/src/signed_interception.h new file mode 100644 index 0000000000..a50ec38222 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/signed_interception.h @@ -0,0 +1,30 @@ +// Copyright 2019 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. + +#ifndef SANDBOX_WIN_SRC_SIGNED_INTERCEPTION_H_ +#define SANDBOX_WIN_SRC_SIGNED_INTERCEPTION_H_ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +// Interceptor for NtCreateSection +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateSection(NtCreateSectionFunction orig_CreateSection, + PHANDLE section_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PLARGE_INTEGER maximum_size, + ULONG section_page_protection, + ULONG allocation_attributes, + HANDLE file_handle); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SIGNED_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/signed_policy.cc b/security/sandbox/chromium/sandbox/win/src/signed_policy.cc new file mode 100644 index 0000000000..ccf11defe7 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/signed_policy.cc @@ -0,0 +1,102 @@ +// Copyright 2019 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/win/src/signed_policy.h" + +#include <stdint.h> + +#include <string> + +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +bool IsValidNtPath(const base::FilePath& name) { + UNICODE_STRING uni_name; + OBJECT_ATTRIBUTES obj_attr; + sandbox::InitObjectAttribs(name.value(), OBJ_CASE_INSENSITIVE, nullptr, + &obj_attr, &uni_name, nullptr); + + NtQueryAttributesFileFunction NtQueryAttributesFile = nullptr; + ResolveNTFunctionPtr("NtQueryAttributesFile", &NtQueryAttributesFile); + FILE_BASIC_INFORMATION file_info; + return NtQueryAttributesFile && + NT_SUCCESS(NtQueryAttributesFile(&obj_attr, &file_info)); +} + +} // namespace + +namespace sandbox { + +bool SignedPolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + // Only support one semantic. + if (TargetPolicy::SIGNED_ALLOW_LOAD != semantics) { + return false; + } + + base::FilePath file_path(name); + base::FilePath nt_filename; + std::wstring nt_path_name; + if (GetNtPathFromWin32Path(file_path.DirName().value().c_str(), + &nt_path_name)) { + base::FilePath nt_path(nt_path_name); + nt_filename = nt_path.Append(file_path.BaseName()); + } else if (IsValidNtPath(file_path)) { + nt_filename = std::move(file_path); + } else { + return false; + } + + // Create a rule to ASK_BROKER if name matches. + PolicyRule signed_policy(ASK_BROKER); + if (!signed_policy.AddStringMatch( + IF, NameBased::NAME, nt_filename.value().c_str(), CASE_INSENSITIVE)) { + return false; + } + if (!policy->AddRule(IpcTag::NTCREATESECTION, &signed_policy)) { + return false; + } + + return true; +} + +NTSTATUS SignedPolicy::CreateSectionAction( + EvalResult eval_result, + const ClientInfo& client_info, + const base::win::ScopedHandle& local_file_handle, + HANDLE* target_section_handle) { + NtCreateSectionFunction NtCreateSection = nullptr; + ResolveNTFunctionPtr("NtCreateSection", &NtCreateSection); + + // The only action supported is ASK_BROKER which means create the requested + // section as specified. + if (ASK_BROKER != eval_result) + return false; + + HANDLE local_section_handle = nullptr; + NTSTATUS status = NtCreateSection(&local_section_handle, + SECTION_QUERY | SECTION_MAP_WRITE | + SECTION_MAP_READ | SECTION_MAP_EXECUTE, + nullptr, 0, PAGE_EXECUTE, SEC_IMAGE, + local_file_handle.Get()); + if (!local_section_handle) + return status; + + // Duplicate section handle back to the target. + if (!::DuplicateHandle(::GetCurrentProcess(), local_section_handle, + client_info.process, target_section_handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return status; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/signed_policy.h b/security/sandbox/chromium/sandbox/win/src/signed_policy.h new file mode 100644 index 0000000000..d22af4d4dc --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/signed_policy.h @@ -0,0 +1,39 @@ +// Copyright 2019 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. + +#ifndef SANDBOX_WIN_SRC_SIGNED_POLICY_H_ +#define SANDBOX_WIN_SRC_SIGNED_POLICY_H_ + +#include <stdint.h> + +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +// This class centralizes most of the knowledge related to signed policy +class SignedPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Performs the desired policy action on a request. + // client_info is the target process that is making the request and + // eval_result is the desired policy action to accomplish. + static NTSTATUS CreateSectionAction( + EvalResult eval_result, + const ClientInfo& client_info, + const base::win::ScopedHandle& local_file_handle, + HANDLE* section_handle); +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SIGNED_POLICY_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sync_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/sync_dispatcher.cc new file mode 100644 index 0000000000..d728e1d831 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_dispatcher.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2013 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/win/src/sync_dispatcher.h" + +#include <stdint.h> + +#include "base/win/windows_version.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sync_interception.h" +#include "sandbox/win/src/sync_policy.h" + +namespace sandbox { + +SyncDispatcher::SyncDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall create_params = { + {IpcTag::CREATEEVENT, {WCHAR_TYPE, UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>(&SyncDispatcher::CreateEvent)}; + + static const IPCCall open_params = { + {IpcTag::OPENEVENT, {WCHAR_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>(&SyncDispatcher::OpenEvent)}; + + ipc_calls_.push_back(create_params); + ipc_calls_.push_back(open_params); +} + +bool SyncDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + if (service == IpcTag::CREATEEVENT) { + return INTERCEPT_NT(manager, NtCreateEvent, CREATE_EVENT_ID, 24); + } + return (service == IpcTag::OPENEVENT) && + INTERCEPT_NT(manager, NtOpenEvent, OPEN_EVENT_ID, 16); +} + +bool SyncDispatcher::CreateEvent(IPCInfo* ipc, + std::wstring* name, + uint32_t event_type, + uint32_t initial_state) { + const wchar_t* event_name = name->c_str(); + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(event_name); + + EvalResult result = + policy_base_->EvalPolicy(IpcTag::CREATEEVENT, params.GetBase()); + HANDLE handle = nullptr; + // Return operation status on the IPC. + ipc->return_info.nt_status = SyncPolicy::CreateEventAction( + result, *ipc->client_info, *name, event_type, initial_state, &handle); + ipc->return_info.handle = handle; + return true; +} + +bool SyncDispatcher::OpenEvent(IPCInfo* ipc, + std::wstring* name, + uint32_t desired_access) { + const wchar_t* event_name = name->c_str(); + + CountedParameterSet<OpenEventParams> params; + params[OpenEventParams::NAME] = ParamPickerMake(event_name); + params[OpenEventParams::ACCESS] = ParamPickerMake(desired_access); + + EvalResult result = + policy_base_->EvalPolicy(IpcTag::OPENEVENT, params.GetBase()); + HANDLE handle = nullptr; + // Return operation status on the IPC. + ipc->return_info.nt_status = SyncPolicy::OpenEventAction( + result, *ipc->client_info, *name, desired_access, &handle); + ipc->return_info.handle = handle; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sync_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/sync_dispatcher.h new file mode 100644 index 0000000000..d4bc025a0b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_dispatcher.h @@ -0,0 +1,44 @@ +// Copyright (c) 2010 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. + +#ifndef SANDBOX_SRC_SYNC_DISPATCHER_H_ +#define SANDBOX_SRC_SYNC_DISPATCHER_H_ + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles sync-related IPC calls. +class SyncDispatcher : public Dispatcher { + public: + explicit SyncDispatcher(PolicyBase* policy_base); + ~SyncDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Processes IPC requests coming from calls to CreateEvent in the target. + bool CreateEvent(IPCInfo* ipc, + std::wstring* name, + uint32_t event_type, + uint32_t initial_state); + + // Processes IPC requests coming from calls to OpenEvent in the target. + bool OpenEvent(IPCInfo* ipc, std::wstring* name, uint32_t desired_access); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(SyncDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SYNC_DISPATCHER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sync_interception.cc b/security/sandbox/chromium/sandbox/win/src/sync_interception.cc new file mode 100644 index 0000000000..9ec680e2fd --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_interception.cc @@ -0,0 +1,177 @@ +// Copyright (c) 2006-2008 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/win/src/sync_interception.h" + +#include <stdint.h> + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" +#include "mozilla/sandboxing/sandboxLogging.h" + +namespace sandbox { + +ResultCode ProxyCreateEvent(LPCWSTR name, + uint32_t initial_state, + EVENT_TYPE event_type, + void* ipc_memory, + CrossCallReturn* answer) { + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(name); + + if (!QueryBroker(IpcTag::CREATEEVENT, params.GetBase())) + return SBOX_ERROR_GENERIC; + + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = CrossCall(ipc, IpcTag::CREATEEVENT, name, event_type, + initial_state, answer); + return code; +} + +ResultCode ProxyOpenEvent(LPCWSTR name, + uint32_t desired_access, + void* ipc_memory, + CrossCallReturn* answer) { + CountedParameterSet<OpenEventParams> params; + params[OpenEventParams::NAME] = ParamPickerMake(name); + params[OpenEventParams::ACCESS] = ParamPickerMake(desired_access); + + if (!QueryBroker(IpcTag::OPENEVENT, params.GetBase())) + return SBOX_ERROR_GENERIC; + + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = + CrossCall(ipc, IpcTag::OPENEVENT, name, desired_access, answer); + + return code; +} + +NTSTATUS WINAPI TargetNtCreateEvent(NtCreateEventFunction orig_CreateEvent, + PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + EVENT_TYPE event_type, + BOOLEAN initial_state) { + NTSTATUS status = + orig_CreateEvent(event_handle, desired_access, object_attributes, + event_type, initial_state); + if (status != STATUS_ACCESS_DENIED || !object_attributes) + return status; + + mozilla::sandboxing::LogBlocked("NtCreatEvent", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(event_handle, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + OBJECT_ATTRIBUTES object_attribs_copy = *object_attributes; + // The RootDirectory points to BaseNamedObjects. We can ignore it. + object_attribs_copy.RootDirectory = nullptr; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes = 0; + NTSTATUS ret = + AllocAndCopyName(&object_attribs_copy, &name, &attributes, nullptr); + if (!NT_SUCCESS(ret) || !name) + break; + + CrossCallReturn answer = {0}; + answer.nt_status = status; + ResultCode code = ProxyCreateEvent(name.get(), initial_state, event_type, + memory, &answer); + + if (code != SBOX_ALL_OK) { + status = answer.nt_status; + break; + } + __try { + *event_handle = answer.handle; + status = STATUS_SUCCESS; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + mozilla::sandboxing::LogAllowed("NtCreateEvent", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +NTSTATUS WINAPI TargetNtOpenEvent(NtOpenEventFunction orig_OpenEvent, + PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes) { + NTSTATUS status = + orig_OpenEvent(event_handle, desired_access, object_attributes); + if (status != STATUS_ACCESS_DENIED || !object_attributes) + return status; + + mozilla::sandboxing::LogBlocked("NtOpenEvent", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(event_handle, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + OBJECT_ATTRIBUTES object_attribs_copy = *object_attributes; + // The RootDirectory points to BaseNamedObjects. We can ignore it. + object_attribs_copy.RootDirectory = nullptr; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes = 0; + NTSTATUS ret = + AllocAndCopyName(&object_attribs_copy, &name, &attributes, nullptr); + if (!NT_SUCCESS(ret) || !name) + break; + + CrossCallReturn answer = {0}; + answer.nt_status = status; + ResultCode code = + ProxyOpenEvent(name.get(), desired_access, memory, &answer); + + if (code != SBOX_ALL_OK) { + status = answer.nt_status; + break; + } + __try { + *event_handle = answer.handle; + status = STATUS_SUCCESS; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + mozilla::sandboxing::LogAllowed("NtOpenEvent", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sync_interception.h b/security/sandbox/chromium/sandbox/win/src/sync_interception.h new file mode 100644 index 0000000000..c21e7ee7b2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_interception.h @@ -0,0 +1,46 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_WIN_SRC_SYNC_INTERCEPTION_H_ +#define SANDBOX_WIN_SRC_SYNC_INTERCEPTION_H_ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +typedef NTSTATUS(WINAPI* NtCreateEventFunction)( + PHANDLE EventHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + EVENT_TYPE EventType, + BOOLEAN InitialState); + +typedef NTSTATUS(WINAPI* NtOpenEventFunction)( + PHANDLE EventHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes); + +// Interceptors for NtCreateEvent/NtOpenEvent +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateEvent(NtCreateEventFunction orig_CreateEvent, + PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + EVENT_TYPE event_type, + BOOLEAN initial_state); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenEvent(NtOpenEventFunction orig_OpenEvent, + PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SYNC_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sync_policy.cc b/security/sandbox/chromium/sandbox/win/src/sync_policy.cc new file mode 100644 index 0000000000..cdc34dd241 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_policy.cc @@ -0,0 +1,243 @@ +// Copyright (c) 2006-2008 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/win/src/sync_policy.h" + +#include <stdint.h> + +#include <string> + +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/sync_interception.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +// Provides functionality to resolve a symbolic link within the object +// directory passed in. +NTSTATUS ResolveSymbolicLink(const std::wstring& directory_name, + const std::wstring& name, + std::wstring* target) { + NtOpenDirectoryObjectFunction NtOpenDirectoryObject = nullptr; + ResolveNTFunctionPtr("NtOpenDirectoryObject", &NtOpenDirectoryObject); + + NtQuerySymbolicLinkObjectFunction NtQuerySymbolicLinkObject = nullptr; + ResolveNTFunctionPtr("NtQuerySymbolicLinkObject", &NtQuerySymbolicLinkObject); + + NtOpenSymbolicLinkObjectFunction NtOpenSymbolicLinkObject = nullptr; + ResolveNTFunctionPtr("NtOpenSymbolicLinkObject", &NtOpenSymbolicLinkObject); + + NtCloseFunction NtClose = nullptr; + ResolveNTFunctionPtr("NtClose", &NtClose); + + OBJECT_ATTRIBUTES symbolic_link_directory_attributes = {}; + UNICODE_STRING symbolic_link_directory_string = {}; + InitObjectAttribs(directory_name, OBJ_CASE_INSENSITIVE, nullptr, + &symbolic_link_directory_attributes, + &symbolic_link_directory_string, nullptr); + + HANDLE symbolic_link_directory = nullptr; + NTSTATUS status = + NtOpenDirectoryObject(&symbolic_link_directory, DIRECTORY_QUERY, + &symbolic_link_directory_attributes); + if (!NT_SUCCESS(status)) + return status; + + OBJECT_ATTRIBUTES symbolic_link_attributes = {}; + UNICODE_STRING name_string = {}; + InitObjectAttribs(name, OBJ_CASE_INSENSITIVE, symbolic_link_directory, + &symbolic_link_attributes, &name_string, nullptr); + + HANDLE symbolic_link = nullptr; + status = NtOpenSymbolicLinkObject(&symbolic_link, GENERIC_READ, + &symbolic_link_attributes); + CHECK(NT_SUCCESS(NtClose(symbolic_link_directory))); + if (!NT_SUCCESS(status)) + return status; + + UNICODE_STRING target_path = {}; + unsigned long target_length = 0; + status = + NtQuerySymbolicLinkObject(symbolic_link, &target_path, &target_length); + if (status != STATUS_BUFFER_TOO_SMALL) { + CHECK(NT_SUCCESS(NtClose(symbolic_link))); + return status; + } + + target_path.Length = 0; + target_path.MaximumLength = static_cast<USHORT>(target_length); + target_path.Buffer = new wchar_t[target_path.MaximumLength + 1]; + status = + NtQuerySymbolicLinkObject(symbolic_link, &target_path, &target_length); + if (NT_SUCCESS(status)) + target->assign(target_path.Buffer, target_length); + + CHECK(NT_SUCCESS(NtClose(symbolic_link))); + delete[] target_path.Buffer; + return status; +} + +NTSTATUS GetBaseNamedObjectsDirectory(HANDLE* directory) { + static HANDLE base_named_objects_handle = nullptr; + if (base_named_objects_handle) { + *directory = base_named_objects_handle; + return STATUS_SUCCESS; + } + + NtOpenDirectoryObjectFunction NtOpenDirectoryObject = nullptr; + ResolveNTFunctionPtr("NtOpenDirectoryObject", &NtOpenDirectoryObject); + + DWORD session_id = 0; + ProcessIdToSessionId(::GetCurrentProcessId(), &session_id); + + std::wstring base_named_objects_path; + + NTSTATUS status = ResolveSymbolicLink(L"\\Sessions\\BNOLINKS", + base::StringPrintf(L"%d", session_id), + &base_named_objects_path); + if (!NT_SUCCESS(status)) { + DLOG(ERROR) << "Failed to resolve BaseNamedObjects path. Error: " << status; + return status; + } + + UNICODE_STRING directory_name = {}; + OBJECT_ATTRIBUTES object_attributes = {}; + InitObjectAttribs(base_named_objects_path, OBJ_CASE_INSENSITIVE, nullptr, + &object_attributes, &directory_name, nullptr); + status = NtOpenDirectoryObject(&base_named_objects_handle, + DIRECTORY_ALL_ACCESS, &object_attributes); + if (NT_SUCCESS(status)) + *directory = base_named_objects_handle; + return status; +} + +bool SyncPolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + std::wstring mod_name(name); + if (mod_name.empty()) { + return false; + } + + if (TargetPolicy::EVENTS_ALLOW_ANY != semantics && + TargetPolicy::EVENTS_ALLOW_READONLY != semantics) { + // Other flags are not valid for sync policy yet. + NOTREACHED(); + return false; + } + + // Add the open rule. + EvalResult result = ASK_BROKER; + PolicyRule open(result); + + if (!open.AddStringMatch(IF, OpenEventParams::NAME, name, CASE_INSENSITIVE)) + return false; + + if (TargetPolicy::EVENTS_ALLOW_READONLY == semantics) { + // We consider all flags that are not known to be readonly as potentially + // used for write. + uint32_t allowed_flags = SYNCHRONIZE | GENERIC_READ | READ_CONTROL; + uint32_t restricted_flags = ~allowed_flags; + open.AddNumberMatch(IF_NOT, OpenEventParams::ACCESS, restricted_flags, AND); + } + + if (!policy->AddRule(IpcTag::OPENEVENT, &open)) + return false; + + // If it's not a read only, add the create rule. + if (TargetPolicy::EVENTS_ALLOW_READONLY != semantics) { + PolicyRule create(result); + if (!create.AddStringMatch(IF, NameBased::NAME, name, CASE_INSENSITIVE)) + return false; + + if (!policy->AddRule(IpcTag::CREATEEVENT, &create)) + return false; + } + + return true; +} + +NTSTATUS SyncPolicy::CreateEventAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& event_name, + uint32_t event_type, + uint32_t initial_state, + HANDLE* handle) { + NtCreateEventFunction NtCreateEvent = nullptr; + ResolveNTFunctionPtr("NtCreateEvent", &NtCreateEvent); + + // The only action supported is ASK_BROKER which means create the requested + // file as specified. + if (ASK_BROKER != eval_result) + return false; + + HANDLE object_directory = nullptr; + NTSTATUS status = GetBaseNamedObjectsDirectory(&object_directory); + if (status != STATUS_SUCCESS) + return status; + + UNICODE_STRING unicode_event_name = {}; + OBJECT_ATTRIBUTES object_attributes = {}; + InitObjectAttribs(event_name, OBJ_CASE_INSENSITIVE, object_directory, + &object_attributes, &unicode_event_name, nullptr); + + HANDLE local_handle = nullptr; + status = NtCreateEvent(&local_handle, EVENT_ALL_ACCESS, &object_attributes, + static_cast<EVENT_TYPE>(event_type), + static_cast<BOOLEAN>(initial_state != 0)); + if (!local_handle) + return status; + + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return status; +} + +NTSTATUS SyncPolicy::OpenEventAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& event_name, + uint32_t desired_access, + HANDLE* handle) { + NtOpenEventFunction NtOpenEvent = nullptr; + ResolveNTFunctionPtr("NtOpenEvent", &NtOpenEvent); + + // The only action supported is ASK_BROKER which means create the requested + // event as specified. + if (ASK_BROKER != eval_result) + return false; + + HANDLE object_directory = nullptr; + NTSTATUS status = GetBaseNamedObjectsDirectory(&object_directory); + if (status != STATUS_SUCCESS) + return status; + + UNICODE_STRING unicode_event_name = {}; + OBJECT_ATTRIBUTES object_attributes = {}; + InitObjectAttribs(event_name, OBJ_CASE_INSENSITIVE, object_directory, + &object_attributes, &unicode_event_name, nullptr); + + HANDLE local_handle = nullptr; + status = NtOpenEvent(&local_handle, desired_access, &object_attributes); + if (!local_handle) + return status; + + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return status; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sync_policy.h b/security/sandbox/chromium/sandbox/win/src/sync_policy.h new file mode 100644 index 0000000000..2eb4124a82 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_policy.h @@ -0,0 +1,49 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_SRC_SYNC_POLICY_H__ +#define SANDBOX_SRC_SYNC_POLICY_H__ + +#include <stdint.h> + +#include <string> + +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +// This class centralizes most of the knowledge related to sync policy +class SyncPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for sync calls, in particular open or create actions. + // name is the sync object name, semantics is the desired semantics for the + // open or create and policy is the policy generator to which the rules are + // going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Performs the desired policy action on a request. + // client_info is the target process that is making the request and + // eval_result is the desired policy action to accomplish. + static NTSTATUS CreateEventAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& event_name, + uint32_t event_type, + uint32_t initial_state, + HANDLE* handle); + static NTSTATUS OpenEventAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& event_name, + uint32_t desired_access, + HANDLE* handle); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SYNC_POLICY_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/sync_policy_test.cc b/security/sandbox/chromium/sandbox/win/src/sync_policy_test.cc new file mode 100644 index 0000000000..97f60d359f --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_policy_test.cc @@ -0,0 +1,145 @@ +// Copyright (c) 2011 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/win/src/sync_policy_test.h" + +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +SBOX_TESTS_COMMAND int Event_Open(int argc, wchar_t** argv) { + if (argc != 2) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + DWORD desired_access = SYNCHRONIZE; + if (L'f' == argv[0][0]) + desired_access = EVENT_ALL_ACCESS; + + base::win::ScopedHandle event_open( + ::OpenEvent(desired_access, false, argv[1])); + DWORD error_open = ::GetLastError(); + + if (event_open.IsValid()) + return SBOX_TEST_SUCCEEDED; + + if (ERROR_ACCESS_DENIED == error_open || ERROR_BAD_PATHNAME == error_open || + ERROR_FILE_NOT_FOUND == error_open) + return SBOX_TEST_DENIED; + + return SBOX_TEST_FAILED; +} + +SBOX_TESTS_COMMAND int Event_CreateOpen(int argc, wchar_t** argv) { + if (argc < 2 || argc > 3) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + wchar_t* event_name = nullptr; + if (3 == argc) + event_name = argv[2]; + + bool manual_reset = false; + bool initial_state = false; + if (L't' == argv[0][0]) + manual_reset = true; + if (L't' == argv[1][0]) + initial_state = true; + + base::win::ScopedHandle event_create( + ::CreateEvent(nullptr, manual_reset, initial_state, event_name)); + DWORD error_create = ::GetLastError(); + base::win::ScopedHandle event_open; + if (event_name) + event_open.Set(::OpenEvent(EVENT_ALL_ACCESS, false, event_name)); + + if (event_create.IsValid()) { + DWORD wait = ::WaitForSingleObject(event_create.Get(), 0); + if (initial_state && WAIT_OBJECT_0 != wait) + return SBOX_TEST_FAILED; + + if (!initial_state && WAIT_TIMEOUT != wait) + return SBOX_TEST_FAILED; + } + + if (event_name) { + // Both event_open and event_create have to be valid. + if (event_open.IsValid() && event_create.IsValid()) + return SBOX_TEST_SUCCEEDED; + + if ((event_open.IsValid() && !event_create.IsValid()) || + (!event_open.IsValid() && event_create.IsValid())) { + return SBOX_TEST_FAILED; + } + } else { + // Only event_create has to be valid. + if (event_create.Get()) + return SBOX_TEST_SUCCEEDED; + } + + if (ERROR_ACCESS_DENIED == error_create || ERROR_BAD_PATHNAME == error_create) + return SBOX_TEST_DENIED; + + return SBOX_TEST_FAILED; +} + +// Tests the creation of events using all the possible combinations. +TEST(SyncPolicyTest, TestEvent) { + TestRunner runner; + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_ANY, L"test1")); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_ANY, L"test2")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen f f")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen t f")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen f t")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen t t")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen f f test1")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen t f test2")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen f t test1")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen t t test2")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen f f test3")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen t f test4")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen f t test3")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen t t test4")); +} + +// Tests opening events with read only access. +TEST(SyncPolicyTest, TestEventReadOnly) { + TestRunner runner; + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_READONLY, L"test1")); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_READONLY, L"test2")); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_READONLY, L"test5")); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_READONLY, L"test6")); + + base::win::ScopedHandle handle1( + ::CreateEvent(nullptr, false, false, L"test1")); + base::win::ScopedHandle handle2( + ::CreateEvent(nullptr, false, false, L"test2")); + base::win::ScopedHandle handle3( + ::CreateEvent(nullptr, false, false, L"test3")); + base::win::ScopedHandle handle4( + ::CreateEvent(nullptr, false, false, L"test4")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen f f")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen t f")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_Open f test1")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_Open s test2")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_Open f test3")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_Open s test4")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen f f test5")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen t f test6")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen f t test5")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen t t test6")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sync_policy_test.h b/security/sandbox/chromium/sandbox/win/src/sync_policy_test.h new file mode 100644 index 0000000000..4f354b35be --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_policy_test.h @@ -0,0 +1,18 @@ +// 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. + +#ifndef SANDBOX_WIN_SRC_SYNC_POLICY_TEST_H_ +#define SANDBOX_WIN_SRC_SYNC_POLICY_TEST_H_ + +#include "sandbox/win/tests/common/controller.h" + +namespace sandbox { + +// Opens the named event received on argv[1]. The requested access is +// EVENT_ALL_ACCESS if argv[0] starts with 'f', or SYNCHRONIZE otherwise. +SBOX_TESTS_COMMAND int Event_Open(int argc, wchar_t **argv); + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SYNC_POLICY_TEST_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/target_interceptions.cc b/security/sandbox/chromium/sandbox/win/src/target_interceptions.cc new file mode 100644 index 0000000000..1b467814c6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/target_interceptions.cc @@ -0,0 +1,136 @@ +// Copyright (c) 2006-2008 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/win/src/target_interceptions.h" + +#include "base/win/static_constants.h" +#include "sandbox/win/src/interception_agent.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +SANDBOX_INTERCEPT NtExports g_nt; + +const char KERNEL32_DLL_NAME[] = "kernel32.dll"; + +enum SectionLoadState { + kBeforeKernel32, + kAfterKernel32, +}; + +// Hooks NtMapViewOfSection to detect the load of DLLs. If hot patching is +// required for this dll, this functions patches it. +NTSTATUS WINAPI +TargetNtMapViewOfSection(NtMapViewOfSectionFunction orig_MapViewOfSection, + HANDLE section, + HANDLE process, + PVOID* base, + ULONG_PTR zero_bits, + SIZE_T commit_size, + PLARGE_INTEGER offset, + PSIZE_T view_size, + SECTION_INHERIT inherit, + ULONG allocation_type, + ULONG protect) { + NTSTATUS ret = orig_MapViewOfSection(section, process, base, zero_bits, + commit_size, offset, view_size, inherit, + allocation_type, protect); + static SectionLoadState s_state = kBeforeKernel32; + + do { + if (!NT_SUCCESS(ret)) + break; + + if (!IsSameProcess(process)) + break; + + // Only check for verifier.dll or kernel32.dll loading if we haven't moved + // past that state yet. + if (s_state == kBeforeKernel32) { + const char* ansi_module_name = + GetAnsiImageInfoFromModule(reinterpret_cast<HMODULE>(*base)); + + // _strnicmp below may hit read access violations for some sections. We + // find what looks like a valid export directory for a PE module but the + // pointer to the module name will be pointing to invalid memory. + __try { + // Don't initialize the heap if verifier.dll is being loaded. This + // indicates Application Verifier is enabled and we should wait until + // the next module is loaded. + if (ansi_module_name && + (g_nt._strnicmp( + ansi_module_name, base::win::kApplicationVerifierDllName, + g_nt.strlen(base::win::kApplicationVerifierDllName) + 1) == 0)) + break; + + if (ansi_module_name && + (g_nt._strnicmp(ansi_module_name, KERNEL32_DLL_NAME, + sizeof(KERNEL32_DLL_NAME)) == 0)) { + s_state = kAfterKernel32; + } + } __except (EXCEPTION_EXECUTE_HANDLER) { + } + } + + if (!InitHeap()) + break; + + if (!IsValidImageSection(section, base, offset, view_size)) + break; + + UINT image_flags; + UNICODE_STRING* module_name = + GetImageInfoFromModule(reinterpret_cast<HMODULE>(*base), &image_flags); + UNICODE_STRING* file_name = GetBackingFilePath(*base); + + if ((!module_name) && (image_flags & MODULE_HAS_CODE)) { + // If the module has no exports we retrieve the module name from the + // full path of the mapped section. + module_name = ExtractModuleName(file_name); + } + + InterceptionAgent* agent = InterceptionAgent::GetInterceptionAgent(); + + if (agent) { + if (!agent->OnDllLoad(file_name, module_name, *base)) { + // Interception agent is demanding to un-map the module. + g_nt.UnmapViewOfSection(process, *base); + *base = nullptr; + ret = STATUS_UNSUCCESSFUL; + } + } + + if (module_name) + operator delete(module_name, NT_ALLOC); + + if (file_name) + operator delete(file_name, NT_ALLOC); + + } while (false); + + return ret; +} + +NTSTATUS WINAPI +TargetNtUnmapViewOfSection(NtUnmapViewOfSectionFunction orig_UnmapViewOfSection, + HANDLE process, + PVOID base) { + NTSTATUS ret = orig_UnmapViewOfSection(process, base); + + if (!NT_SUCCESS(ret)) + return ret; + + if (!IsSameProcess(process)) + return ret; + + InterceptionAgent* agent = InterceptionAgent::GetInterceptionAgent(); + + if (agent) + agent->OnDllUnload(base); + + return ret; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/target_interceptions.h b/security/sandbox/chromium/sandbox/win/src/target_interceptions.h new file mode 100644 index 0000000000..fac1c65c27 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/target_interceptions.h @@ -0,0 +1,43 @@ +// Copyright (c) 2006-2008 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. + +#ifndef SANDBOX_WIN_SRC_TARGET_INTERCEPTIONS_H_ +#define SANDBOX_WIN_SRC_TARGET_INTERCEPTIONS_H_ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +// Interception of NtMapViewOfSection on the child process. +// It should never be called directly. This function provides the means to +// detect dlls being loaded, so we can patch them if needed. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtMapViewOfSection(NtMapViewOfSectionFunction orig_MapViewOfSection, + HANDLE section, + HANDLE process, + PVOID* base, + ULONG_PTR zero_bits, + SIZE_T commit_size, + PLARGE_INTEGER offset, + PSIZE_T view_size, + SECTION_INHERIT inherit, + ULONG allocation_type, + ULONG protect); + +// Interception of NtUnmapViewOfSection on the child process. +// It should never be called directly. This function provides the means to +// detect dlls being unloaded, so we can clean up our interceptions. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtUnmapViewOfSection(NtUnmapViewOfSectionFunction orig_UnmapViewOfSection, + HANDLE process, + PVOID base); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_TARGET_INTERCEPTIONS_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/target_process.cc b/security/sandbox/chromium/sandbox/win/src/target_process.cc new file mode 100644 index 0000000000..4661267d1e --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/target_process.cc @@ -0,0 +1,393 @@ +// 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 "sandbox/win/src/target_process.h" + +#include <stddef.h> +#include <stdint.h> + +#include <memory> +#include <utility> +#include <vector> + +#include "base/macros.h" +#include "base/memory/free_deleter.h" +#include "base/numerics/safe_conversions.h" +#include "base/process/environment_internal.h" +#include "base/win/startup_information.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/security_capabilities.h" +#include "sandbox/win/src/sharedmem_ipc_server.h" +#include "sandbox/win/src/sid.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +namespace { + +void CopyPolicyToTarget(const void* source, size_t size, void* dest) { + if (!source || !size) + return; + memcpy(dest, source, size); + sandbox::PolicyGlobal* policy = + reinterpret_cast<sandbox::PolicyGlobal*>(dest); + + size_t offset = reinterpret_cast<size_t>(source); + + for (size_t i = 0; i < sandbox::kMaxServiceCount; i++) { + size_t buffer = reinterpret_cast<size_t>(policy->entry[i]); + if (buffer) { + buffer -= offset; + policy->entry[i] = reinterpret_cast<sandbox::PolicyBuffer*>(buffer); + } + } +} + +bool GetTokenAppContainerSid(HANDLE token_handle, + std::unique_ptr<Sid>* app_container_sid) { + std::vector<char> app_container_info(sizeof(TOKEN_APPCONTAINER_INFORMATION) + + SECURITY_MAX_SID_SIZE); + DWORD return_length; + + if (!::GetTokenInformation( + token_handle, TokenAppContainerSid, app_container_info.data(), + base::checked_cast<DWORD>(app_container_info.size()), + &return_length)) { + return false; + } + + PTOKEN_APPCONTAINER_INFORMATION info = + reinterpret_cast<PTOKEN_APPCONTAINER_INFORMATION>( + app_container_info.data()); + if (!info->TokenAppContainer) + return false; + *app_container_sid = std::unique_ptr<Sid>(new Sid(info->TokenAppContainer)); + return true; +} + +bool GetProcessAppContainerSid(HANDLE process, + std::unique_ptr<Sid>* app_container_sid) { + HANDLE token_handle; + if (!::OpenProcessToken(process, TOKEN_QUERY, &token_handle)) + return false; + base::win::ScopedHandle process_token(token_handle); + + return GetTokenAppContainerSid(process_token.Get(), app_container_sid); +} + +bool GetAppContainerImpersonationToken( + HANDLE process, + HANDLE initial_token, + const std::vector<Sid>& capabilities, + base::win::ScopedHandle* impersonation_token) { + std::unique_ptr<Sid> app_container_sid; + if (!GetProcessAppContainerSid(process, &app_container_sid)) { + return false; + } + SecurityCapabilities security_caps(*app_container_sid, capabilities); + return CreateLowBoxToken(initial_token, IMPERSONATION, &security_caps, + nullptr, 0, impersonation_token) == ERROR_SUCCESS; +} + +} // namespace + +SANDBOX_INTERCEPT HANDLE g_shared_section; +SANDBOX_INTERCEPT size_t g_shared_IPC_size; +SANDBOX_INTERCEPT size_t g_shared_policy_size; + +TargetProcess::TargetProcess(base::win::ScopedHandle initial_token, + base::win::ScopedHandle lockdown_token, + HANDLE job, + ThreadProvider* thread_pool, + const std::vector<Sid>& impersonation_capabilities) + // This object owns everything initialized here except thread_pool and + // the job_ handle. The Job handle is closed by BrokerServices and results + // eventually in a call to our dtor. + : lockdown_token_(std::move(lockdown_token)), + initial_token_(std::move(initial_token)), + job_(job), + thread_pool_(thread_pool), + base_address_(nullptr), + impersonation_capabilities_(impersonation_capabilities) {} + +TargetProcess::~TargetProcess() { + // Give a chance to the process to die. In most cases the JOB_KILL_ON_CLOSE + // will take effect only when the context changes. As far as the testing went, + // this wait was enough to switch context and kill the processes in the job. + // If this process is already dead, the function will return without waiting. + // For now, this wait is there only to do a best effort to prevent some leaks + // from showing up in purify. + if (sandbox_process_info_.IsValid()) { + ::WaitForSingleObject(sandbox_process_info_.process_handle(), 50); + // Terminate the process if it's still alive, as its IPC server is going + // away. 1 is RESULT_CODE_KILLED. + ::TerminateProcess(sandbox_process_info_.process_handle(), 1); + } + + // ipc_server_ references our process handle, so make sure the former is shut + // down before the latter is closed (by ScopedProcessInformation). + ipc_server_.reset(); +} + +// Creates the target (child) process suspended and assigns it to the job +// object. +ResultCode TargetProcess::Create( + const wchar_t* exe_path, + const wchar_t* command_line, + bool inherit_handles, + const base::win::StartupInformation& startup_info, + base::win::ScopedProcessInformation* target_info, + base::EnvironmentMap& env_changes, + DWORD* win_error) { + exe_name_.reset(_wcsdup(exe_path)); + + // the command line needs to be writable by CreateProcess(). + std::unique_ptr<wchar_t, base::FreeDeleter> cmd_line(_wcsdup(command_line)); + + // Start the target process suspended. + DWORD flags = + CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS; + + if (startup_info.has_extended_startup_info()) + flags |= EXTENDED_STARTUPINFO_PRESENT; + + if (job_ && base::win::GetVersion() < base::win::Version::WIN8) { + // Windows 8 implements nested jobs, but for older systems we need to + // break out of any job we're in to enforce our restrictions. + flags |= CREATE_BREAKAWAY_FROM_JOB; + } + + LPTCH original_environment = GetEnvironmentStrings(); + base::NativeEnvironmentString new_environment = + base::internal::AlterEnvironment(original_environment, env_changes); + // Ignore return value? What can we do? + FreeEnvironmentStrings(original_environment); + LPVOID new_env_ptr = (void*)new_environment.data(); + + PROCESS_INFORMATION temp_process_info = {}; + if (!::CreateProcessAsUserW(lockdown_token_.Get(), exe_path, cmd_line.get(), + nullptr, // No security attribute. + nullptr, // No thread attribute. + inherit_handles, flags, + new_env_ptr, + nullptr, // Use current directory of the caller. + startup_info.startup_info(), + &temp_process_info)) { + *win_error = ::GetLastError(); + return SBOX_ERROR_CREATE_PROCESS; + } + base::win::ScopedProcessInformation process_info(temp_process_info); + + if (job_) { + // Assign the suspended target to the windows job object. + if (!::AssignProcessToJobObject(job_, process_info.process_handle())) { + *win_error = ::GetLastError(); + ::TerminateProcess(process_info.process_handle(), 0); + return SBOX_ERROR_ASSIGN_PROCESS_TO_JOB_OBJECT; + } + } + + if (initial_token_.IsValid()) { + HANDLE impersonation_token = initial_token_.Get(); + base::win::ScopedHandle app_container_token; + if (GetAppContainerImpersonationToken( + process_info.process_handle(), impersonation_token, + impersonation_capabilities_, &app_container_token)) { + impersonation_token = app_container_token.Get(); + } + + // Change the token of the main thread of the new process for the + // impersonation token with more rights. This allows the target to start; + // otherwise it will crash too early for us to help. + HANDLE temp_thread = process_info.thread_handle(); + if (!::SetThreadToken(&temp_thread, impersonation_token)) { + *win_error = ::GetLastError(); + ::TerminateProcess(process_info.process_handle(), 0); + return SBOX_ERROR_SET_THREAD_TOKEN; + } + initial_token_.Close(); + } + + if (!target_info->DuplicateFrom(process_info)) { + *win_error = ::GetLastError(); // This may or may not be correct. + ::TerminateProcess(process_info.process_handle(), 0); + return SBOX_ERROR_DUPLICATE_TARGET_INFO; + } + + base_address_ = GetProcessBaseAddress(process_info.process_handle()); + DCHECK(base_address_); + if (!base_address_) { + *win_error = ::GetLastError(); + ::TerminateProcess(process_info.process_handle(), 0); + return SBOX_ERROR_CANNOT_FIND_BASE_ADDRESS; + } + + sandbox_process_info_.Set(process_info.Take()); + return SBOX_ALL_OK; +} + +ResultCode TargetProcess::TransferVariable(const char* name, + void* address, + size_t size) { + if (!sandbox_process_info_.IsValid()) + return SBOX_ERROR_UNEXPECTED_CALL; + + void* child_var = address; + +#if SANDBOX_EXPORTS + HMODULE module = ::LoadLibrary(exe_name_.get()); + if (!module) + return SBOX_ERROR_CANNOT_LOADLIBRARY_EXECUTABLE; + + child_var = reinterpret_cast<void*>(::GetProcAddress(module, name)); + ::FreeLibrary(module); + + if (!child_var) + return SBOX_ERROR_CANNOT_FIND_VARIABLE_ADDRESS; + + size_t offset = + reinterpret_cast<char*>(child_var) - reinterpret_cast<char*>(module); + child_var = reinterpret_cast<char*>(MainModule()) + offset; +#endif + + SIZE_T written; + if (!::WriteProcessMemory(sandbox_process_info_.process_handle(), child_var, + address, size, &written)) + return SBOX_ERROR_CANNOT_WRITE_VARIABLE_VALUE; + + if (written != size) + return SBOX_ERROR_INVALID_WRITE_VARIABLE_SIZE; + + return SBOX_ALL_OK; +} + +// Construct the IPC server and the IPC dispatcher. When the target does +// an IPC it will eventually call the dispatcher. +ResultCode TargetProcess::Init(Dispatcher* ipc_dispatcher, + void* policy, + uint32_t shared_IPC_size, + uint32_t shared_policy_size, + DWORD* win_error) { + // We need to map the shared memory on the target. This is necessary for + // any IPC that needs to take place, even if the target has not yet hit + // the main( ) function or even has initialized the CRT. So here we set + // the handle to the shared section. The target on the first IPC must do + // the rest, which boils down to calling MapViewofFile() + + // We use this single memory pool for IPC and for policy. + DWORD shared_mem_size = + static_cast<DWORD>(shared_IPC_size + shared_policy_size); + shared_section_.Set(::CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, + PAGE_READWRITE | SEC_COMMIT, 0, + shared_mem_size, nullptr)); + if (!shared_section_.IsValid()) { + *win_error = ::GetLastError(); + return SBOX_ERROR_CREATE_FILE_MAPPING; + } + + void* shared_memory = ::MapViewOfFile( + shared_section_.Get(), FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, 0); + if (!shared_memory) { + *win_error = ::GetLastError(); + return SBOX_ERROR_MAP_VIEW_OF_SHARED_SECTION; + } + + CopyPolicyToTarget(policy, shared_policy_size, + reinterpret_cast<char*>(shared_memory) + shared_IPC_size); + + ResultCode ret; + // Set the global variables in the target. These are not used on the broker. + g_shared_IPC_size = shared_IPC_size; + ret = TransferVariable("g_shared_IPC_size", &g_shared_IPC_size, + sizeof(g_shared_IPC_size)); + g_shared_IPC_size = 0; + if (SBOX_ALL_OK != ret) { + *win_error = ::GetLastError(); + return ret; + } + g_shared_policy_size = shared_policy_size; + ret = TransferVariable("g_shared_policy_size", &g_shared_policy_size, + sizeof(g_shared_policy_size)); + g_shared_policy_size = 0; + if (SBOX_ALL_OK != ret) { + *win_error = ::GetLastError(); + return ret; + } + + ipc_server_.reset(new SharedMemIPCServer( + sandbox_process_info_.process_handle(), + sandbox_process_info_.process_id(), thread_pool_, ipc_dispatcher)); + + if (!ipc_server_->Init(shared_memory, shared_IPC_size, kIPCChannelSize)) + return SBOX_ERROR_NO_SPACE; + + DWORD access = FILE_MAP_READ | FILE_MAP_WRITE | SECTION_QUERY; + HANDLE target_shared_section; + if (!::DuplicateHandle(::GetCurrentProcess(), shared_section_.Get(), + sandbox_process_info_.process_handle(), + &target_shared_section, access, false, 0)) { + *win_error = ::GetLastError(); + return SBOX_ERROR_DUPLICATE_SHARED_SECTION; + } + + g_shared_section = target_shared_section; + ret = TransferVariable("g_shared_section", &g_shared_section, + sizeof(g_shared_section)); + g_shared_section = nullptr; + if (SBOX_ALL_OK != ret) { + *win_error = ::GetLastError(); + return ret; + } + + // After this point we cannot use this handle anymore. + ::CloseHandle(sandbox_process_info_.TakeThreadHandle()); + + return SBOX_ALL_OK; +} + +void TargetProcess::Terminate() { + if (!sandbox_process_info_.IsValid()) + return; + + ::TerminateProcess(sandbox_process_info_.process_handle(), 0); +} + +ResultCode TargetProcess::AssignLowBoxToken( + const base::win::ScopedHandle& token) { + if (!token.IsValid()) + return SBOX_ALL_OK; + PROCESS_ACCESS_TOKEN process_access_token = {}; + process_access_token.token = token.Get(); + + NtSetInformationProcess SetInformationProcess = nullptr; + ResolveNTFunctionPtr("NtSetInformationProcess", &SetInformationProcess); + + NTSTATUS status = SetInformationProcess( + sandbox_process_info_.process_handle(), + static_cast<PROCESS_INFORMATION_CLASS>(NtProcessInformationAccessToken), + &process_access_token, sizeof(process_access_token)); + if (!NT_SUCCESS(status)) { + ::SetLastError(GetLastErrorFromNtStatus(status)); + return SBOX_ERROR_SET_LOW_BOX_TOKEN; + } + return SBOX_ALL_OK; +} + +TargetProcess* MakeTestTargetProcess(HANDLE process, HMODULE base_address) { + TargetProcess* target = + new TargetProcess(base::win::ScopedHandle(), base::win::ScopedHandle(), + nullptr, nullptr, std::vector<Sid>()); + PROCESS_INFORMATION process_info = {}; + process_info.hProcess = process; + target->sandbox_process_info_.Set(process_info); + target->base_address_ = base_address; + return target; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/target_process.h b/security/sandbox/chromium/sandbox/win/src/target_process.h new file mode 100644 index 0000000000..c489ed7bc1 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/target_process.h @@ -0,0 +1,143 @@ +// 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. + +#ifndef SANDBOX_WIN_SRC_TARGET_PROCESS_H_ +#define SANDBOX_WIN_SRC_TARGET_PROCESS_H_ + +#include <windows.h> + +#include <stddef.h> +#include <stdint.h> + +#include <memory> +#include <vector> + +#include "base/macros.h" +#include "base/environment.h" +#include "base/memory/free_deleter.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace base { +namespace win { + +class StartupInformation; + +} // namespace win +} // namespace base + +namespace sandbox { + +class SharedMemIPCServer; +class Sid; +class ThreadProvider; + +// TargetProcess models a target instance (child process). Objects of this +// class are owned by the Policy used to create them. +class TargetProcess { + public: + // The constructor takes ownership of |initial_token| and |lockdown_token| + TargetProcess(base::win::ScopedHandle initial_token, + base::win::ScopedHandle lockdown_token, + HANDLE job, + ThreadProvider* thread_pool, + const std::vector<Sid>& impersonation_capabilities); + ~TargetProcess(); + + // TODO(cpu): Currently there does not seem to be a reason to implement + // reference counting for this class since is internal, but kept the + // the same interface so the interception framework does not need to be + // touched at this point. + void AddRef() {} + void Release() {} + + // Creates the new target process. The process is created suspended. + ResultCode Create(const wchar_t* exe_path, + const wchar_t* command_line, + bool inherit_handles, + const base::win::StartupInformation& startup_info, + base::win::ScopedProcessInformation* target_info, + base::EnvironmentMap& env_map, + DWORD* win_error); + + // Assign a new lowbox token to the process post creation. The process + // must still be in its initial suspended state, however this still + // might fail in the presence of third-party software. + ResultCode AssignLowBoxToken(const base::win::ScopedHandle& token); + + // Destroys the target process. + void Terminate(); + + // Creates the IPC objects such as the BrokerDispatcher and the + // IPC server. The IPC server uses the services of the thread_pool. + ResultCode Init(Dispatcher* ipc_dispatcher, + void* policy, + uint32_t shared_IPC_size, + uint32_t shared_policy_size, + DWORD* win_error); + + // Returns the handle to the target process. + HANDLE Process() const { return sandbox_process_info_.process_handle(); } + + // Returns the handle to the job object that the target process belongs to. + HANDLE Job() const { return job_; } + + // Returns the address of the target main exe. This is used by the + // interceptions framework. + HMODULE MainModule() const { + return reinterpret_cast<HMODULE>(base_address_); + } + + // Returns the name of the executable. + const wchar_t* Name() const { return exe_name_.get(); } + + // Returns the process id. + DWORD ProcessId() const { return sandbox_process_info_.process_id(); } + + // Returns the handle to the main thread. + HANDLE MainThread() const { return sandbox_process_info_.thread_handle(); } + + // Transfers variable at |address| of |size| bytes from broker to target. + ResultCode TransferVariable(const char* name, void* address, size_t size); + + private: + // Details of the target process. + base::win::ScopedProcessInformation sandbox_process_info_; + // The token associated with the process. It provides the core of the + // sbox security. + base::win::ScopedHandle lockdown_token_; + // The token given to the initial thread so that the target process can + // start. It has more powers than the lockdown_token. + base::win::ScopedHandle initial_token_; + // Kernel handle to the shared memory used by the IPC server. + base::win::ScopedHandle shared_section_; + // Job object containing the target process. + HANDLE job_; + // Reference to the IPC subsystem. + std::unique_ptr<SharedMemIPCServer> ipc_server_; + // Provides the threads used by the IPC. This class does not own this pointer. + ThreadProvider* thread_pool_; + // Base address of the main executable + void* base_address_; + // Full name of the target executable. + std::unique_ptr<wchar_t, base::FreeDeleter> exe_name_; + /// List of capability sids for use when impersonating in an AC process. + std::vector<Sid> impersonation_capabilities_; + + // Function used for testing. + friend TargetProcess* MakeTestTargetProcess(HANDLE process, + HMODULE base_address); + + DISALLOW_IMPLICIT_CONSTRUCTORS(TargetProcess); +}; + +// Creates a mock TargetProcess used for testing interceptions. +// TODO(cpu): It seems that this method is not going to be used anymore. +TargetProcess* MakeTestTargetProcess(HANDLE process, HMODULE base_address); + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_TARGET_PROCESS_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/target_services.cc b/security/sandbox/chromium/sandbox/win/src/target_services.cc new file mode 100644 index 0000000000..a80e0106ef --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/target_services.cc @@ -0,0 +1,264 @@ +// 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 "sandbox/win/src/target_services.h" + +#include <new> + +#include <process.h> +#include <stdint.h> + +#include "base/win/windows_version.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/handle_closer_agent.h" +#include "sandbox/win/src/handle_interception.h" +#include "sandbox/win/src/heap_helper.h" +#include "sandbox/win/src/line_break_interception.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/process_mitigations.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" + +namespace sandbox { +namespace { + +// Flushing a cached key is triggered by just opening the key and closing the +// resulting handle. RegDisablePredefinedCache() is the documented way to flush +// HKCU so do not use it with this function. +bool FlushRegKey(HKEY root) { + HKEY key; + if (ERROR_SUCCESS == + ::RegOpenKeyExW(root, nullptr, 0, MAXIMUM_ALLOWED, &key)) { + if (ERROR_SUCCESS != ::RegCloseKey(key)) + return false; + } + return true; +} + +// This function forces advapi32.dll to release some internally cached handles +// that were made during calls to RegOpenkey and RegOpenKeyEx if it is called +// with a more restrictive token. Returns true if the flushing is succesful +// although this behavior is undocumented and there is no guarantee that in +// fact this will happen in future versions of windows. +bool FlushCachedRegHandles() { + return (FlushRegKey(HKEY_LOCAL_MACHINE) && FlushRegKey(HKEY_CLASSES_ROOT) && + FlushRegKey(HKEY_USERS)); +} + +// Cleans up this process if CSRSS will be disconnected, as this disconnection +// is not supported Windows behavior. +// Currently, this step requires closing a heap that this shared with csrss.exe. +// Closing the ALPC Port handle to csrss.exe leaves this heap in an invalid +// state. This causes problems if anyone enumerates the heap. +bool CsrssDisconnectCleanup() { + HANDLE csr_port_heap = FindCsrPortHeap(); + if (!csr_port_heap) { + DLOG(ERROR) << "Failed to find CSR Port heap handle"; + return false; + } + HeapDestroy(csr_port_heap); + return true; +} + +// Used by EnumSystemLocales for warming up. +static BOOL CALLBACK EnumLocalesProcEx(LPWSTR lpLocaleString, + DWORD dwFlags, + LPARAM lParam) { + return TRUE; +} + +// Additional warmup done just when CSRSS is being disconnected. +bool CsrssDisconnectWarmup() { + return ::EnumSystemLocalesEx(EnumLocalesProcEx, LOCALE_WINDOWS, 0, 0); +} + +// Checks if we have handle entries pending and runs the closer. +// Updates is_csrss_connected based on which handle types are closed. +bool CloseOpenHandles(bool* is_csrss_connected) { + if (HandleCloserAgent::NeedsHandlesClosed()) { + HandleCloserAgent handle_closer; + handle_closer.InitializeHandlesToClose(is_csrss_connected); + if (!*is_csrss_connected) { + if (!CsrssDisconnectWarmup() || !CsrssDisconnectCleanup()) { + return false; + } + } + if (!handle_closer.CloseHandles()) + return false; + } + return true; +} + +// Warm up language subsystems before the sandbox is turned on. +// Tested on Win8.1 x64: +// This needs to happen after RevertToSelf() is called, because (at least) in +// the case of GetUserDefaultLCID() it checks the TEB to see if the process is +// impersonating (TEB!IsImpersonating). If it is, the cached locale information +// is not used, nor is it set. Therefore, calls after RevertToSelf() will not +// have warmed-up values to use. +bool WarmupWindowsLocales() { + // NOTE(liamjm): When last checked (Win 8.1 x64) it wasn't necessary to + // warmup all of these functions, but let's not assume that. + ::GetUserDefaultLangID(); + ::GetUserDefaultLCID(); + wchar_t localeName[LOCALE_NAME_MAX_LENGTH] = {0}; + return (0 != ::GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH)); +} + +// Used as storage for g_target_services, because other allocation facilities +// are not available early. We can't use a regular function static because on +// VS2015, because the CRT tries to acquire a lock to guard initialization, but +// this code runs before the CRT is initialized. +char g_target_services_memory[sizeof(TargetServicesBase)]; +TargetServicesBase* g_target_services = nullptr; + +} // namespace + +SANDBOX_INTERCEPT IntegrityLevel g_shared_delayed_integrity_level = + INTEGRITY_LEVEL_LAST; +SANDBOX_INTERCEPT MitigationFlags g_shared_delayed_mitigations = 0; + +TargetServicesBase::TargetServicesBase() {} + +ResultCode TargetServicesBase::Init() { + process_state_.SetInitCalled(); + return SBOX_ALL_OK; +} + +// Failure here is a breach of security so the process is terminated. +void TargetServicesBase::LowerToken() { + if (ERROR_SUCCESS != + SetProcessIntegrityLevel(g_shared_delayed_integrity_level)) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_INTEGRITY); + process_state_.SetRevertedToSelf(); + // If the client code as called RegOpenKey, advapi32.dll has cached some + // handles. The following code gets rid of them. + if (!::RevertToSelf()) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_DROPTOKEN); + if (!FlushCachedRegHandles()) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_FLUSHANDLES); + if (ERROR_SUCCESS != ::RegDisablePredefinedCache()) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_CACHEDISABLE); + if (!WarmupWindowsLocales()) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_WARMUP); + bool is_csrss_connected = true; + if (!CloseOpenHandles(&is_csrss_connected)) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_CLOSEHANDLES); + process_state_.SetCsrssConnected(is_csrss_connected); + // Enabling mitigations must happen last otherwise handle closing breaks + if (g_shared_delayed_mitigations && + !ApplyProcessMitigationsToCurrentProcess(g_shared_delayed_mitigations)) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_MITIGATION); +} + +ProcessState* TargetServicesBase::GetState() { + return &process_state_; +} + +TargetServicesBase* TargetServicesBase::GetInstance() { + // Leak on purpose TargetServicesBase. + if (!g_target_services) + g_target_services = new (g_target_services_memory) TargetServicesBase; + return g_target_services; +} + +// The broker services a 'test' IPC service with the PING tag. +bool TargetServicesBase::TestIPCPing(int version) { + void* memory = GetGlobalIPCMemory(); + if (!memory) + return false; + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + + if (1 == version) { + uint32_t tick1 = ::GetTickCount(); + uint32_t cookie = 717115; + ResultCode code = CrossCall(ipc, IpcTag::PING1, cookie, &answer); + + if (SBOX_ALL_OK != code) { + return false; + } + // We should get two extended returns values from the IPC, one is the + // tick count on the broker and the other is the cookie times two. + if ((answer.extended_count != 2)) { + return false; + } + // We test the first extended answer to be within the bounds of the tick + // count only if there was no tick count wraparound. + uint32_t tick2 = ::GetTickCount(); + if (tick2 >= tick1) { + if ((answer.extended[0].unsigned_int < tick1) || + (answer.extended[0].unsigned_int > tick2)) { + return false; + } + } + + if (answer.extended[1].unsigned_int != cookie * 2) { + return false; + } + } else if (2 == version) { + uint32_t cookie = 717111; + InOutCountedBuffer counted_buffer(&cookie, sizeof(cookie)); + ResultCode code = CrossCall(ipc, IpcTag::PING2, counted_buffer, &answer); + + if (SBOX_ALL_OK != code) { + return false; + } + if (cookie != 717111 * 3) { + return false; + } + } else { + return false; + } + return true; +} + +ProcessState::ProcessState() + : process_state_(ProcessStateInternal::NONE), csrss_connected_(true) {} + +bool ProcessState::InitCalled() const { + return process_state_ >= ProcessStateInternal::INIT_CALLED; +} + +bool ProcessState::RevertedToSelf() const { + return process_state_ >= ProcessStateInternal::REVERTED_TO_SELF; +} + +bool ProcessState::IsCsrssConnected() const { + return csrss_connected_; +} + +void ProcessState::SetInitCalled() { + if (process_state_ < ProcessStateInternal::INIT_CALLED) + process_state_ = ProcessStateInternal::INIT_CALLED; +} + +void ProcessState::SetRevertedToSelf() { + if (process_state_ < ProcessStateInternal::REVERTED_TO_SELF) + process_state_ = ProcessStateInternal::REVERTED_TO_SELF; +} + +void ProcessState::SetCsrssConnected(bool csrss_connected) { + csrss_connected_ = csrss_connected; +} + +ResultCode TargetServicesBase::DuplicateHandle(HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options) { + return sandbox::DuplicateHandleProxy(source_handle, target_process_id, + target_handle, desired_access, options); +} + +ResultCode TargetServicesBase::GetComplexLineBreaks(const WCHAR* text, + uint32_t length, + uint8_t* break_before) { + return sandbox::GetComplexLineBreaksProxy(text, length, break_before); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/target_services.h b/security/sandbox/chromium/sandbox/win/src/target_services.h new file mode 100644 index 0000000000..1d70d4cd34 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/target_services.h @@ -0,0 +1,73 @@ +// 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. + +#ifndef SANDBOX_SRC_TARGET_SERVICES_H__ +#define SANDBOX_SRC_TARGET_SERVICES_H__ + +#include "base/macros.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +class ProcessState { + public: + ProcessState(); + // Returns true if main has been called. + bool InitCalled() const; + // Returns true if LowerToken has been called. + bool RevertedToSelf() const; + // Returns true if Csrss is connected. + bool IsCsrssConnected() const; + // Set the current state. + void SetInitCalled(); + void SetRevertedToSelf(); + void SetCsrssConnected(bool csrss_connected); + + private: + enum class ProcessStateInternal { NONE = 0, INIT_CALLED, REVERTED_TO_SELF }; + + ProcessStateInternal process_state_; + bool csrss_connected_; + DISALLOW_COPY_AND_ASSIGN(ProcessState); +}; + +// This class is an implementation of the TargetServices. +// Look in the documentation of sandbox::TargetServices for more info. +// Do NOT add a destructor to this class without changing the implementation of +// the factory method. +class TargetServicesBase : public TargetServices { + public: + TargetServicesBase(); + + // Public interface of TargetServices. + ResultCode Init() override; + void LowerToken() override; + ProcessState* GetState() override; + ResultCode DuplicateHandle(HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options) override; + ResultCode GetComplexLineBreaks(const WCHAR* text, uint32_t length, + uint8_t* break_before) final; + + // Factory method. + static TargetServicesBase* GetInstance(); + + // Sends a simple IPC Message that has a well-known answer. Returns true + // if the IPC was successful and false otherwise. There are 2 versions of + // this test: 1 and 2. The first one send a simple message while the + // second one send a message with an in/out param. + bool TestIPCPing(int version); + + private: + ~TargetServicesBase() {} + ProcessState process_state_; + DISALLOW_COPY_AND_ASSIGN(TargetServicesBase); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_TARGET_SERVICES_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/threadpool_unittest.cc b/security/sandbox/chromium/sandbox/win/src/threadpool_unittest.cc new file mode 100644 index 0000000000..3f951b761b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/threadpool_unittest.cc @@ -0,0 +1,97 @@ +// Copyright (c) 2006-2008 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/win/src/win2k_threadpool.h" + +#include <stdint.h> + +#include "testing/gtest/include/gtest/gtest.h" + +void __stdcall EmptyCallBack(void*, unsigned char) {} + +void __stdcall TestCallBack(void* context, unsigned char) { + HANDLE event = reinterpret_cast<HANDLE>(context); + ::SetEvent(event); +} + +namespace sandbox { + +// Test that register and unregister work, part 1. +TEST(IPCTest, ThreadPoolRegisterTest1) { + Win2kThreadPool thread_pool; + + EXPECT_EQ(0u, thread_pool.OutstandingWaits()); + + HANDLE event1 = ::CreateEventW(nullptr, false, false, nullptr); + HANDLE event2 = ::CreateEventW(nullptr, false, false, nullptr); + + uint32_t context = 0; + EXPECT_FALSE(thread_pool.RegisterWait(0, event1, EmptyCallBack, &context)); + EXPECT_EQ(0u, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(thread_pool.RegisterWait(this, event1, EmptyCallBack, &context)); + EXPECT_EQ(1u, thread_pool.OutstandingWaits()); + EXPECT_TRUE(thread_pool.RegisterWait(this, event2, EmptyCallBack, &context)); + EXPECT_EQ(2u, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(thread_pool.UnRegisterWaits(this)); + EXPECT_EQ(0u, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(::CloseHandle(event1)); + EXPECT_TRUE(::CloseHandle(event2)); +} + +// Test that register and unregister work, part 2. +TEST(IPCTest, ThreadPoolRegisterTest2) { + Win2kThreadPool thread_pool; + + HANDLE event1 = ::CreateEventW(nullptr, false, false, nullptr); + HANDLE event2 = ::CreateEventW(nullptr, false, false, nullptr); + + uint32_t context = 0; + uint32_t c1 = 0; + uint32_t c2 = 0; + + EXPECT_TRUE(thread_pool.RegisterWait(&c1, event1, EmptyCallBack, &context)); + EXPECT_EQ(1u, thread_pool.OutstandingWaits()); + EXPECT_TRUE(thread_pool.RegisterWait(&c2, event2, EmptyCallBack, &context)); + EXPECT_EQ(2u, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(thread_pool.UnRegisterWaits(&c2)); + EXPECT_EQ(1u, thread_pool.OutstandingWaits()); + EXPECT_TRUE(thread_pool.UnRegisterWaits(&c2)); + EXPECT_EQ(1u, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(thread_pool.UnRegisterWaits(&c1)); + EXPECT_EQ(0u, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(::CloseHandle(event1)); + EXPECT_TRUE(::CloseHandle(event2)); +} + +// Test that the thread pool has at least a thread that services an event. +// Test that when the event is un-registered is no longer serviced. +TEST(IPCTest, ThreadPoolSignalAndWaitTest) { + Win2kThreadPool thread_pool; + + // The events are auto reset and start not signaled. + HANDLE event1 = ::CreateEventW(nullptr, false, false, nullptr); + HANDLE event2 = ::CreateEventW(nullptr, false, false, nullptr); + + EXPECT_TRUE(thread_pool.RegisterWait(this, event1, TestCallBack, event2)); + + EXPECT_EQ(WAIT_OBJECT_0, ::SignalObjectAndWait(event1, event2, 5000, false)); + EXPECT_EQ(WAIT_OBJECT_0, ::SignalObjectAndWait(event1, event2, 5000, false)); + + EXPECT_TRUE(thread_pool.UnRegisterWaits(this)); + EXPECT_EQ(0u, thread_pool.OutstandingWaits()); + + EXPECT_EQ(static_cast<DWORD>(WAIT_TIMEOUT), + ::SignalObjectAndWait(event1, event2, 1000, false)); + + EXPECT_TRUE(::CloseHandle(event1)); + EXPECT_TRUE(::CloseHandle(event2)); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/top_level_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/top_level_dispatcher.cc new file mode 100644 index 0000000000..3c8f8e25e5 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/top_level_dispatcher.cc @@ -0,0 +1,178 @@ +// Copyright 2015 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/win/src/top_level_dispatcher.h" + +#include <stdint.h> +#include <string.h> + +#include "base/logging.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/filesystem_dispatcher.h" +#include "sandbox/win/src/handle_dispatcher.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/internal_types.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/line_break_dispatcher.h" +#include "sandbox/win/src/named_pipe_dispatcher.h" +#include "sandbox/win/src/process_mitigations_win32k_dispatcher.h" +#include "sandbox/win/src/process_thread_dispatcher.h" +#include "sandbox/win/src/registry_dispatcher.h" +#include "sandbox/win/src/sandbox_policy_base.h" +#include "sandbox/win/src/signed_dispatcher.h" +#include "sandbox/win/src/sync_dispatcher.h" + +namespace sandbox { + +TopLevelDispatcher::TopLevelDispatcher(PolicyBase* policy) : policy_(policy) { + // Initialize the IPC dispatcher array. + memset(ipc_targets_, 0, sizeof(ipc_targets_)); + Dispatcher* dispatcher; + + dispatcher = new FilesystemDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::NTCREATEFILE)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTOPENFILE)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTSETINFO_RENAME)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTQUERYATTRIBUTESFILE)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTQUERYFULLATTRIBUTESFILE)] = + dispatcher; + filesystem_dispatcher_.reset(dispatcher); + + dispatcher = new NamedPipeDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::CREATENAMEDPIPEW)] = dispatcher; + named_pipe_dispatcher_.reset(dispatcher); + + dispatcher = new ThreadProcessDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::NTOPENTHREAD)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTOPENPROCESS)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::CREATEPROCESSW)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTOPENPROCESSTOKEN)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTOPENPROCESSTOKENEX)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::CREATETHREAD)] = dispatcher; + thread_process_dispatcher_.reset(dispatcher); + + dispatcher = new SyncDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::CREATEEVENT)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::OPENEVENT)] = dispatcher; + sync_dispatcher_.reset(dispatcher); + + dispatcher = new RegistryDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::NTCREATEKEY)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTOPENKEY)] = dispatcher; + registry_dispatcher_.reset(dispatcher); + + dispatcher = new HandleDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::DUPLICATEHANDLEPROXY)] = dispatcher; + handle_dispatcher_.reset(dispatcher); + + dispatcher = new ProcessMitigationsWin32KDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::GDI_GDIDLLINITIALIZE)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_GETSTOCKOBJECT)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::USER_REGISTERCLASSW)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::USER_ENUMDISPLAYMONITORS)] = + dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::USER_ENUMDISPLAYDEVICES)] = + dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::USER_GETMONITORINFO)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_CREATEOPMPROTECTEDOUTPUTS)] = + dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_GETCERTIFICATE)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_GETCERTIFICATESIZE)] = + dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_DESTROYOPMPROTECTEDOUTPUT)] = + dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_CONFIGUREOPMPROTECTEDOUTPUT)] = + dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_GETOPMINFORMATION)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_GETOPMRANDOMNUMBER)] = + dispatcher; + ipc_targets_[static_cast<size_t>( + IpcTag::GDI_GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE)] = dispatcher; + ipc_targets_[static_cast<size_t>( + IpcTag::GDI_SETOPMSIGNINGKEYANDSEQUENCENUMBERS)] = dispatcher; + process_mitigations_win32k_dispatcher_.reset(dispatcher); + + dispatcher = new SignedDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::NTCREATESECTION)] = dispatcher; + signed_dispatcher_.reset(dispatcher); + + dispatcher = new LineBreakDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::GETCOMPLEXLINEBREAKS)] = dispatcher; + line_break_dispatcher_.reset(dispatcher); +} + +TopLevelDispatcher::~TopLevelDispatcher() {} + +// When an IPC is ready in any of the targets we get called. We manage an array +// of IPC dispatchers which are keyed on the IPC tag so we normally delegate +// to the appropriate dispatcher unless we can handle the IPC call ourselves. +Dispatcher* TopLevelDispatcher::OnMessageReady(IPCParams* ipc, + CallbackGeneric* callback) { + DCHECK(callback); + static const IPCParams ping1 = {IpcTag::PING1, {UINT32_TYPE}}; + static const IPCParams ping2 = {IpcTag::PING2, {INOUTPTR_TYPE}}; + + if (ping1.Matches(ipc) || ping2.Matches(ipc)) { + *callback = reinterpret_cast<CallbackGeneric>( + static_cast<Callback1>(&TopLevelDispatcher::Ping)); + return this; + } + + Dispatcher* dispatcher = GetDispatcher(ipc->ipc_tag); + if (!dispatcher) { + NOTREACHED(); + return nullptr; + } + return dispatcher->OnMessageReady(ipc, callback); +} + +// Delegate to the appropriate dispatcher. +bool TopLevelDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + if (IpcTag::PING1 == service || IpcTag::PING2 == service) + return true; + + Dispatcher* dispatcher = GetDispatcher(service); + if (!dispatcher) { + NOTREACHED(); + return false; + } + return dispatcher->SetupService(manager, service); +} + +// We service PING message which is a way to test a round trip of the +// IPC subsystem. We receive a integer cookie and we are expected to return the +// cookie times two (or three) and the current tick count. +bool TopLevelDispatcher::Ping(IPCInfo* ipc, void* arg1) { + switch (ipc->ipc_tag) { + case IpcTag::PING1: { + IPCInt ipc_int(arg1); + uint32_t cookie = ipc_int.As32Bit(); + ipc->return_info.extended_count = 2; + ipc->return_info.extended[0].unsigned_int = ::GetTickCount(); + ipc->return_info.extended[1].unsigned_int = 2 * cookie; + return true; + } + case IpcTag::PING2: { + CountedBuffer* io_buffer = reinterpret_cast<CountedBuffer*>(arg1); + if (sizeof(uint32_t) != io_buffer->Size()) + return false; + + uint32_t* cookie = reinterpret_cast<uint32_t*>(io_buffer->Buffer()); + *cookie = (*cookie) * 3; + return true; + } + default: + return false; + } +} + +Dispatcher* TopLevelDispatcher::GetDispatcher(IpcTag ipc_tag) { + if (ipc_tag >= IpcTag::LAST || ipc_tag <= IpcTag::UNUSED) + return nullptr; + + return ipc_targets_[static_cast<size_t>(ipc_tag)]; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/top_level_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/top_level_dispatcher.h new file mode 100644 index 0000000000..a80ce1ef5d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/top_level_dispatcher.h @@ -0,0 +1,54 @@ +// Copyright 2015 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. + +#ifndef SANDBOX_SRC_TOP_LEVEL_DISPATCHER_H__ +#define SANDBOX_SRC_TOP_LEVEL_DISPATCHER_H__ + +#include <memory> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// Top level dispatcher which hands requests to the appropriate service +// dispatchers. +class TopLevelDispatcher : public Dispatcher { + public: + // |policy| must outlive this class. + explicit TopLevelDispatcher(PolicyBase* policy); + ~TopLevelDispatcher() override; + + Dispatcher* OnMessageReady(IPCParams* ipc, + CallbackGeneric* callback) override; + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Test IPC provider. + bool Ping(IPCInfo* ipc, void* cookie); + + // Returns a dispatcher from ipc_targets_. + Dispatcher* GetDispatcher(IpcTag ipc_tag); + + PolicyBase* policy_; + std::unique_ptr<Dispatcher> filesystem_dispatcher_; + std::unique_ptr<Dispatcher> named_pipe_dispatcher_; + std::unique_ptr<Dispatcher> thread_process_dispatcher_; + std::unique_ptr<Dispatcher> sync_dispatcher_; + std::unique_ptr<Dispatcher> registry_dispatcher_; + std::unique_ptr<Dispatcher> handle_dispatcher_; + std::unique_ptr<Dispatcher> process_mitigations_win32k_dispatcher_; + std::unique_ptr<Dispatcher> signed_dispatcher_; + std::unique_ptr<Dispatcher> line_break_dispatcher_; + Dispatcher* ipc_targets_[kMaxIpcTag]; + + DISALLOW_COPY_AND_ASSIGN(TopLevelDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_TOP_LEVEL_DISPATCHER_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/unload_dll_test.cc b/security/sandbox/chromium/sandbox/win/src/unload_dll_test.cc new file mode 100644 index 0000000000..0acb178987 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/unload_dll_test.cc @@ -0,0 +1,100 @@ +// 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 "base/win/scoped_handle.h" +#include "build/build_config.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" + +namespace sandbox { + +// Loads and or unloads a DLL passed in the second parameter of argv. +// The first parameter of argv is 'L' = load, 'U' = unload or 'B' for both. +SBOX_TESTS_COMMAND int UseOneDLL(int argc, wchar_t** argv) { + if (argc != 2) + return SBOX_TEST_FAILED_TO_RUN_TEST; + int rv = SBOX_TEST_FAILED_TO_RUN_TEST; + + wchar_t option = (argv[0])[0]; + if ((option == L'L') || (option == L'B')) { + HMODULE module1 = ::LoadLibraryW(argv[1]); + rv = (!module1) ? SBOX_TEST_FAILED : SBOX_TEST_SUCCEEDED; + } + + if ((option == L'U') || (option == L'B')) { + HMODULE module2 = ::GetModuleHandleW(argv[1]); + rv = ::FreeLibrary(module2) ? SBOX_TEST_SUCCEEDED : SBOX_TEST_FAILED; + } + return rv; +} + +// Opens an event passed as the first parameter of argv. +SBOX_TESTS_COMMAND int SimpleOpenEvent(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + base::win::ScopedHandle event_open(::OpenEvent(SYNCHRONIZE, false, argv[0])); + return event_open.Get() ? SBOX_TEST_SUCCEEDED : SBOX_TEST_FAILED; +} + +// Fails on Windows ARM64: https://crbug.com/905526 +#if defined(ARCH_CPU_ARM64) +#define MAYBE_BaselineAvicapDll DISABLED_BaselineAvicapDll +#else +#define MAYBE_BaselineAvicapDll BaselineAvicapDll +#endif +TEST(UnloadDllTest, MAYBE_BaselineAvicapDll) { + TestRunner runner; + runner.SetTestState(BEFORE_REVERT); + runner.SetTimeout(2000); + // Add a sync rule, because that ensures that the interception agent has + // more than one item in its internal table. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_ANY, L"t0001")); + + // Note for the puzzled: avicap32.dll is a 64-bit dll in 64-bit versions of + // windows so this test and the others just work. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"UseOneDLL L avicap32.dll")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"UseOneDLL B avicap32.dll")); +} + +TEST(UnloadDllTest, UnloadAviCapDllNoPatching) { + TestRunner runner; + runner.SetTestState(BEFORE_REVERT); + runner.SetTimeout(2000); + sandbox::TargetPolicy* policy = runner.GetPolicy(); + policy->AddDllToUnload(L"avicap32.dll"); + EXPECT_EQ(SBOX_TEST_FAILED, runner.RunTest(L"UseOneDLL L avicap32.dll")); + EXPECT_EQ(SBOX_TEST_FAILED, runner.RunTest(L"UseOneDLL B avicap32.dll")); +} + +TEST(UnloadDllTest, UnloadAviCapDllWithPatching) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(BEFORE_REVERT); + sandbox::TargetPolicy* policy = runner.GetPolicy(); + policy->AddDllToUnload(L"avicap32.dll"); + + base::win::ScopedHandle handle1( + ::CreateEvent(nullptr, false, false, L"tst0001")); + + // Add a couple of rules that ensures that the interception agent add EAT + // patching on the client which makes sure that the unload dll record does + // not interact badly with them. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Microsoft")); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_ANY, L"tst0001")); + + EXPECT_EQ(SBOX_TEST_FAILED, runner.RunTest(L"UseOneDLL L avicap32.dll")); + + runner.SetTestState(AFTER_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"SimpleOpenEvent tst0001")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/win2k_threadpool.cc b/security/sandbox/chromium/sandbox/win/src/win2k_threadpool.cc new file mode 100644 index 0000000000..49cc68bb00 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/win2k_threadpool.cc @@ -0,0 +1,67 @@ +// 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 "sandbox/win/src/win2k_threadpool.h" + +#include <stddef.h> + +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +Win2kThreadPool::Win2kThreadPool() { + ::InitializeCriticalSection(&lock_); +} + +bool Win2kThreadPool::RegisterWait(const void* cookie, + HANDLE waitable_object, + CrossCallIPCCallback callback, + void* context) { + if (0 == cookie) { + return false; + } + HANDLE pool_object = nullptr; + // create a wait for a kernel object, with no timeout + if (!::RegisterWaitForSingleObject(&pool_object, waitable_object, callback, + context, INFINITE, WT_EXECUTEDEFAULT)) { + return false; + } + PoolObject pool_obj = {cookie, pool_object}; + AutoLock lock(&lock_); + pool_objects_.push_back(pool_obj); + return true; +} + +bool Win2kThreadPool::UnRegisterWaits(void* cookie) { + if (0 == cookie) { + return false; + } + AutoLock lock(&lock_); + bool success = true; + PoolObjects::iterator it = pool_objects_.begin(); + while (it != pool_objects_.end()) { + if (it->cookie == cookie) { + HANDLE wait = it->wait; + it = pool_objects_.erase(it); + success &= (::UnregisterWaitEx(wait, INVALID_HANDLE_VALUE) != 0); + } else { + ++it; + } + } + return success; +} + +size_t Win2kThreadPool::OutstandingWaits() { + AutoLock lock(&lock_); + return pool_objects_.size(); +} + +Win2kThreadPool::~Win2kThreadPool() { + // Here we used to unregister all the pool wait handles. Now, following the + // rest of the code we avoid lengthy or blocking calls given that the process + // is being torn down. + ::DeleteCriticalSection(&lock_); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/win2k_threadpool.h b/security/sandbox/chromium/sandbox/win/src/win2k_threadpool.h new file mode 100644 index 0000000000..c4d539dd7f --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/win2k_threadpool.h @@ -0,0 +1,61 @@ +// 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. + +#ifndef SANDBOX_SRC_WIN2K_THREADPOOL_H_ +#define SANDBOX_SRC_WIN2K_THREADPOOL_H_ + +#include <stddef.h> + +#include <algorithm> +#include <list> +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" + +namespace sandbox { + +// Win2kThreadPool a simple implementation of a thread provider as required +// for the sandbox IPC subsystem. See sandbox\crosscall_server.h for the details +// and requirements of this interface. +// +// Implementing the thread provider as a thread pool is desirable in the case +// of shared memory IPC because it can generate a large number of waitable +// events: as many as channels. A thread pool does not create a thread per +// event, instead maintains a few idle threads but can create more if the need +// arises. +// +// This implementation simply thunks to the nice thread pool API of win2k. +class Win2kThreadPool : public ThreadProvider { + public: + Win2kThreadPool(); + ~Win2kThreadPool() override; + + bool RegisterWait(const void* cookie, + HANDLE waitable_object, + CrossCallIPCCallback callback, + void* context) override; + + bool UnRegisterWaits(void* cookie) override; + + // Returns the total number of wait objects associated with + // the thread pool. + size_t OutstandingWaits(); + + private: + // record to keep track of a wait and its associated cookie. + struct PoolObject { + const void* cookie; + HANDLE wait; + }; + // The list of pool wait objects. + typedef std::list<PoolObject> PoolObjects; + PoolObjects pool_objects_; + // This lock protects the list of pool wait objects. + CRITICAL_SECTION lock_; + + DISALLOW_COPY_AND_ASSIGN(Win2kThreadPool); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_WIN2K_THREADPOOL_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/win_utils.cc b/security/sandbox/chromium/sandbox/win/src/win_utils.cc new file mode 100644 index 0000000000..8fb345a239 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/win_utils.cc @@ -0,0 +1,619 @@ +// Copyright (c) 2011 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/win/src/win_utils.h" + +#include <psapi.h> +#include <stddef.h> +#include <stdint.h> + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "base/numerics/safe_math.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "base/win/pe_image.h" +#include "sandbox/win/src/internal_types.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace { + +const size_t kDriveLetterLen = 3; + +constexpr wchar_t kNTDotPrefix[] = L"\\\\.\\"; +const size_t kNTDotPrefixLen = base::size(kNTDotPrefix) - 1; + +// Holds the information about a known registry key. +struct KnownReservedKey { + const wchar_t* name; + HKEY key; +}; + +// Contains all the known registry key by name and by handle. +const KnownReservedKey kKnownKey[] = { + {L"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT}, + {L"HKEY_CURRENT_USER", HKEY_CURRENT_USER}, + {L"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE}, + {L"HKEY_USERS", HKEY_USERS}, + {L"HKEY_PERFORMANCE_DATA", HKEY_PERFORMANCE_DATA}, + {L"HKEY_PERFORMANCE_TEXT", HKEY_PERFORMANCE_TEXT}, + {L"HKEY_PERFORMANCE_NLSTEXT", HKEY_PERFORMANCE_NLSTEXT}, + {L"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG}, + {L"HKEY_DYN_DATA", HKEY_DYN_DATA}}; + +// These functions perform case independent path comparisons. +bool EqualPath(const std::wstring& first, const std::wstring& second) { + return _wcsicmp(first.c_str(), second.c_str()) == 0; +} + +bool EqualPath(const std::wstring& first, + size_t first_offset, + const std::wstring& second, + size_t second_offset) { + return _wcsicmp(first.c_str() + first_offset, + second.c_str() + second_offset) == 0; +} + +bool EqualPath(const std::wstring& first, + const wchar_t* second, + size_t second_len) { + return _wcsnicmp(first.c_str(), second, second_len) == 0; +} + +bool EqualPath(const std::wstring& first, + size_t first_offset, + const wchar_t* second, + size_t second_len) { + return _wcsnicmp(first.c_str() + first_offset, second, second_len) == 0; +} + +// Returns true if |path| starts with "\??\" and returns a path without that +// component. +bool IsNTPath(const std::wstring& path, std::wstring* trimmed_path) { + if ((path.size() < sandbox::kNTPrefixLen) || + !EqualPath(path, sandbox::kNTPrefix, sandbox::kNTPrefixLen)) { + *trimmed_path = path; + return false; + } + + *trimmed_path = path.substr(sandbox::kNTPrefixLen); + return true; +} + +// Returns true if |path| starts with "\Device\" and returns a path without that +// component. +bool IsDevicePath(const std::wstring& path, std::wstring* trimmed_path) { + if ((path.size() < sandbox::kNTDevicePrefixLen) || + (!EqualPath(path, sandbox::kNTDevicePrefix, + sandbox::kNTDevicePrefixLen))) { + *trimmed_path = path; + return false; + } + + *trimmed_path = path.substr(sandbox::kNTDevicePrefixLen); + return true; +} + +// Returns the offset to the path seperator following +// "\Device\HarddiskVolumeX" in |path|. +size_t PassHarddiskVolume(const std::wstring& path) { + static constexpr wchar_t pattern[] = L"\\Device\\HarddiskVolume"; + const size_t patternLen = base::size(pattern) - 1; + + // First, check for |pattern|. + if ((path.size() < patternLen) || (!EqualPath(path, pattern, patternLen))) + return std::wstring::npos; + + // Find the next path separator, after the pattern match. + return path.find_first_of(L'\\', patternLen - 1); +} + +// Returns true if |path| starts with "\Device\HarddiskVolumeX\" and returns a +// path without that component. |removed| will hold the prefix removed. +bool IsDeviceHarddiskPath(const std::wstring& path, + std::wstring* trimmed_path, + std::wstring* removed) { + size_t offset = PassHarddiskVolume(path); + if (offset == std::wstring::npos) + return false; + + // Remove up to and including the path separator. + *removed = path.substr(0, offset + 1); + // Remaining path starts after the path separator. + *trimmed_path = path.substr(offset + 1); + return true; +} + +bool StartsWithDriveLetter(const std::wstring& path) { + if (path.size() < kDriveLetterLen) + return false; + + if (path[1] != L':' || path[2] != L'\\') + return false; + + return base::IsAsciiAlpha(path[0]); +} + +// Removes "\\\\.\\" from the path. +void RemoveImpliedDevice(std::wstring* path) { + if (EqualPath(*path, kNTDotPrefix, kNTDotPrefixLen)) + *path = path->substr(kNTDotPrefixLen); +} + +} // namespace + +namespace sandbox { + +// Returns true if the provided path points to a pipe. +bool IsPipe(const std::wstring& path) { + size_t start = 0; + if (EqualPath(path, sandbox::kNTPrefix, sandbox::kNTPrefixLen)) + start = sandbox::kNTPrefixLen; + + const wchar_t kPipe[] = L"pipe\\"; + if (path.size() < start + base::size(kPipe) - 1) + return false; + + return EqualPath(path, start, kPipe, base::size(kPipe) - 1); +} + +HKEY GetReservedKeyFromName(const std::wstring& name) { + for (size_t i = 0; i < base::size(kKnownKey); ++i) { + if (name == kKnownKey[i].name) + return kKnownKey[i].key; + } + + return nullptr; +} + +bool ResolveRegistryName(std::wstring name, std::wstring* resolved_name) { + for (size_t i = 0; i < base::size(kKnownKey); ++i) { + if (name.find(kKnownKey[i].name) == 0) { + HKEY key; + DWORD disposition; + if (ERROR_SUCCESS != ::RegCreateKeyEx(kKnownKey[i].key, L"", 0, nullptr, + 0, MAXIMUM_ALLOWED, nullptr, &key, + &disposition)) + return false; + + bool result = GetPathFromHandle(key, resolved_name); + ::RegCloseKey(key); + + if (!result) + return false; + + *resolved_name += name.substr(wcslen(kKnownKey[i].name)); + return true; + } + } + + return false; +} + +// |full_path| can have any of the following forms: +// \??\c:\some\foo\bar +// \Device\HarddiskVolume0\some\foo\bar +// \??\HarddiskVolume0\some\foo\bar +// \??\UNC\SERVER\Share\some\foo\bar +DWORD IsReparsePoint(const std::wstring& full_path) { + // Check if it's a pipe. We can't query the attributes of a pipe. + if (IsPipe(full_path)) + return ERROR_NOT_A_REPARSE_POINT; + + std::wstring path; + bool nt_path = IsNTPath(full_path, &path); + bool has_drive = StartsWithDriveLetter(path); + bool is_device_path = IsDevicePath(path, &path); + + if (!has_drive && !is_device_path && !nt_path) + return ERROR_INVALID_NAME; + + if (!has_drive) { + // Add Win32 device namespace prefix, required for some Windows APIs. + path.insert(0, kNTDotPrefix); + } + + // Ensure that volume path matches start of path. + wchar_t vol_path[MAX_PATH]; + if (!::GetVolumePathNameW(path.c_str(), vol_path, MAX_PATH)) { + // This will fail if this is a device that isn't volume related, which can't + // then be a reparse point. + return is_device_path ? ERROR_NOT_A_REPARSE_POINT : ERROR_INVALID_NAME; + } + + // vol_path includes a trailing slash, so reduce size for path and loop check. + size_t vol_path_len = wcslen(vol_path) - 1; + if (!EqualPath(path, vol_path, vol_path_len)) { + return ERROR_INVALID_NAME; + } + + do { + DWORD attributes = ::GetFileAttributes(path.c_str()); + if (INVALID_FILE_ATTRIBUTES == attributes) { + DWORD error = ::GetLastError(); + if (error != ERROR_FILE_NOT_FOUND && error != ERROR_PATH_NOT_FOUND && + error != ERROR_INVALID_FUNCTION && + error != ERROR_INVALID_NAME) { + // Unexpected error. + return error; + } + } else if (FILE_ATTRIBUTE_REPARSE_POINT & attributes) { + // This is a reparse point. + return ERROR_SUCCESS; + } + + path.resize(path.rfind(L'\\')); + } while (path.size() > vol_path_len); // Skip root dir. + + return ERROR_NOT_A_REPARSE_POINT; +} + +// We get a |full_path| of the forms accepted by IsReparsePoint(), and the name +// we'll get from |handle| will be \device\harddiskvolume1\some\foo\bar. +bool SameObject(HANDLE handle, const wchar_t* full_path) { + // Check if it's a pipe. + if (IsPipe(full_path)) + return true; + + std::wstring actual_path; + if (!GetPathFromHandle(handle, &actual_path)) + return false; + + std::wstring path(full_path); + DCHECK_NT(!path.empty()); + + // This may end with a backslash. + if (path.back() == L'\\') { + path.pop_back(); + } + + // Perfect match (case-insensitive check). + if (EqualPath(actual_path, path)) + return true; + + bool nt_path = IsNTPath(path, &path); + bool has_drive = StartsWithDriveLetter(path); + + if (!has_drive && nt_path) { + std::wstring simple_actual_path; + if (IsDevicePath(path, &path)) { + if (IsDevicePath(actual_path, &simple_actual_path)) { + // Perfect match (case-insensitive check). + return (EqualPath(simple_actual_path, path)); + } else { + return false; + } + } else { + // Add Win32 device namespace for GetVolumePathName. + path.insert(0, kNTDotPrefix); + } + } + + // Get the volume path in the same format as actual_path. + wchar_t vol_path[MAX_PATH]; + if (!::GetVolumePathName(path.c_str(), vol_path, MAX_PATH)) { + return false; + } + size_t vol_path_len = wcslen(vol_path); + base::string16 nt_vol; + if (!GetNtPathFromWin32Path(vol_path, &nt_vol)) { + return false; + } + + // The two paths should be the same length. + if (nt_vol.size() + path.size() - vol_path_len != actual_path.size()) { + return false; + } + + // Check the volume matches. + if (!EqualPath(actual_path, nt_vol.c_str(), nt_vol.size())) { + return false; + } + + // Check the path after the volume matches. + if (!EqualPath(actual_path, nt_vol.size(), path, vol_path_len)) { + return false; + } + + return true; +} + +// Just make a best effort here. There are lots of corner cases that we're +// not expecting - and will fail to make long. +bool ConvertToLongPath(std::wstring* native_path, + const std::wstring* drive_letter) { + if (IsPipe(*native_path)) + return true; + + bool is_device_harddisk_path = false; + bool is_nt_path = false; + bool added_implied_device = false; + std::wstring temp_path; + std::wstring to_restore; + + // Process a few prefix types. + if (IsNTPath(*native_path, &temp_path)) { + // "\??\" + if (!StartsWithDriveLetter(temp_path)) { + // Prepend with "\\.\". + temp_path = std::wstring(kNTDotPrefix) + temp_path; + added_implied_device = true; + } + is_nt_path = true; + } else if (IsDeviceHarddiskPath(*native_path, &temp_path, &to_restore)) { + // "\Device\HarddiskVolumeX\" - hacky attempt making ::GetLongPathName + // work for native device paths. Remove "\Device\HarddiskVolumeX\" and + // replace with drive letter. + + // Nothing we can do if we don't have a drive letter. Leave |native_path| + // as is. + if (!drive_letter || drive_letter->empty()) + return false; + temp_path = *drive_letter + temp_path; + is_device_harddisk_path = true; + } else if (IsDevicePath(*native_path, &temp_path)) { + // "\Device\" - there's nothing we can do to convert to long here. + return false; + } + + DWORD size = MAX_PATH; + std::unique_ptr<wchar_t[]> long_path_buf(new wchar_t[size]); + + DWORD return_value = + ::GetLongPathName(temp_path.c_str(), long_path_buf.get(), size); + while (return_value >= size) { + size *= 2; + long_path_buf.reset(new wchar_t[size]); + return_value = + ::GetLongPathName(temp_path.c_str(), long_path_buf.get(), size); + } + + DWORD last_error = ::GetLastError(); + if (0 == return_value && (ERROR_FILE_NOT_FOUND == last_error || + ERROR_PATH_NOT_FOUND == last_error || + ERROR_INVALID_NAME == last_error)) { + // The file does not exist, but maybe a sub path needs to be expanded. + std::wstring::size_type last_slash = temp_path.rfind(L'\\'); + if (std::wstring::npos == last_slash) + return false; + + std::wstring begin = temp_path.substr(0, last_slash); + std::wstring end = temp_path.substr(last_slash); + if (!ConvertToLongPath(&begin)) + return false; + + // Ok, it worked. Let's reset the return value. + temp_path = begin + end; + return_value = 1; + } else if (0 != return_value) { + temp_path = long_path_buf.get(); + } + + // If successful, re-apply original namespace prefix before returning. + if (return_value != 0) { + if (added_implied_device) + RemoveImpliedDevice(&temp_path); + + if (is_nt_path) { + *native_path = kNTPrefix; + *native_path += temp_path; + } else if (is_device_harddisk_path) { + // Remove the added drive letter. + temp_path = temp_path.substr(kDriveLetterLen); + *native_path = to_restore; + *native_path += temp_path; + } else { + *native_path = temp_path; + } + + return true; + } + + return false; +} + +bool GetPathFromHandle(HANDLE handle, std::wstring* path) { + NtQueryObjectFunction NtQueryObject = nullptr; + ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject); + + OBJECT_NAME_INFORMATION initial_buffer; + OBJECT_NAME_INFORMATION* name = &initial_buffer; + ULONG size = sizeof(initial_buffer); + // Query the name information a first time to get the size of the name. + // Windows XP requires that the size of the buffer passed in here be != 0. + NTSTATUS status = + NtQueryObject(handle, ObjectNameInformation, name, size, &size); + + std::unique_ptr<BYTE[]> name_ptr; + if (size) { + name_ptr.reset(new BYTE[size]); + name = reinterpret_cast<OBJECT_NAME_INFORMATION*>(name_ptr.get()); + + // Query the name information a second time to get the name of the + // object referenced by the handle. + status = NtQueryObject(handle, ObjectNameInformation, name, size, &size); + } + + if (STATUS_SUCCESS != status) + return false; + + path->assign(name->ObjectName.Buffer, + name->ObjectName.Length / sizeof(name->ObjectName.Buffer[0])); + return true; +} + +bool GetNtPathFromWin32Path(const std::wstring& path, std::wstring* nt_path) { + HANDLE file = ::CreateFileW( + path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (file == INVALID_HANDLE_VALUE) + return false; + bool rv = GetPathFromHandle(file, nt_path); + ::CloseHandle(file); + return rv; +} + +bool WriteProtectedChildMemory(HANDLE child_process, + void* address, + const void* buffer, + size_t length, + DWORD writeProtection) { + // First, remove the protections. + DWORD old_protection; + if (!::VirtualProtectEx(child_process, address, length, writeProtection, + &old_protection)) + return false; + + SIZE_T written; + bool ok = + ::WriteProcessMemory(child_process, address, buffer, length, &written) && + (length == written); + + // Always attempt to restore the original protection. + if (!::VirtualProtectEx(child_process, address, length, old_protection, + &old_protection)) + return false; + + return ok; +} + +bool CopyToChildMemory(HANDLE child, + const void* local_buffer, + size_t buffer_bytes, + void** remote_buffer) { + DCHECK(remote_buffer); + if (0 == buffer_bytes) { + *remote_buffer = nullptr; + return true; + } + + // Allocate memory in the target process without specifying the address + void* remote_data = ::VirtualAllocEx(child, nullptr, buffer_bytes, MEM_COMMIT, + PAGE_READWRITE); + if (!remote_data) + return false; + + SIZE_T bytes_written; + bool success = ::WriteProcessMemory(child, remote_data, local_buffer, + buffer_bytes, &bytes_written); + if (!success || bytes_written != buffer_bytes) { + ::VirtualFreeEx(child, remote_data, 0, MEM_RELEASE); + return false; + } + + *remote_buffer = remote_data; + + return true; +} + +DWORD GetLastErrorFromNtStatus(NTSTATUS status) { + RtlNtStatusToDosErrorFunction NtStatusToDosError = nullptr; + ResolveNTFunctionPtr("RtlNtStatusToDosError", &NtStatusToDosError); + return NtStatusToDosError(status); +} + +// This function uses the undocumented PEB ImageBaseAddress field to extract +// the base address of the new process. +void* GetProcessBaseAddress(HANDLE process) { + NtQueryInformationProcessFunction query_information_process = nullptr; + ResolveNTFunctionPtr("NtQueryInformationProcess", &query_information_process); + if (!query_information_process) + return nullptr; + PROCESS_BASIC_INFORMATION process_basic_info = {}; + NTSTATUS status = query_information_process( + process, ProcessBasicInformation, &process_basic_info, + sizeof(process_basic_info), nullptr); + if (STATUS_SUCCESS != status) + return nullptr; + + PEB peb = {}; + SIZE_T bytes_read = 0; + if (!::ReadProcessMemory(process, process_basic_info.PebBaseAddress, &peb, + sizeof(peb), &bytes_read) || + (sizeof(peb) != bytes_read)) { + return nullptr; + } + + void* base_address = peb.ImageBaseAddress; + char magic[2] = {}; + if (!::ReadProcessMemory(process, base_address, magic, sizeof(magic), + &bytes_read) || + (sizeof(magic) != bytes_read)) { + return nullptr; + } + + if (magic[0] != 'M' || magic[1] != 'Z') + return nullptr; + +#if defined(_M_ARM64) + // Windows 10 on ARM64 has multi-threaded DLL loading that does not work with + // the sandbox. (On x86 this gets disabled by hook detection code that was not + // ported to ARM64). This overwrites the LoaderThreads value in the process + // parameters part of the PEB, if it is set to the default of 0 (which + // actually means it defaults to 4 loading threads). This is an undocumented + // field so there is a, probably small, risk that it might change or move in + // the future. In order to slightly guard against that we only update if the + // value is currently 0. + auto processParameters = reinterpret_cast<uint8_t*>(peb.ProcessParameters); + const uint32_t loaderThreadsOffset = 0x40c; + uint32_t maxLoaderThreads = 0; + BOOL memoryRead = ::ReadProcessMemory( + process, processParameters + loaderThreadsOffset, &maxLoaderThreads, + sizeof(maxLoaderThreads), &bytes_read); + if (memoryRead && (sizeof(maxLoaderThreads) == bytes_read) && + (maxLoaderThreads == 0)) { + maxLoaderThreads = 1; + WriteProtectedChildMemory(process, processParameters + loaderThreadsOffset, + &maxLoaderThreads, sizeof(maxLoaderThreads), + PAGE_READWRITE); + } +#endif + + return base_address; +} + +DWORD GetTokenInformation(HANDLE token, + TOKEN_INFORMATION_CLASS info_class, + std::unique_ptr<BYTE[]>* buffer) { + // Get the required buffer size. + DWORD size = 0; + ::GetTokenInformation(token, info_class, nullptr, 0, &size); + if (!size) { + return ::GetLastError(); + } + + auto temp_buffer = std::make_unique<BYTE[]>(size); + if (!::GetTokenInformation(token, info_class, temp_buffer.get(), size, + &size)) { + return ::GetLastError(); + } + + *buffer = std::move(temp_buffer); + return ERROR_SUCCESS; +} + +} // namespace sandbox + +void ResolveNTFunctionPtr(const char* name, void* ptr) { + static volatile HMODULE ntdll = nullptr; + + if (!ntdll) { + HMODULE ntdll_local = ::GetModuleHandle(sandbox::kNtdllName); + // Use PEImage to sanity-check that we have a valid ntdll handle. + base::win::PEImage ntdll_peimage(ntdll_local); + CHECK_NT(ntdll_peimage.VerifyMagic()); + // Race-safe way to set static ntdll. + ::InterlockedCompareExchangePointer( + reinterpret_cast<PVOID volatile*>(&ntdll), ntdll_local, nullptr); + } + + CHECK_NT(ntdll); + FARPROC* function_ptr = reinterpret_cast<FARPROC*>(ptr); + *function_ptr = ::GetProcAddress(ntdll, name); + CHECK_NT(*function_ptr); +} diff --git a/security/sandbox/chromium/sandbox/win/src/win_utils.h b/security/sandbox/chromium/sandbox/win/src/win_utils.h new file mode 100644 index 0000000000..7c848f2f08 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/win_utils.h @@ -0,0 +1,156 @@ +// Copyright (c) 2006-2010 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. + +#ifndef SANDBOX_SRC_WIN_UTILS_H_ +#define SANDBOX_SRC_WIN_UTILS_H_ + +#include <stddef.h> +#include <windows.h> +#include <memory> +#include <string> + +#include "base/macros.h" +#include "base/stl_util.h" +#include "sandbox/win/src/nt_internals.h" + +namespace sandbox { + +// Prefix for path used by NT calls. +const wchar_t kNTPrefix[] = L"\\??\\"; +const size_t kNTPrefixLen = base::size(kNTPrefix) - 1; + +const wchar_t kNTDevicePrefix[] = L"\\Device\\"; +const size_t kNTDevicePrefixLen = base::size(kNTDevicePrefix) - 1; + +// Automatically acquires and releases a lock when the object is +// is destroyed. +class AutoLock { + public: + // Acquires the lock. + explicit AutoLock(CRITICAL_SECTION* lock) : lock_(lock) { + ::EnterCriticalSection(lock); + } + + // Releases the lock; + ~AutoLock() { ::LeaveCriticalSection(lock_); } + + private: + CRITICAL_SECTION* lock_; + DISALLOW_IMPLICIT_CONSTRUCTORS(AutoLock); +}; + +// Basic implementation of a singleton which calls the destructor +// when the exe is shutting down or the DLL is being unloaded. +template <typename Derived> +class SingletonBase { + public: + static Derived* GetInstance() { + static Derived* instance = nullptr; + if (!instance) { + instance = new Derived(); + // Microsoft CRT extension. In an exe this this called after + // winmain returns, in a dll is called in DLL_PROCESS_DETACH + _onexit(OnExit); + } + return instance; + } + + private: + // this is the function that gets called by the CRT when the + // process is shutting down. + static int __cdecl OnExit() { + delete GetInstance(); + return 0; + } +}; + +// Function object which invokes LocalFree on its parameter, which must be +// a pointer. Can be used to store LocalAlloc pointers in std::unique_ptr: +// +// std::unique_ptr<int, sandbox::LocalFreeDeleter> foo_ptr( +// static_cast<int*>(LocalAlloc(LMEM_FIXED, sizeof(int)))); +struct LocalFreeDeleter { + inline void operator()(void* ptr) const { ::LocalFree(ptr); } +}; + +// Convert a short path (C:\path~1 or \\??\\c:\path~1) to the long version of +// the path. If the path is not a valid filesystem path, the function returns +// false and argument is not modified. +// - If passing in a short native device path (\Device\HarddiskVolumeX\path~1), +// a drive letter string (c:\) must also be provided. +bool ConvertToLongPath(std::wstring* path, + const std::wstring* drive_letter = nullptr); + +// Returns ERROR_SUCCESS if the path contains a reparse point, +// ERROR_NOT_A_REPARSE_POINT if there's no reparse point in this path, or an +// error code when the function fails. +// This function is not smart. It looks for each element in the path and +// returns true if any of them is a reparse point. +DWORD IsReparsePoint(const std::wstring& full_path); + +// Returns true if the handle corresponds to the object pointed by this path. +bool SameObject(HANDLE handle, const wchar_t* full_path); + +// Resolves a handle to an nt path. Returns true if the handle can be resolved. +bool GetPathFromHandle(HANDLE handle, std::wstring* path); + +// Resolves a win32 path to an nt path using GetPathFromHandle. The path must +// exist. Returs true if the translation was succesful. +bool GetNtPathFromWin32Path(const std::wstring& path, std::wstring* nt_path); + +// Translates a reserved key name to its handle. +// For example "HKEY_LOCAL_MACHINE" returns HKEY_LOCAL_MACHINE. +// Returns nullptr if the name does not represent any reserved key name. +HKEY GetReservedKeyFromName(const std::wstring& name); + +// Resolves a user-readable registry path to a system-readable registry path. +// For example, HKEY_LOCAL_MACHINE\\Software\\microsoft is translated to +// \\registry\\machine\\software\\microsoft. Returns false if the path +// cannot be resolved. +bool ResolveRegistryName(std::wstring name, std::wstring* resolved_name); + +// Writes |length| bytes from the provided |buffer| into the address space of +// |child_process|, at the specified |address|, preserving the original write +// protection attributes. Returns true on success. +bool WriteProtectedChildMemory(HANDLE child_process, + void* address, + const void* buffer, + size_t length, + DWORD writeProtection = PAGE_WRITECOPY); + +// Allocates |buffer_bytes| in child (PAGE_READWRITE) and copies data +// from |local_buffer| in this process into |child|. |remote_buffer| +// contains the address in the chile. If a zero byte copy is +// requested |true| is returned and no allocation or copying is +// attempted. Returns false if allocation or copying fails. If +// copying fails, the allocation will be reversed. +bool CopyToChildMemory(HANDLE child, + const void* local_buffer, + size_t buffer_bytes, + void** remote_buffer); + +// Returns true if the provided path points to a pipe. +bool IsPipe(const std::wstring& path); + +// Converts a NTSTATUS code to a Win32 error code. +DWORD GetLastErrorFromNtStatus(NTSTATUS status); + +// Returns the address of the main exe module in memory taking in account +// address space layout randomization. This uses the process' PEB to extract +// the base address. This should only be called on new, suspended processes. +void* GetProcessBaseAddress(HANDLE process); + +// Calls GetTokenInformation with the desired |info_class| and returns a +// |buffer| and the Win32 error code. +DWORD GetTokenInformation(HANDLE token, + TOKEN_INFORMATION_CLASS info_class, + std::unique_ptr<BYTE[]>* buffer); + +} // namespace sandbox + +// Resolves a function name in NTDLL to a function pointer. The second parameter +// is a pointer to the function pointer. +void ResolveNTFunctionPtr(const char* name, void* ptr); + +#endif // SANDBOX_SRC_WIN_UTILS_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/win_utils_unittest.cc b/security/sandbox/chromium/sandbox/win/src/win_utils_unittest.cc new file mode 100644 index 0000000000..36e7d4dd1e --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/win_utils_unittest.cc @@ -0,0 +1,258 @@ +// Copyright (c) 2011 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/win/src/win_utils.h" + +#include <windows.h> + +#include <psapi.h> + +#include <vector> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/numerics/safe_conversions.h" +#include "base/path_service.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/tests/common/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +class ScopedTerminateProcess { + public: + ScopedTerminateProcess(HANDLE process) : process_(process) {} + + ~ScopedTerminateProcess() { ::TerminateProcess(process_, 0); } + + private: + HANDLE process_; +}; + +bool GetModuleList(HANDLE process, std::vector<HMODULE>* result) { + std::vector<HMODULE> modules(256); + DWORD size_needed = 0; + if (EnumProcessModules( + process, &modules[0], + base::checked_cast<DWORD>(modules.size() * sizeof(HMODULE)), + &size_needed)) { + result->assign(modules.begin(), + modules.begin() + (size_needed / sizeof(HMODULE))); + return true; + } + modules.resize(size_needed / sizeof(HMODULE)); + if (EnumProcessModules( + process, &modules[0], + base::checked_cast<DWORD>(modules.size() * sizeof(HMODULE)), + &size_needed)) { + result->assign(modules.begin(), + modules.begin() + (size_needed / sizeof(HMODULE))); + return true; + } + return false; +} + +} // namespace + +TEST(WinUtils, IsReparsePoint) { + using sandbox::IsReparsePoint; + + // Create a temp file because we need write access to it. + wchar_t temp_directory[MAX_PATH]; + wchar_t my_folder[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, my_folder), 0u); + + // Delete the file and create a directory instead. + ASSERT_TRUE(::DeleteFile(my_folder)); + ASSERT_TRUE(::CreateDirectory(my_folder, nullptr)); + + EXPECT_EQ(static_cast<DWORD>(ERROR_NOT_A_REPARSE_POINT), + IsReparsePoint(my_folder)); + + std::wstring not_found = std::wstring(my_folder) + L"\\foo\\bar"; + EXPECT_EQ(static_cast<DWORD>(ERROR_NOT_A_REPARSE_POINT), + IsReparsePoint(not_found)); + + std::wstring new_file = std::wstring(my_folder) + L"\\foo"; + EXPECT_EQ(static_cast<DWORD>(ERROR_NOT_A_REPARSE_POINT), + IsReparsePoint(new_file)); + + // Replace the directory with a reparse point to %temp%. + HANDLE dir = ::CreateFile(my_folder, FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + EXPECT_NE(INVALID_HANDLE_VALUE, dir); + + std::wstring temp_dir_nt = std::wstring(L"\\??\\") + temp_directory; + EXPECT_TRUE(SetReparsePoint(dir, temp_dir_nt.c_str())); + + EXPECT_EQ(static_cast<DWORD>(ERROR_SUCCESS), IsReparsePoint(new_file)); + + EXPECT_TRUE(DeleteReparsePoint(dir)); + EXPECT_TRUE(::CloseHandle(dir)); + EXPECT_TRUE(::RemoveDirectory(my_folder)); +} + +TEST(WinUtils, SameObject) { + using sandbox::SameObject; + + // Create a temp file because we need write access to it. + wchar_t temp_directory[MAX_PATH]; + wchar_t my_folder[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, my_folder), 0u); + + // Delete the file and create a directory instead. + ASSERT_TRUE(::DeleteFile(my_folder)); + ASSERT_TRUE(::CreateDirectory(my_folder, nullptr)); + + std::wstring folder(my_folder); + std::wstring file_name = folder + L"\\foo.txt"; + const ULONG kSharing = FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE; + base::win::ScopedHandle file(CreateFile(file_name.c_str(), GENERIC_WRITE, + kSharing, nullptr, CREATE_ALWAYS, + FILE_FLAG_DELETE_ON_CLOSE, nullptr)); + + EXPECT_TRUE(file.IsValid()); + std::wstring file_name_nt1 = std::wstring(L"\\??\\") + file_name; + std::wstring file_name_nt2 = std::wstring(L"\\??\\") + folder + L"\\FOO.txT"; + EXPECT_TRUE(SameObject(file.Get(), file_name_nt1.c_str())); + EXPECT_TRUE(SameObject(file.Get(), file_name_nt2.c_str())); + + file.Close(); + EXPECT_TRUE(::RemoveDirectory(my_folder)); +} + +TEST(WinUtils, IsPipe) { + using sandbox::IsPipe; + + std::wstring pipe_name = L"\\??\\pipe\\mypipe"; + EXPECT_TRUE(IsPipe(pipe_name)); + + pipe_name = L"\\??\\PiPe\\mypipe"; + EXPECT_TRUE(IsPipe(pipe_name)); + + pipe_name = L"\\??\\pipe"; + EXPECT_FALSE(IsPipe(pipe_name)); + + pipe_name = L"\\??\\_pipe_\\mypipe"; + EXPECT_FALSE(IsPipe(pipe_name)); + + pipe_name = L"\\??\\ABCD\\mypipe"; + EXPECT_FALSE(IsPipe(pipe_name)); + + // Written as two strings to prevent trigraph '?' '?' '/'. + pipe_name = + L"/?" + L"?/pipe/mypipe"; + EXPECT_FALSE(IsPipe(pipe_name)); + + pipe_name = L"\\XX\\pipe\\mypipe"; + EXPECT_FALSE(IsPipe(pipe_name)); + + pipe_name = L"\\Device\\NamedPipe\\mypipe"; + EXPECT_FALSE(IsPipe(pipe_name)); +} + +TEST(WinUtils, NtStatusToWin32Error) { + using sandbox::GetLastErrorFromNtStatus; + EXPECT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + GetLastErrorFromNtStatus(STATUS_SUCCESS)); + EXPECT_EQ(static_cast<DWORD>(ERROR_NOT_SUPPORTED), + GetLastErrorFromNtStatus(STATUS_NOT_SUPPORTED)); + EXPECT_EQ(static_cast<DWORD>(ERROR_ALREADY_EXISTS), + GetLastErrorFromNtStatus(STATUS_OBJECT_NAME_COLLISION)); + EXPECT_EQ(static_cast<DWORD>(ERROR_ACCESS_DENIED), + GetLastErrorFromNtStatus(STATUS_ACCESS_DENIED)); +} + +TEST(WinUtils, GetProcessBaseAddress) { + using sandbox::GetProcessBaseAddress; + STARTUPINFO start_info = {}; + PROCESS_INFORMATION proc_info = {}; + WCHAR command_line[] = L"notepad"; + start_info.cb = sizeof(start_info); + start_info.dwFlags = STARTF_USESHOWWINDOW; + start_info.wShowWindow = SW_HIDE; + ASSERT_TRUE(::CreateProcessW(nullptr, command_line, nullptr, nullptr, false, + CREATE_SUSPENDED, nullptr, nullptr, &start_info, + &proc_info)); + base::win::ScopedProcessInformation scoped_proc_info(proc_info); + ScopedTerminateProcess process_terminate(scoped_proc_info.process_handle()); + void* base_address = GetProcessBaseAddress(scoped_proc_info.process_handle()); + ASSERT_NE(nullptr, base_address); + ASSERT_NE(static_cast<DWORD>(-1), + ::ResumeThread(scoped_proc_info.thread_handle())); + ::WaitForInputIdle(scoped_proc_info.process_handle(), 1000); + ASSERT_NE(static_cast<DWORD>(-1), + ::SuspendThread(scoped_proc_info.thread_handle())); + + std::vector<HMODULE> modules; + // Compare against the loader's module list (which should now be initialized). + ASSERT_TRUE(GetModuleList(scoped_proc_info.process_handle(), &modules)); + ASSERT_GT(modules.size(), 0U); + EXPECT_EQ(base_address, modules[0]); +} + +// This test requires an elevated prompt to setup. +TEST(WinUtils, ConvertToLongPath) { + // Test setup. + base::FilePath orig_path; + ASSERT_TRUE(base::PathService::Get(base::DIR_SYSTEM, &orig_path)); + orig_path = orig_path.Append(L"calc.exe"); + + base::FilePath temp_path; + ASSERT_TRUE(base::PathService::Get(base::DIR_PROGRAM_FILES, &temp_path)); + temp_path = temp_path.Append(L"test_calc.exe"); + + ASSERT_TRUE(base::CopyFile(orig_path, temp_path)); + // No more asserts until cleanup. + + // WIN32 long path: "c:\Program Files\test_calc.exe" + wchar_t short_path[MAX_PATH] = {}; + DWORD size = + ::GetShortPathNameW(temp_path.value().c_str(), short_path, MAX_PATH); + EXPECT_TRUE(size > 0 && size < MAX_PATH); + // WIN32 short path: "C:\PROGRA~1\TEST_C~1.exe" + + // Sanity check that we actually got a short path above! Small chance + // it was disabled in the filesystem setup. + EXPECT_NE(temp_path.value().length(), ::wcslen(short_path)); + + std::wstring short_form_native_path; + EXPECT_TRUE(sandbox::GetNtPathFromWin32Path(std::wstring(short_path), + &short_form_native_path)); + // NT short path: "\Device\HarddiskVolume4\PROGRA~1\TEST_C~1.EXE" + + // Test 1: convert win32 short path to long: + std::wstring test1(short_path); + EXPECT_TRUE(sandbox::ConvertToLongPath(&test1)); + EXPECT_TRUE(::wcsicmp(temp_path.value().c_str(), test1.c_str()) == 0); + // Expected result: "c:\Program Files\test_calc.exe" + + // Test 2: convert native short path to long: + std::wstring drive_letter = temp_path.value().substr(0, 3); + std::wstring test2(short_form_native_path); + EXPECT_TRUE(sandbox::ConvertToLongPath(&test2, &drive_letter)); + + size_t index = short_form_native_path.find_first_of( + L'\\', ::wcslen(L"\\Device\\HarddiskVolume")); + EXPECT_TRUE(index != std::wstring::npos); + std::wstring expected_result = short_form_native_path.substr(0, index + 1); + expected_result.append(temp_path.value().substr(3)); + EXPECT_TRUE(::wcsicmp(expected_result.c_str(), test2.c_str()) == 0); + // Expected result: "\Device\HarddiskVolumeX\Program Files\test_calc.exe" + + // clean up + EXPECT_TRUE(base::DeleteFileW(temp_path, false)); + + return; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/window.cc b/security/sandbox/chromium/sandbox/win/src/window.cc new file mode 100644 index 0000000000..27645a9006 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/window.cc @@ -0,0 +1,147 @@ +// Copyright (c) 2011 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/win/src/window.h" + +#include <aclapi.h> + +#include <memory> + +#include "base/logging.h" +#include "base/win/win_util.h" +#include "sandbox/win/src/acl.h" +#include "sandbox/win/src/sid.h" + +namespace { + +// Gets the security attributes of a window object referenced by |handle|. The +// lpSecurityDescriptor member of the SECURITY_ATTRIBUTES parameter returned +// must be freed using LocalFree by the caller. +bool GetSecurityAttributes(HANDLE handle, SECURITY_ATTRIBUTES* attributes) { + attributes->bInheritHandle = false; + attributes->nLength = sizeof(SECURITY_ATTRIBUTES); + + PACL dacl = nullptr; + DWORD result = ::GetSecurityInfo( + handle, SE_WINDOW_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, + &dacl, nullptr, &attributes->lpSecurityDescriptor); + if (ERROR_SUCCESS == result) + return true; + + return false; +} + +} // namespace + +namespace sandbox { + +ResultCode CreateAltWindowStation(HWINSTA* winsta) { + // Get the security attributes from the current window station; we will + // use this as the base security attributes for the new window station. + HWINSTA current_winsta = ::GetProcessWindowStation(); + if (!current_winsta) + return SBOX_ERROR_CANNOT_GET_WINSTATION; + + SECURITY_ATTRIBUTES attributes = {0}; + if (!GetSecurityAttributes(current_winsta, &attributes)) + return SBOX_ERROR_CANNOT_QUERY_WINSTATION_SECURITY; + + // Create the window station using nullptr for the name to ask the os to + // generate it. + *winsta = ::CreateWindowStationW( + nullptr, 0, GENERIC_READ | WINSTA_CREATEDESKTOP, &attributes); + if (!*winsta && ::GetLastError() == ERROR_ACCESS_DENIED) { + *winsta = ::CreateWindowStationW( + nullptr, 0, WINSTA_READATTRIBUTES | WINSTA_CREATEDESKTOP, &attributes); + } + LocalFree(attributes.lpSecurityDescriptor); + + if (*winsta) + return SBOX_ALL_OK; + + return SBOX_ERROR_CANNOT_CREATE_WINSTATION; +} + +ResultCode CreateAltDesktop(HWINSTA winsta, HDESK* desktop) { + std::wstring desktop_name = L"sbox_alternate_desktop_"; + + if (!winsta) { + desktop_name += L"local_winstation_"; + } + + // Append the current PID to the desktop name. + wchar_t buffer[16]; + _snwprintf_s(buffer, sizeof(buffer) / sizeof(wchar_t), L"0x%X", + ::GetCurrentProcessId()); + desktop_name += buffer; + + HDESK current_desktop = GetThreadDesktop(GetCurrentThreadId()); + + if (!current_desktop) + return SBOX_ERROR_CANNOT_GET_DESKTOP; + + // Get the security attributes from the current desktop, we will use this as + // the base security attributes for the new desktop. + SECURITY_ATTRIBUTES attributes = {0}; + if (!GetSecurityAttributes(current_desktop, &attributes)) + return SBOX_ERROR_CANNOT_QUERY_DESKTOP_SECURITY; + + // Back up the current window station, in case we need to switch it. + HWINSTA current_winsta = ::GetProcessWindowStation(); + + if (winsta) { + // We need to switch to the alternate window station before creating the + // desktop. + if (!::SetProcessWindowStation(winsta)) { + ::LocalFree(attributes.lpSecurityDescriptor); + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; + } + } + + // Create the destkop. + *desktop = ::CreateDesktop(desktop_name.c_str(), nullptr, nullptr, 0, + DESKTOP_CREATEWINDOW | DESKTOP_READOBJECTS | + READ_CONTROL | WRITE_DAC | WRITE_OWNER, + &attributes); + ::LocalFree(attributes.lpSecurityDescriptor); + + if (winsta) { + // Revert to the right window station. + if (!::SetProcessWindowStation(current_winsta)) { + return SBOX_ERROR_FAILED_TO_SWITCH_BACK_WINSTATION; + } + } + + if (*desktop) { + // Replace the DACL on the new Desktop with a reduced privilege version. + // We can soft fail on this for now, as it's just an extra mitigation. + static const ACCESS_MASK kDesktopDenyMask = + WRITE_DAC | WRITE_OWNER | DELETE | DESKTOP_CREATEMENU | + DESKTOP_CREATEWINDOW | DESKTOP_HOOKCONTROL | DESKTOP_JOURNALPLAYBACK | + DESKTOP_JOURNALRECORD | DESKTOP_SWITCHDESKTOP; + AddKnownSidToObject(*desktop, SE_WINDOW_OBJECT, Sid(WinRestrictedCodeSid), + DENY_ACCESS, kDesktopDenyMask); + return SBOX_ALL_OK; + } + + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; +} + +std::wstring GetFullDesktopName(HWINSTA winsta, HDESK desktop) { + if (!desktop) { + NOTREACHED(); + return std::wstring(); + } + + std::wstring name; + if (winsta) { + name = base::win::GetWindowObjectName(winsta); + name += L'\\'; + } + + name += base::win::GetWindowObjectName(desktop); + return name; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/window.h b/security/sandbox/chromium/sandbox/win/src/window.h new file mode 100644 index 0000000000..1fe5b6cc72 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/window.h @@ -0,0 +1,37 @@ +// Copyright (c) 2009 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. + +#ifndef SANDBOX_SRC_WINDOW_H_ +#define SANDBOX_SRC_WINDOW_H_ + +#include <windows.h> + +#include <string> + +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +// Creates a window station. The name is generated by the OS. The security +// descriptor is based on the security descriptor of the current window +// station. +ResultCode CreateAltWindowStation(HWINSTA* winsta); + +// Creates a desktop. The name is a static string followed by the pid of the +// current process. The security descriptor on the new desktop is based on the +// security descriptor of the desktop associated with the current thread. +// If a winsta is specified, the function will switch to it before creating +// the desktop. If the functions fails the switch back to the current winsta, +// the function will return SBOX_ERROR_FAILED_TO_SWITCH_BACK_WINSTATION. +ResultCode CreateAltDesktop(HWINSTA winsta, HDESK* desktop); + +// Returns the name of the desktop referenced by |desktop|. If a window +// station is specified, the name is prepended with the window station name, +// followed by a backslash. This name can be used as the lpDesktop parameter +// to CreateProcess. +std::wstring GetFullDesktopName(HWINSTA winsta, HDESK desktop); + +} // namespace sandbox + +#endif // SANDBOX_SRC_WINDOW_H_ |