// Copyright 2010-2018, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "base/win_sandbox.h" // skipp all unless OS_WIN #ifdef OS_WIN #include #include #include #include #include #include #include #include "base/logging.h" #include "base/scoped_handle.h" #include "base/system_util.h" #include "base/util.h" using std::unique_ptr; namespace mozc { namespace { bool OpenEffectiveToken(const DWORD dwDesiredAccess, HANDLE *phToken) { HANDLE hToken = nullptr; if (!::OpenThreadToken(::GetCurrentThread(), dwDesiredAccess, TRUE, &hToken)) { if (ERROR_NO_TOKEN != ::GetLastError()) { ::CloseHandle(hToken); return false; } if (!::OpenProcessToken(::GetCurrentProcess(), dwDesiredAccess, &hToken)) { ::CloseHandle(hToken); return false; } } *phToken = hToken; return true; } bool AllocGetTokenInformation(const HANDLE hToken, const TOKEN_INFORMATION_CLASS TokenInfoClass, PVOID* ppInfo, DWORD* pdwSizeBc) { DWORD dwBufferSizeBc = 0; DWORD dwReturnSizeBc = 0; const BOOL bReturn = ::GetTokenInformation(hToken, TokenInfoClass, nullptr, 0, &dwBufferSizeBc); if (bReturn || (ERROR_INSUFFICIENT_BUFFER != ::GetLastError())) { return false; } PVOID pBuffer = ::LocalAlloc(LPTR, dwBufferSizeBc); if (pBuffer == nullptr) { return false; } if (!::GetTokenInformation(hToken, TokenInfoClass, pBuffer, dwBufferSizeBc, &dwReturnSizeBc)) { ::LocalFree(pBuffer); return false; } if (ppInfo != nullptr) { *ppInfo = pBuffer; } if (pdwSizeBc != nullptr) { *pdwSizeBc = dwReturnSizeBc; } return true; } bool GetTokenUserSidStringW(const HANDLE hToken, PWSTR *pwszSidString) { PTOKEN_USER pTokenUser = nullptr; PWSTR wszSidString = nullptr; if (AllocGetTokenInformation(hToken, TokenUser, reinterpret_cast(&pTokenUser), nullptr) && ::ConvertSidToStringSidW(pTokenUser->User.Sid, &wszSidString)) { ::LocalFree(pTokenUser); *pwszSidString = wszSidString; return true; } if (wszSidString != nullptr) { ::LocalFree(wszSidString); } if (pTokenUser != nullptr) { ::LocalFree(pTokenUser); } return false; } bool GetTokenPrimaryGroupSidStringW(const HANDLE hToken, PWSTR *pwszSidString) { PTOKEN_PRIMARY_GROUP pTokenPrimaryGroup = nullptr; PWSTR wszSidString = nullptr; if (AllocGetTokenInformation(hToken, TokenPrimaryGroup, reinterpret_cast(&pTokenPrimaryGroup), nullptr) && ::ConvertSidToStringSidW(pTokenPrimaryGroup->PrimaryGroup, &wszSidString)) { ::LocalFree(pTokenPrimaryGroup); *pwszSidString = wszSidString; return true; } if (wszSidString != nullptr) { ::LocalFree(wszSidString); } if (pTokenPrimaryGroup != nullptr) { ::LocalFree(pTokenPrimaryGroup); } return false; } class ScopedLocalFreeInvoker { public: explicit ScopedLocalFreeInvoker(void *address) : address_(address) {} ~ScopedLocalFreeInvoker() { if (address_ != nullptr) { ::LocalFree(address_); address_ = nullptr; } } private: void *address_; DISALLOW_COPY_AND_ASSIGN(ScopedLocalFreeInvoker); }; bool GetUserSid(std::wstring *token_user_sid, std::wstring *token_primary_group_sid) { DCHECK(token_user_sid); DCHECK(token_primary_group_sid); token_user_sid->clear(); token_primary_group_sid->clear(); ScopedHandle token; { HANDLE hToken = nullptr; if (!OpenEffectiveToken(TOKEN_QUERY, &hToken)) { LOG(ERROR) << "OpenEffectiveToken failed " << ::GetLastError(); return false; } token.reset(hToken); } // Get token user SID { wchar_t* sid_string = nullptr; if (!GetTokenUserSidStringW(token.get(), &sid_string)) { LOG(ERROR) << "GetTokenUserSidStringW failed " << ::GetLastError(); return false; } *token_user_sid = sid_string; ::LocalFree(sid_string); } // Get token primary group SID { wchar_t* sid_string = nullptr; if (!GetTokenPrimaryGroupSidStringW(token.get(), &sid_string)) { LOG(ERROR) << "GetTokenPrimaryGroupSidStringW failed " << ::GetLastError(); return false; } *token_primary_group_sid = sid_string; ::LocalFree(sid_string); } return true; } std::wstring Allow(const std::wstring &access_right, const std::wstring &account_sid) { return (std::wstring(L"(") + SDDL_ACCESS_ALLOWED + L";;" + access_right + L";;;" + account_sid + L")"); } std::wstring Deny(const std::wstring &access_right, const std::wstring &account_sid) { return (std::wstring(L"(") + SDDL_ACCESS_DENIED + L";;" + access_right + L";;;" + account_sid + L")"); } std::wstring MandatoryLevel(const std::wstring &mandatory_label, const std::wstring &integrity_levels) { return (std::wstring(L"(") + SDDL_MANDATORY_LABEL + L";;" + mandatory_label + L";;;" + integrity_levels + L")"); } // SDDL_ALL_APP_PACKAGES is available on Windows SDK 8.0 and later. #ifndef SDDL_ALL_APP_PACKAGES #define SDDL_ALL_APP_PACKAGES L"AC" #endif // SDDL_ALL_APP_PACKAGES // SDDL for PROCESS_QUERY_INFORMATION is not defined. So use hex digits instead. #ifndef SDDL_PROCESS_QUERY_INFORMATION static_assert(PROCESS_QUERY_INFORMATION == 0x0400, "PROCESS_QUERY_INFORMATION must be 0x0400"); #define SDDL_PROCESS_QUERY_INFORMATION L"0x0400" #endif // SDDL_PROCESS_QUERY_INFORMATION // SDDL for PROCESS_QUERY_LIMITED_INFORMATION is not defined. So use hex digits // instead. #ifndef SDDL_PROCESS_QUERY_LIMITED_INFORMATION static_assert(PROCESS_QUERY_LIMITED_INFORMATION == 0x1000, "PROCESS_QUERY_LIMITED_INFORMATION must be 0x1000"); #define SDDL_PROCESS_QUERY_LIMITED_INFORMATION L"0x1000" #endif // SDDL_PROCESS_QUERY_LIMITED_INFORMATION } // namespace std::wstring WinSandbox::GetSDDL(ObjectSecurityType shareble_object_type, const std::wstring &token_user_sid, const std::wstring &token_primary_group_sid, bool is_windows_8_or_later) { // See http://social.msdn.microsoft.com/Forums/en-US/windowssecurity/thread/e92502b1-0b9f-4e02-9d72-e4e47e924a8f/ // for how to acess named objects from an AppContainer. std::wstring dacl; std::wstring sacl; switch (shareble_object_type) { case WinSandbox::kSharablePipe: // Strip implicit owner rights // http://technet.microsoft.com/en-us/library/dd125370.aspx dacl += Allow(L"", SDDL_OWNER_RIGHTS); // Deny remote acccess dacl += Deny(SDDL_GENERIC_ALL, SDDL_NETWORK); // Allow general access to LocalSystem dacl += Allow(SDDL_GENERIC_ALL, SDDL_LOCAL_SYSTEM); // Allow general access to Built-in Administorators dacl += Allow(SDDL_GENERIC_ALL, SDDL_BUILTIN_ADMINISTRATORS); if (is_windows_8_or_later) { // Allow general access to ALL APPLICATION PACKAGES dacl += Allow(SDDL_GENERIC_ALL, SDDL_ALL_APP_PACKAGES); } // Allow general access to the current user dacl += Allow(SDDL_GENERIC_ALL, token_user_sid); // Allow read/write access to low integrity sacl += MandatoryLevel(SDDL_NO_EXECUTE_UP, SDDL_ML_LOW); break; case WinSandbox::kLooseSharablePipe: // Strip implicit owner rights // http://technet.microsoft.com/en-us/library/dd125370.aspx dacl += Allow(L"", SDDL_OWNER_RIGHTS); // Deny remote acccess dacl += Deny(SDDL_GENERIC_ALL, SDDL_NETWORK); // Allow general access to LocalSystem dacl += Allow(SDDL_GENERIC_ALL, SDDL_LOCAL_SYSTEM); // Allow general access to Built-in Administorators dacl += Allow(SDDL_GENERIC_ALL, SDDL_BUILTIN_ADMINISTRATORS); if (is_windows_8_or_later) { // Allow general access to ALL APPLICATION PACKAGES dacl += Allow(SDDL_GENERIC_ALL, SDDL_ALL_APP_PACKAGES); } // Allow general access to the current user dacl += Allow(SDDL_GENERIC_ALL, token_user_sid); // Skip 2nd-phase ACL validation against restricted tokens. dacl += Allow(SDDL_GENERIC_ALL, SDDL_RESTRICTED_CODE); // Allow read/write access to low integrity sacl += MandatoryLevel(SDDL_NO_EXECUTE_UP, SDDL_ML_LOW); break; case WinSandbox::kSharableEvent: // Strip implicit owner rights // http://technet.microsoft.com/en-us/library/dd125370.aspx dacl += Allow(L"", SDDL_OWNER_RIGHTS); // Allow general access to LocalSystem dacl += Allow(SDDL_GENERIC_ALL, SDDL_LOCAL_SYSTEM); // Allow general access to Built-in Administorators dacl += Allow(SDDL_GENERIC_ALL, SDDL_BUILTIN_ADMINISTRATORS); if (is_windows_8_or_later) { // Allow state change/synchronize to ALL APPLICATION PACKAGES dacl += Allow(SDDL_GENERIC_EXECUTE, SDDL_ALL_APP_PACKAGES); } // Allow general access to the current user dacl += Allow(SDDL_GENERIC_ALL, token_user_sid); // Skip 2nd-phase ACL validation against restricted tokens regarding // change/synchronize. dacl += Allow(SDDL_GENERIC_EXECUTE, SDDL_RESTRICTED_CODE); // Allow read/write access to low integrity sacl += MandatoryLevel(SDDL_NO_EXECUTE_UP, SDDL_ML_LOW); break; case WinSandbox::kSharableMutex: // Strip implicit owner rights // http://technet.microsoft.com/en-us/library/dd125370.aspx dacl += Allow(L"", SDDL_OWNER_RIGHTS); // Allow general access to LocalSystem dacl += Allow(SDDL_GENERIC_ALL, SDDL_LOCAL_SYSTEM); // Allow general access to Built-in Administorators dacl += Allow(SDDL_GENERIC_ALL, SDDL_BUILTIN_ADMINISTRATORS); if (is_windows_8_or_later) { // Allow state change/synchronize to ALL APPLICATION PACKAGES dacl += Allow(SDDL_GENERIC_EXECUTE, SDDL_ALL_APP_PACKAGES); } // Allow general access to the current user dacl += Allow(SDDL_GENERIC_ALL, token_user_sid); // Skip 2nd-phase ACL validation against restricted tokens regarding // change/synchronize. dacl += Allow(SDDL_GENERIC_EXECUTE, SDDL_RESTRICTED_CODE); // Allow read/write access to low integrity sacl += MandatoryLevel(SDDL_NO_EXECUTE_UP, SDDL_ML_LOW); break; case WinSandbox::kSharableFileForRead: // Strip implicit owner rights // http://technet.microsoft.com/en-us/library/dd125370.aspx dacl += Allow(L"", SDDL_OWNER_RIGHTS); // Allow general access to LocalSystem dacl += Allow(SDDL_GENERIC_ALL, SDDL_LOCAL_SYSTEM); // Allow general access to Built-in Administorators dacl += Allow(SDDL_GENERIC_ALL, SDDL_BUILTIN_ADMINISTRATORS); // Allow read access to low integrity if (is_windows_8_or_later) { // Allow general read access to ALL APPLICATION PACKAGES dacl += Allow(SDDL_GENERIC_READ, SDDL_ALL_APP_PACKAGES); } // Allow general access to the current user dacl += Allow(SDDL_GENERIC_ALL, token_user_sid); // Skip 2nd-phase ACL validation against restricted tokens regarding // general read access. dacl += Allow(SDDL_GENERIC_READ, SDDL_RESTRICTED_CODE); // Allow read access to low integrity sacl += MandatoryLevel( SDDL_NO_WRITE_UP SDDL_NO_EXECUTE_UP, SDDL_ML_LOW); break; case WinSandbox::kIPCServerProcess: // Strip implicit owner rights // http://technet.microsoft.com/en-us/library/dd125370.aspx dacl += Allow(L"", SDDL_OWNER_RIGHTS); // Allow general access to LocalSystem dacl += Allow(SDDL_GENERIC_ALL, SDDL_LOCAL_SYSTEM); // Allow general access to Built-in Administorators dacl += Allow(SDDL_GENERIC_ALL, SDDL_BUILTIN_ADMINISTRATORS); if (is_windows_8_or_later) { // Allow PROCESS_QUERY_LIMITED_INFORMATION to ALL APPLICATION PACKAGES dacl += Allow(SDDL_PROCESS_QUERY_LIMITED_INFORMATION, SDDL_ALL_APP_PACKAGES); } // Allow general access to the current user dacl += Allow(SDDL_GENERIC_ALL, token_user_sid); // Allow PROCESS_QUERY_LIMITED_INFORMATION to restricted tokens dacl += Allow(SDDL_PROCESS_QUERY_LIMITED_INFORMATION, SDDL_RESTRICTED_CODE); break; case WinSandbox::kPrivateObject: default: // Strip implicit owner rights // http://technet.microsoft.com/en-us/library/dd125370.aspx dacl += Allow(L"", SDDL_OWNER_RIGHTS); // Allow general access to LocalSystem dacl += Allow(SDDL_GENERIC_ALL, SDDL_LOCAL_SYSTEM); // Allow general access to Built-in Administorators dacl += Allow(SDDL_GENERIC_ALL, SDDL_BUILTIN_ADMINISTRATORS); // Allow general access to the current user dacl += Allow(SDDL_GENERIC_ALL, token_user_sid); break; } std::wstring sddl; // Owner SID sddl += ((SDDL_OWNER SDDL_DELIMINATOR) + token_user_sid); // Primary Group SID sddl += ((SDDL_GROUP SDDL_DELIMINATOR) + token_primary_group_sid); // DACL if (!dacl.empty()) { sddl += ((SDDL_DACL SDDL_DELIMINATOR) + dacl); } // SACL if (!sacl.empty()) { sddl += ((SDDL_SACL SDDL_DELIMINATOR) + sacl); } return sddl; } Sid::Sid(const SID *sid) { ::CopySid(sizeof(sid_), sid_, const_cast(sid)); }; Sid::Sid(WELL_KNOWN_SID_TYPE type) { DWORD size_sid = sizeof(sid_); ::CreateWellKnownSid(type, nullptr, sid_, &size_sid); } const SID *Sid::GetPSID() const { return reinterpret_cast(const_cast(sid_)); } SID *Sid::GetPSID() { return reinterpret_cast(const_cast(sid_)); } std::wstring Sid::GetName() const { wchar_t *ptr = nullptr; Sid temp_sid(GetPSID()); ConvertSidToStringSidW(temp_sid.GetPSID(), &ptr); std::wstring name = ptr; ::LocalFree(ptr); return name; } std::wstring Sid::GetAccountName() const { wchar_t *ptr = nullptr; DWORD name_size = 0; DWORD domain_name_size = 0; SID_NAME_USE name_use; Sid temp_sid(GetPSID()); ::LookupAccountSid(nullptr, temp_sid.GetPSID(), nullptr, &name_size, nullptr, &domain_name_size, &name_use); if (domain_name_size == 0) { if (name_size == 0) { // Use string SID instead. return GetName(); } unique_ptr name_buffer(new wchar_t[name_size]); ::LookupAccountSid(nullptr, temp_sid.GetPSID(), name_buffer.get(), &name_size, nullptr, &domain_name_size, &name_use); return std::wstring(L"/") + name_buffer.get(); } unique_ptr name_buffer(new wchar_t[name_size]); unique_ptr domain_name_buffer(new wchar_t[domain_name_size]); ::LookupAccountSid(nullptr, temp_sid.GetPSID(), name_buffer.get(), &name_size, domain_name_buffer.get(), &domain_name_size, &name_use); const std::wstring domain_name = std::wstring(domain_name_buffer.get()); const std::wstring user_name = std::wstring(name_buffer.get()); return domain_name + L"/" + user_name; } // make SecurityAttributes for the named pipe. bool WinSandbox::MakeSecurityAttributes( ObjectSecurityType shareble_object_type, SECURITY_ATTRIBUTES *security_attributes) { std::wstring token_user_sid; std::wstring token_primary_group_sid; if (!GetUserSid(&token_user_sid, &token_primary_group_sid)) { return false; } const std::wstring &sddl = GetSDDL( shareble_object_type, token_user_sid, token_primary_group_sid, SystemUtil::IsWindows8OrLater()); // Create self-relative SD PSECURITY_DESCRIPTOR self_relative_desc = nullptr; if (!::ConvertStringSecurityDescriptorToSecurityDescriptorW( sddl.c_str(), SDDL_REVISION_1, &self_relative_desc, nullptr)) { if (self_relative_desc != nullptr) { ::LocalFree(self_relative_desc); } LOG(ERROR) << "ConvertStringSecurityDescriptorToSecurityDescriptorW failed: " << ::GetLastError(); return false; } // Set up security attributes security_attributes->nLength= sizeof(SECURITY_ATTRIBUTES); security_attributes->lpSecurityDescriptor= self_relative_desc; security_attributes->bInheritHandle= FALSE; return true; } bool WinSandbox::AddKnownSidToKernelObject(HANDLE object, const SID *known_sid, DWORD inheritance_flag, ACCESS_MASK access_mask) { // We must pass |&descriptor| because 6th argument (|&old_dacl|) is // non-null. Actually, returned |old_dacl| points the memory block // of |descriptor|, which must be freed by ::LocalFree API. // http://msdn.microsoft.com/en-us/library/aa446654.aspx PSECURITY_DESCRIPTOR descriptor = nullptr; PACL old_dacl = nullptr; DWORD error = ::GetSecurityInfo( object, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &old_dacl, nullptr, &descriptor); // You need not to free |old_dacl| because |old_dacl| points inside of // |descriptor|. ScopedLocalFreeInvoker descripter_deleter(descriptor); if (error != ERROR_SUCCESS) { DLOG(ERROR) << "GetSecurityInfo failed" << error; return false; } EXPLICIT_ACCESS new_access = {}; new_access.grfAccessMode = GRANT_ACCESS; new_access.grfAccessPermissions = access_mask; new_access.grfInheritance = inheritance_flag; new_access.Trustee.pMultipleTrustee = nullptr; new_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; new_access.Trustee.TrusteeForm = TRUSTEE_IS_SID; // When |TrusteeForm| is TRUSTEE_IS_SID, |ptstrName| is a pointer to the SID // of the trustee. // http://msdn.microsoft.com/en-us/library/aa379636.aspx new_access.Trustee.ptstrName = reinterpret_cast(const_cast(known_sid)); PACL new_dacl = nullptr; error = ::SetEntriesInAcl(1, &new_access, old_dacl, &new_dacl); ScopedLocalFreeInvoker new_decl_deleter(new_dacl); if (error != ERROR_SUCCESS) { DLOG(ERROR) << "SetEntriesInAcl failed" << error; return false; } error = ::SetSecurityInfo( object, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, new_dacl, nullptr); if (error != ERROR_SUCCESS) { DLOG(ERROR) << "SetSecurityInfo failed" << error; return false; } return true; } // Local functions for SpawnSandboxedProcess. namespace { // This Windows job object wrapper class corresponds to the Job class in // Chromium sandbox library with JOB_LOCKDOWN except for LockedDownJob does not // set JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, which is not required by Mozc. // http://src.chromium.org/viewvc/chrome/trunk/src/sandbox/src/security_level.h?view=markup class LockedDownJob { public: LockedDownJob() : job_handle_(nullptr) {} ~LockedDownJob() { if (job_handle_ != nullptr) { ::CloseHandle(job_handle_); job_handle_ = nullptr; }; } bool IsValid() const { return (job_handle_ != nullptr); } DWORD Init(const wchar_t *job_name, bool allow_ui_operation) { if (job_handle_ != nullptr) { return ERROR_ALREADY_INITIALIZED; } job_handle_ = ::CreateJobObject(nullptr, job_name); if (job_handle_ == nullptr) { return ::GetLastError(); } { JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info = {}; limit_info.BasicLimitInformation.ActiveProcessLimit = 1; limit_info.BasicLimitInformation.LimitFlags = // Mozc does not use JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE so that the // child process can continue running even after the parent is // terminated. // JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION | JOB_OBJECT_LIMIT_ACTIVE_PROCESS; if (::SetInformationJobObject(job_handle_, JobObjectExtendedLimitInformation, &limit_info, sizeof(limit_info))) { return ::GetLastError(); } } if (!allow_ui_operation) { JOBOBJECT_BASIC_UI_RESTRICTIONS ui_restrictions = {}; ui_restrictions.UIRestrictionsClass = JOB_OBJECT_UILIMIT_WRITECLIPBOARD | JOB_OBJECT_UILIMIT_READCLIPBOARD | JOB_OBJECT_UILIMIT_HANDLES | JOB_OBJECT_UILIMIT_GLOBALATOMS | JOB_OBJECT_UILIMIT_DISPLAYSETTINGS | JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS | JOB_OBJECT_UILIMIT_DESKTOP | JOB_OBJECT_UILIMIT_EXITWINDOWS; if (!::SetInformationJobObject(job_handle_, JobObjectBasicUIRestrictions, &ui_restrictions, sizeof(ui_restrictions))) { return ::GetLastError(); } } return ERROR_SUCCESS; } DWORD AssignProcessToJob(HANDLE process_handle) { if (job_handle_ == nullptr) { return ERROR_NO_DATA; } if (!::AssignProcessToJobObject(job_handle_, process_handle)) { return ::GetLastError(); } return ERROR_SUCCESS; } private: HANDLE job_handle_; DISALLOW_COPY_AND_ASSIGN(LockedDownJob); }; bool CreateSuspendedRestrictedProcess(unique_ptr *command_line, const WinSandbox::SecurityInfo &info, ScopedHandle *process_handle, ScopedHandle *thread_handle, DWORD *pid) { HANDLE process_token_ret = nullptr; if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &process_token_ret)) { return false; } ScopedHandle process_token(process_token_ret); ScopedHandle primary_token; if (!WinSandbox::GetRestrictedTokenHandle(process_token.get(), info.primary_level, info.integrity_level, &primary_token)) { return false; } ScopedHandle impersonation_token; if (!WinSandbox::GetRestrictedTokenHandleForImpersonation( process_token.get(), info.impersonation_level, info.integrity_level, &impersonation_token)) { return false; } PSECURITY_ATTRIBUTES security_attributes_ptr = nullptr; SECURITY_ATTRIBUTES security_attributes = {}; if (WinSandbox::MakeSecurityAttributes(WinSandbox::kIPCServerProcess, &security_attributes)) { security_attributes_ptr = &security_attributes; // Override the impersonation thread token's DACL to avoid http://b/1728895 // On Windows Server, the objects created by a member of // the built-in administrators group do not always explicitly // allow the current user to access the objects. // Instead, such objects implicitly allow the user by allowing // the built-in administratros group. // However, Mozc asks Sandbox to remove the built-in administrators // group from the current user's groups. Thus the impersonation thread // cannot even look at its own thread token. // That prevents GetRunLevel() from verifying its own thread identity. // Note: Overriding the thread token's // DACL will not elevate the thread's running context. if (!::SetKernelObjectSecurity( impersonation_token.get(), DACL_SECURITY_INFORMATION, security_attributes_ptr->lpSecurityDescriptor)) { const DWORD last_error = ::GetLastError(); DLOG(ERROR) << "SetKernelObjectSecurity failed. Error: " << last_error; return false; } } DWORD creation_flags = info.creation_flags | CREATE_SUSPENDED; // Note: If the current process is already in a job, you cannot use // CREATE_BREAKAWAY_FROM_JOB. See b/1571395 if (info.use_locked_down_job) { creation_flags |= CREATE_BREAKAWAY_FROM_JOB; } const wchar_t *startup_directory = (info.in_system_dir ? SystemUtil::GetSystemDir() : nullptr); STARTUPINFO startup_info = {sizeof(STARTUPINFO)}; PROCESS_INFORMATION process_info = {}; // 3rd parameter of CreateProcessAsUser must be a writable buffer. if (!::CreateProcessAsUser(primary_token.get(), nullptr, // No application name. command_line->get(), // must be writable. security_attributes_ptr, nullptr, FALSE, // Do not inherit handles. creation_flags, nullptr, // Use the environment of the caller. startup_directory, &startup_info, &process_info)) { const DWORD last_error = ::GetLastError(); DLOG(ERROR) << "CreateProcessAsUser failed. Error: " << last_error; return false; } if (security_attributes_ptr != nullptr) { ::LocalFree(security_attributes_ptr->lpSecurityDescriptor); } // Change the token of the main thread of the new process for the // impersonation token with more rights. if (!::SetThreadToken(&process_info.hThread, impersonation_token.get())) { const DWORD last_error = ::GetLastError(); DLOG(ERROR) << "SetThreadToken failed. Error: " << last_error; ::TerminateProcess(process_info.hProcess, 0); ::CloseHandle(process_info.hProcess); ::CloseHandle(process_info.hThread); return false; } if (thread_handle != nullptr) { thread_handle->reset(process_info.hThread); } else { ::CloseHandle(process_info.hThread); } if (process_handle != nullptr) { process_handle->reset(process_info.hProcess); } else { ::CloseHandle(process_info.hProcess); } if (pid != nullptr) { *pid = process_info.dwProcessId; } return true; } bool SpawnSandboxedProcessImpl(unique_ptr *command_line, const WinSandbox::SecurityInfo &info, DWORD *pid) { LockedDownJob job; if (info.use_locked_down_job) { const DWORD error_code = job.Init(nullptr, info.allow_ui_operation); if (error_code != ERROR_SUCCESS) { return false; } } ScopedHandle thread_handle; ScopedHandle process_handle; if (!CreateSuspendedRestrictedProcess( command_line, info, &process_handle, &thread_handle, pid)) { return false; } if (job.IsValid()) { const DWORD error_code = job.AssignProcessToJob(process_handle.get()); if (error_code != ERROR_SUCCESS) { ::TerminateProcess(process_handle.get(), 0); return false; } } ::ResumeThread(thread_handle.get()); return true; } } // namespace WinSandbox::SecurityInfo::SecurityInfo() : primary_level(WinSandbox::USER_LOCKDOWN), impersonation_level(WinSandbox::USER_LOCKDOWN), integrity_level(WinSandbox::INTEGRITY_LEVEL_SYSTEM), creation_flags(0), use_locked_down_job(false), allow_ui_operation(false), in_system_dir(false) {} bool WinSandbox::SpawnSandboxedProcess(const string &path, const string &arg, const SecurityInfo &info, DWORD *pid) { std::wstring wpath; Util::UTF8ToWide(path, &wpath); wpath = L"\"" + wpath + L"\""; if (!arg.empty()) { std::wstring warg; Util::UTF8ToWide(arg, &warg); wpath += L" "; wpath += warg; } unique_ptr wpath2(new wchar_t[wpath.size() + 1]); if (0 != wcscpy_s(wpath2.get(), wpath.size() + 1, wpath.c_str())) { return false; } if (!SpawnSandboxedProcessImpl(&wpath2, info, pid)) { return false; } return true; } // Utility functions and classes for GetRestrictionInfo namespace { template class Optional { public: Optional() : value_(T()), has_value_(false) {} explicit Optional(const T &src) : value_(src), has_value_(true) {} const T &value() const { return value_; } static Optional None() { return Optional(); } T *mutable_value() { has_value_ = true; return &value_; } bool has_value() const { return has_value_; } void Clear() { has_value_ = false; } private: T value_; bool has_value_; }; // A utility class for GetTokenInformation API. // This class manages data buffer into which |TokenDataType| type data // is filled. template class ScopedTokenInfo { public: explicit ScopedTokenInfo(HANDLE token) : initialized_(false) { DWORD num_bytes = 0; ::GetTokenInformation(token, TokenClass, nullptr, 0, &num_bytes); if (num_bytes == 0) { return; } buffer_.reset(new BYTE[num_bytes]); TokenDataType *all_token_groups = reinterpret_cast(buffer_.get()); if (!::GetTokenInformation(token, TokenClass, all_token_groups, num_bytes, &num_bytes)) { const DWORD last_error = ::GetLastError(); DLOG(ERROR) << "GetTokenInformation failed. Last error: " << last_error; buffer_.reset(); return; } initialized_ = true; } TokenDataType *get() const { return reinterpret_cast(buffer_.get()); } TokenDataType *operator->() const { return get(); } private: unique_ptr buffer_; bool initialized_; }; // Wrapper class for the SID_AND_ATTRIBUTES structure. class SidAndAttributes { public: SidAndAttributes() : sid_(static_cast(nullptr)), attributes_(0) {} SidAndAttributes(Sid sid, DWORD attributes) : sid_(sid), attributes_(attributes) {} const Sid &sid() const { return sid_; } const DWORD &attributes() const { return attributes_; } const bool HasAttribute(DWORD attribute) const { return (attributes_ & attribute) == attribute; } private: Sid sid_; DWORD attributes_; }; // Returns all the 'TokenGroups' information of the specified |token_handle|. std::vector GetAllTokenGroups(HANDLE token_handle) { std::vector result; ScopedTokenInfo all_token_groups(token_handle); if (all_token_groups.get() == nullptr) { return result; } for (size_t i = 0; i < all_token_groups->GroupCount; ++i) { Sid sid(static_cast(all_token_groups->Groups[i].Sid)); const DWORD attributes = all_token_groups->Groups[i].Attributes; result.push_back(SidAndAttributes(sid, attributes)); } return result; } std::vector FilterByHavingAttribute( const std::vector &source, DWORD attribute) { std::vector result; for (size_t i = 0; i < source.size(); ++i) { if (source[i].HasAttribute(attribute)) { result.push_back(source[i]); } } return result; } std::vector FilterByNotHavingAttribute( const std::vector &source, DWORD attribute) { std::vector result; for (size_t i = 0; i < source.size(); ++i) { if (!source[i].HasAttribute(attribute)) { result.push_back(source[i]); } } return result; } template std::vector FilterSidExceptFor( const std::vector &source_sids, const WELL_KNOWN_SID_TYPE (&exception_sids)[NumExceptions]) { std::vector result; // find logon_sid. for (size_t i = 0; i < source_sids.size(); ++i) { bool in_the_exception_list = false; for (size_t j = 0; j < NumExceptions; ++j) { // These variables must be non-const because EqualSid API requires // non-const pointer. Sid source = source_sids[i].sid(); Sid except(exception_sids[j]); if (::EqualSid(source.GetPSID(), except.GetPSID())) { in_the_exception_list = true; break; } } if (!in_the_exception_list) { result.push_back(source_sids[i].sid()); } } return result; } template std::vector FilterPrivilegesExceptFor( const std::vector &source_privileges, const wchar_t *(&exception_privileges)[NumExceptions]) { std::vector result; for (size_t i = 0; i < source_privileges.size(); ++i) { bool in_the_exception_list = false; for (size_t j = 0; j < NumExceptions; ++j) { const LUID source = source_privileges[i].Luid; LUID except = {}; ::LookupPrivilegeValue(nullptr, exception_privileges[j], &except); if ((source.HighPart == except.HighPart) && (source.LowPart == except.LowPart)) { in_the_exception_list = true; break; } } if (!in_the_exception_list) { result.push_back(source_privileges[i].Luid); } } return result; } Optional GetUserSid(HANDLE token) { ScopedTokenInfo token_user(token); if (token_user.get() == nullptr) { return Optional::None(); } Sid sid(static_cast(token_user->User.Sid)); const DWORD attributes = token_user->User.Attributes; return Optional(SidAndAttributes(sid, attributes)); } std::vector GetPrivileges(HANDLE token) { std::vector result; ScopedTokenInfo token_privileges(token); if (token_privileges.get() == nullptr) { return result; } for (size_t i = 0; i < token_privileges->PrivilegeCount; ++i) { result.push_back(token_privileges->Privileges[i]); } return result; } bool CreateRestrictedTokenImpl(HANDLE effective_token, WinSandbox::TokenLevel security_level, ScopedHandle *restricted_token) { const std::vector sids_to_disable = WinSandbox::GetSidsToDisable(effective_token, security_level); const std::vector privileges_to_disable = WinSandbox::GetPrivilegesToDisable(effective_token, security_level); const std::vector sids_to_restrict = WinSandbox::GetSidsToRestrict(effective_token, security_level); if ((sids_to_disable.size() == 0) && (privileges_to_disable.size() == 0) && (sids_to_restrict.size() == 0)) { // 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. HANDLE new_token = nullptr; const BOOL result = ::DuplicateTokenEx( effective_token, TOKEN_ALL_ACCESS, nullptr, SecurityIdentification, TokenPrimary, &new_token); if (result == FALSE) { return false; } restricted_token->reset(new_token); return true; } unique_ptr sids_to_disable_array; std::vector sids_to_disable_array_buffer = sids_to_disable; { const size_t size = sids_to_disable.size(); if (size > 0) { sids_to_disable_array.reset(new SID_AND_ATTRIBUTES[size]); for (size_t i = 0; i < size; ++i) { sids_to_disable_array[i].Attributes = SE_GROUP_USE_FOR_DENY_ONLY; sids_to_disable_array[i].Sid = sids_to_disable_array_buffer[i].GetPSID(); } } } unique_ptr privileges_to_disable_array; { const size_t size = privileges_to_disable.size(); if (size > 0) { privileges_to_disable_array.reset(new LUID_AND_ATTRIBUTES[size]); for (unsigned int i = 0; i < size; ++i) { privileges_to_disable_array[i].Attributes = 0; privileges_to_disable_array[i].Luid = privileges_to_disable[i]; } } } unique_ptr sids_to_restrict_array; std::vector sids_to_restrict_array_buffer = sids_to_restrict; { const size_t size = sids_to_restrict.size(); if (size > 0) { sids_to_restrict_array.reset(new SID_AND_ATTRIBUTES[size]); for (size_t i = 0; i < size; ++i) { sids_to_restrict_array[i].Attributes = 0; sids_to_restrict_array[i].Sid = sids_to_restrict_array_buffer[i].GetPSID(); } } } HANDLE new_token = nullptr; const BOOL result = ::CreateRestrictedToken(effective_token, SANDBOX_INERT, // This flag is used on Windows 7 static_cast(sids_to_disable.size()), sids_to_disable_array.get(), static_cast(privileges_to_disable.size()), privileges_to_disable_array.get(), static_cast(sids_to_restrict.size()), sids_to_restrict_array.get(), &new_token); if (result == FALSE) { return false; } restricted_token->reset(new_token); return true; } bool AddSidToDefaultDacl(HANDLE token, const Sid& sid, ACCESS_MASK access) { if (token == nullptr) { return false; } ScopedTokenInfo default_dacl(token); if (default_dacl.get() == nullptr) { return false; } ACL* new_dacl = nullptr; { EXPLICIT_ACCESS new_access = {}; new_access.grfAccessMode = GRANT_ACCESS; 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; Sid temp_sid(sid); new_access.Trustee.ptstrName = reinterpret_cast(temp_sid.GetPSID()); const DWORD result = ::SetEntriesInAcl( 1, &new_access, default_dacl->DefaultDacl, &new_dacl); if (result != ERROR_SUCCESS) { return false; } } TOKEN_DEFAULT_DACL new_token_dacl = {new_dacl}; const BOOL result = ::SetTokenInformation( token, TokenDefaultDacl, &new_token_dacl, sizeof(new_token_dacl)); ::LocalFree(new_dacl); return (result != FALSE); } const wchar_t *GetPredefinedSidString( WinSandbox::IntegrityLevel integrity_level) { // Defined in the following documents. // http://msdn.microsoft.com/en-us/library/cc980032.aspx // http://support.microsoft.com/kb/243330 switch (integrity_level) { case WinSandbox::INTEGRITY_LEVEL_SYSTEM: return L"S-1-16-16384"; case WinSandbox::INTEGRITY_LEVEL_HIGH: return L"S-1-16-12288"; case WinSandbox::INTEGRITY_LEVEL_MEDIUM_PLUS: return L"S-1-16-8448"; case WinSandbox::INTEGRITY_LEVEL_MEDIUM: return L"S-1-16-8192"; case WinSandbox::INTEGRITY_LEVEL_LOW: return L"S-1-16-4096"; case WinSandbox::INTEGRITY_LEVEL_UNTRUSTED: return L"S-1-16-0"; case WinSandbox::INTEGRITY_LEVEL_LAST: return nullptr; } return nullptr; } bool SetTokenIntegrityLevel(HANDLE token, WinSandbox::IntegrityLevel integrity_level) { const wchar_t* sid_string = GetPredefinedSidString(integrity_level); if (sid_string == nullptr) { // do not change the integrity level. return true; } PSID integrity_sid = nullptr; if (!::ConvertStringSidToSid(sid_string, &integrity_sid)) { return false; } TOKEN_MANDATORY_LABEL label = { {integrity_sid, SE_GROUP_INTEGRITY} }; const DWORD size = sizeof(TOKEN_MANDATORY_LABEL) + ::GetLengthSid(integrity_sid); const BOOL result = ::SetTokenInformation( token, TokenIntegrityLevel, &label, size); ::LocalFree(integrity_sid); return (result != FALSE); } } // namespace std::vector WinSandbox::GetSidsToDisable(HANDLE effective_token, TokenLevel security_level) { const std::vector all_token_groups = GetAllTokenGroups(effective_token); const Optional current_user_sid = GetUserSid(effective_token); const std::vector normal_tokens = FilterByNotHavingAttribute( FilterByNotHavingAttribute(all_token_groups, SE_GROUP_LOGON_ID), SE_GROUP_INTEGRITY); std::vector sids_to_disable; switch (security_level) { case USER_UNPROTECTED: case USER_RESTRICTED_SAME_ACCESS: sids_to_disable.clear(); break; case USER_NON_ADMIN: case USER_INTERACTIVE: { const WELL_KNOWN_SID_TYPE kSidExceptions[] = { WinBuiltinUsersSid, WinWorldSid, WinInteractiveSid, WinAuthenticatedUserSid, }; sids_to_disable = FilterSidExceptFor(normal_tokens, kSidExceptions); break; } case USER_LIMITED: { const WELL_KNOWN_SID_TYPE kSidExceptions[] = { WinBuiltinUsersSid, WinWorldSid, WinInteractiveSid, }; sids_to_disable = FilterSidExceptFor(normal_tokens, kSidExceptions); break; } case USER_RESTRICTED: case USER_LOCKDOWN: if (current_user_sid.has_value()) { sids_to_disable.push_back(current_user_sid.value().sid()); } for (size_t i = 0; i < normal_tokens.size(); ++i) { sids_to_disable.push_back(normal_tokens[i].sid()); } break; default: DLOG(FATAL) << "unexpeced TokenLevel"; break; } return sids_to_disable; } std::vector WinSandbox::GetPrivilegesToDisable(HANDLE effective_token, TokenLevel security_level ) { const std::vector all_privileges = GetPrivileges(effective_token); std::vector privileges_to_disable; switch (security_level) { case USER_UNPROTECTED: case USER_RESTRICTED_SAME_ACCESS: privileges_to_disable.clear(); break; case USER_NON_ADMIN: case USER_INTERACTIVE: case USER_LIMITED: case USER_RESTRICTED: { const wchar_t* kPrivilegeExceptions[] = { SE_CHANGE_NOTIFY_NAME, }; privileges_to_disable = FilterPrivilegesExceptFor(all_privileges, kPrivilegeExceptions); break; } case USER_LOCKDOWN: for (size_t i = 0; i < all_privileges.size(); ++i) { privileges_to_disable.push_back(all_privileges[i].Luid); } break; default: DLOG(FATAL) << "unexpeced TokenLevel"; break; } return privileges_to_disable; } std::vector WinSandbox::GetSidsToRestrict(HANDLE effective_token, TokenLevel security_level) { const std::vector all_token_groups = GetAllTokenGroups(effective_token); const Optional current_user_sid = GetUserSid(effective_token); const std::vector token_logon_session = FilterByHavingAttribute(all_token_groups, SE_GROUP_LOGON_ID); std::vector sids_to_restrict; switch (security_level) { case USER_UNPROTECTED: sids_to_restrict.clear(); break; case USER_RESTRICTED_SAME_ACCESS: { if (current_user_sid.has_value()) { sids_to_restrict.push_back(current_user_sid.value().sid()); } const std::vector tokens = FilterByNotHavingAttribute(all_token_groups, SE_GROUP_INTEGRITY); for (size_t i = 0; i < tokens.size(); ++i) { sids_to_restrict.push_back(tokens[i].sid()); } break; } case USER_NON_ADMIN: sids_to_restrict.clear(); break; case USER_INTERACTIVE: sids_to_restrict.push_back(Sid(WinBuiltinUsersSid)); sids_to_restrict.push_back(Sid(WinWorldSid)); sids_to_restrict.push_back(Sid(WinRestrictedCodeSid)); if (current_user_sid.has_value()) { sids_to_restrict.push_back(current_user_sid.value().sid()); } for (size_t i = 0; i < token_logon_session.size(); ++i) { sids_to_restrict.push_back(token_logon_session[i].sid()); } break; case USER_LIMITED: sids_to_restrict.push_back(Sid(WinBuiltinUsersSid)); sids_to_restrict.push_back(Sid(WinWorldSid)); sids_to_restrict.push_back(Sid(WinRestrictedCodeSid)); // On Windows Vista, the following token (current logon sid) is required // to create objects in BNO. Consider to use low integrity level // so that it cannot access object created by other processes. for (size_t i = 0; i < token_logon_session.size(); ++i) { sids_to_restrict.push_back(token_logon_session[i].sid()); } break; case USER_RESTRICTED: sids_to_restrict.push_back(Sid(WinRestrictedCodeSid)); break; case USER_LOCKDOWN: sids_to_restrict.push_back(Sid(WinNullSid)); break; default: DLOG(FATAL) << "unexpeced TokenLevel"; break; } return sids_to_restrict; } bool WinSandbox::GetRestrictedTokenHandle( HANDLE effective_token, TokenLevel security_level, IntegrityLevel integrity_level, ScopedHandle* restricted_token) { ScopedHandle new_token; if (!CreateRestrictedTokenImpl(effective_token, security_level, &new_token)) { return false; } // Modify the default dacl on the token to contain Restricted and the user. if (!AddSidToDefaultDacl(new_token.get(), Sid(WinRestrictedCodeSid), GENERIC_ALL)) { return false; } { ScopedTokenInfo token_user(new_token.get()); if (token_user.get() == nullptr) { return false; } Sid user_sid(static_cast(token_user->User.Sid)); if (!AddSidToDefaultDacl(new_token.get(), user_sid, GENERIC_ALL)) { return false; } } if (!SetTokenIntegrityLevel(new_token.get(), integrity_level)) { return false; } HANDLE token_handle = nullptr; const BOOL result = ::DuplicateHandle( ::GetCurrentProcess(), new_token.get(), ::GetCurrentProcess(), &token_handle, TOKEN_ALL_ACCESS, FALSE, 0); if (result == FALSE) { return false; } restricted_token->reset(token_handle); return true; } bool WinSandbox::GetRestrictedTokenHandleForImpersonation( HANDLE effective_token, TokenLevel security_level, IntegrityLevel integrity_level, ScopedHandle* restricted_token) { ScopedHandle new_token; if (!GetRestrictedTokenHandle(effective_token, security_level, integrity_level, &new_token)) { return false; } HANDLE impersonation_token_ret = nullptr; if (!::DuplicateToken(new_token.get(), SecurityImpersonation, &impersonation_token_ret)) { return false; } ScopedHandle impersonation_token(impersonation_token_ret); HANDLE restricted_token_ret = nullptr; if (!::DuplicateHandle(::GetCurrentProcess(), impersonation_token.get(), ::GetCurrentProcess(), &restricted_token_ret, TOKEN_ALL_ACCESS, FALSE, 0)) { return false; } restricted_token->reset(restricted_token_ret); return true; } bool WinSandbox::EnsureAllApplicationPackagesPermisssion( const std::wstring &file_name) { // Get "All Application Packages" group SID. const ATL::CSid all_application_packages( Sid(WinBuiltinAnyPackageSid).GetPSID()); // Get current DACL (Discretionary Access Control List) of |file_name|. ATL::CDacl dacl; if (!ATL::AtlGetDacl(file_name.c_str(), SE_FILE_OBJECT, &dacl)) { return false; } // As of Windows 10 Anniversary Update, following access masks (==0x1200a9) // are specified to files under Program Files by default. const ACCESS_MASK kDesiredMask = FILE_READ_DATA | FILE_READ_EA | FILE_EXECUTE | READ_CONTROL | SYNCHRONIZE; // Check if the desired ACE is already specified or not. for (UINT i = 0; i < dacl.GetAceCount(); ++i) { CSid ace_sid; ACCESS_MASK acess_mask = 0; BYTE ace_type = 0; dacl.GetAclEntry(i, &ace_sid, &acess_mask, &ace_type); if (ace_sid == all_application_packages && ace_type == ACCESS_ALLOWED_ACE_TYPE && (acess_mask & kDesiredMask) == kDesiredMask) { // This is the desired ACE. There is nothing to do. return true; } } // We are here because there is no desired ACE. Hence we do add it. if (!dacl.AddAllowedAce( all_application_packages, kDesiredMask, ACCESS_ALLOWED_ACE_TYPE)) { return false; } return ATL::AtlSetDacl(file_name.c_str(), SE_FILE_OBJECT, dacl); } } // namespace mozc #endif // OS_WIN