michael@0: // Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "sandbox/win/src/restricted_token_utils.h" michael@0: michael@0: #include "base/logging.h" michael@0: #include "base/win/scoped_handle.h" michael@0: #include "base/win/scoped_process_information.h" michael@0: #include "base/win/windows_version.h" michael@0: #include "sandbox/win/src/job.h" michael@0: #include "sandbox/win/src/restricted_token.h" michael@0: #include "sandbox/win/src/security_level.h" michael@0: #include "sandbox/win/src/sid.h" michael@0: michael@0: namespace sandbox { michael@0: michael@0: DWORD CreateRestrictedToken(HANDLE *token_handle, michael@0: TokenLevel security_level, michael@0: IntegrityLevel integrity_level, michael@0: TokenType token_type) { michael@0: if (!token_handle) michael@0: return ERROR_BAD_ARGUMENTS; michael@0: michael@0: RestrictedToken restricted_token; michael@0: restricted_token.Init(NULL); // Initialized with the current process token michael@0: michael@0: std::vector privilege_exceptions; michael@0: std::vector sid_exceptions; michael@0: michael@0: bool deny_sids = true; michael@0: bool remove_privileges = true; michael@0: michael@0: switch (security_level) { michael@0: case USER_UNPROTECTED: { michael@0: deny_sids = false; michael@0: remove_privileges = false; michael@0: break; michael@0: } michael@0: case USER_RESTRICTED_SAME_ACCESS: { michael@0: deny_sids = false; michael@0: remove_privileges = false; michael@0: michael@0: unsigned err_code = restricted_token.AddRestrictingSidAllSids(); michael@0: if (ERROR_SUCCESS != err_code) michael@0: return err_code; michael@0: michael@0: break; michael@0: } michael@0: case USER_NON_ADMIN: { michael@0: sid_exceptions.push_back(WinBuiltinUsersSid); michael@0: sid_exceptions.push_back(WinWorldSid); michael@0: sid_exceptions.push_back(WinInteractiveSid); michael@0: sid_exceptions.push_back(WinAuthenticatedUserSid); michael@0: privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); michael@0: break; michael@0: } michael@0: case USER_INTERACTIVE: { michael@0: sid_exceptions.push_back(WinBuiltinUsersSid); michael@0: sid_exceptions.push_back(WinWorldSid); michael@0: sid_exceptions.push_back(WinInteractiveSid); michael@0: sid_exceptions.push_back(WinAuthenticatedUserSid); michael@0: privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); michael@0: restricted_token.AddRestrictingSid(WinBuiltinUsersSid); michael@0: restricted_token.AddRestrictingSid(WinWorldSid); michael@0: restricted_token.AddRestrictingSid(WinRestrictedCodeSid); michael@0: restricted_token.AddRestrictingSidCurrentUser(); michael@0: restricted_token.AddRestrictingSidLogonSession(); michael@0: break; michael@0: } michael@0: case USER_LIMITED: { michael@0: sid_exceptions.push_back(WinBuiltinUsersSid); michael@0: sid_exceptions.push_back(WinWorldSid); michael@0: sid_exceptions.push_back(WinInteractiveSid); michael@0: privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); michael@0: restricted_token.AddRestrictingSid(WinBuiltinUsersSid); michael@0: restricted_token.AddRestrictingSid(WinWorldSid); michael@0: restricted_token.AddRestrictingSid(WinRestrictedCodeSid); michael@0: michael@0: // This token has to be able to create objects in BNO. michael@0: // Unfortunately, on vista, it needs the current logon sid michael@0: // in the token to achieve this. You should also set the process to be michael@0: // low integrity level so it can't access object created by other michael@0: // processes. michael@0: if (base::win::GetVersion() >= base::win::VERSION_VISTA) michael@0: restricted_token.AddRestrictingSidLogonSession(); michael@0: break; michael@0: } michael@0: case USER_RESTRICTED: { michael@0: privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); michael@0: restricted_token.AddUserSidForDenyOnly(); michael@0: restricted_token.AddRestrictingSid(WinRestrictedCodeSid); michael@0: break; michael@0: } michael@0: case USER_LOCKDOWN: { michael@0: restricted_token.AddUserSidForDenyOnly(); michael@0: restricted_token.AddRestrictingSid(WinNullSid); michael@0: break; michael@0: } michael@0: default: { michael@0: return ERROR_BAD_ARGUMENTS; michael@0: } michael@0: } michael@0: michael@0: DWORD err_code = ERROR_SUCCESS; michael@0: if (deny_sids) { michael@0: err_code = restricted_token.AddAllSidsForDenyOnly(&sid_exceptions); michael@0: if (ERROR_SUCCESS != err_code) michael@0: return err_code; michael@0: } michael@0: michael@0: if (remove_privileges) { michael@0: err_code = restricted_token.DeleteAllPrivileges(&privilege_exceptions); michael@0: if (ERROR_SUCCESS != err_code) michael@0: return err_code; michael@0: } michael@0: michael@0: restricted_token.SetIntegrityLevel(integrity_level); michael@0: michael@0: switch (token_type) { michael@0: case PRIMARY: { michael@0: err_code = restricted_token.GetRestrictedTokenHandle(token_handle); michael@0: break; michael@0: } michael@0: case IMPERSONATION: { michael@0: err_code = restricted_token.GetRestrictedTokenHandleForImpersonation( michael@0: token_handle); michael@0: break; michael@0: } michael@0: default: { michael@0: err_code = ERROR_BAD_ARGUMENTS; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return err_code; michael@0: } michael@0: michael@0: DWORD StartRestrictedProcessInJob(wchar_t *command_line, michael@0: TokenLevel primary_level, michael@0: TokenLevel impersonation_level, michael@0: JobLevel job_level, michael@0: HANDLE *const job_handle_ret) { michael@0: Job job; michael@0: DWORD err_code = job.Init(job_level, NULL, 0); michael@0: if (ERROR_SUCCESS != err_code) michael@0: return err_code; michael@0: michael@0: if (JOB_UNPROTECTED != job_level) { michael@0: // Share the Desktop handle to be able to use MessageBox() in the sandboxed michael@0: // application. michael@0: err_code = job.UserHandleGrantAccess(GetDesktopWindow()); michael@0: if (ERROR_SUCCESS != err_code) michael@0: return err_code; michael@0: } michael@0: michael@0: // Create the primary (restricted) token for the process michael@0: HANDLE primary_token_handle = NULL; michael@0: err_code = CreateRestrictedToken(&primary_token_handle, michael@0: primary_level, michael@0: INTEGRITY_LEVEL_LAST, michael@0: PRIMARY); michael@0: if (ERROR_SUCCESS != err_code) { michael@0: return err_code; michael@0: } michael@0: base::win::ScopedHandle primary_token(primary_token_handle); michael@0: michael@0: // Create the impersonation token (restricted) to be able to start the michael@0: // process. michael@0: HANDLE impersonation_token_handle; michael@0: err_code = CreateRestrictedToken(&impersonation_token_handle, michael@0: impersonation_level, michael@0: INTEGRITY_LEVEL_LAST, michael@0: IMPERSONATION); michael@0: if (ERROR_SUCCESS != err_code) { michael@0: return err_code; michael@0: } michael@0: base::win::ScopedHandle impersonation_token(impersonation_token_handle); michael@0: michael@0: // Start the process michael@0: STARTUPINFO startup_info = {0}; michael@0: base::win::ScopedProcessInformation process_info; michael@0: DWORD flags = CREATE_SUSPENDED; michael@0: michael@0: if (base::win::GetVersion() < base::win::VERSION_WIN8) { michael@0: // Windows 8 implements nested jobs, but for older systems we need to michael@0: // break out of any job we're in to enforce our restrictions. michael@0: flags |= CREATE_BREAKAWAY_FROM_JOB; michael@0: } michael@0: michael@0: if (!::CreateProcessAsUser(primary_token.Get(), michael@0: NULL, // No application name. michael@0: command_line, michael@0: NULL, // No security attribute. michael@0: NULL, // No thread attribute. michael@0: FALSE, // Do not inherit handles. michael@0: flags, michael@0: NULL, // Use the environment of the caller. michael@0: NULL, // Use current directory of the caller. michael@0: &startup_info, michael@0: process_info.Receive())) { michael@0: return ::GetLastError(); michael@0: } michael@0: michael@0: // Change the token of the main thread of the new process for the michael@0: // impersonation token with more rights. michael@0: { michael@0: HANDLE temp_thread = process_info.thread_handle(); michael@0: if (!::SetThreadToken(&temp_thread, impersonation_token.Get())) { michael@0: ::TerminateProcess(process_info.process_handle(), michael@0: 0); // exit code michael@0: return ::GetLastError(); michael@0: } michael@0: } michael@0: michael@0: err_code = job.AssignProcessToJob(process_info.process_handle()); michael@0: if (ERROR_SUCCESS != err_code) { michael@0: ::TerminateProcess(process_info.process_handle(), michael@0: 0); // exit code michael@0: return ::GetLastError(); michael@0: } michael@0: michael@0: // Start the application michael@0: ::ResumeThread(process_info.thread_handle()); michael@0: michael@0: (*job_handle_ret) = job.Detach(); michael@0: michael@0: return ERROR_SUCCESS; michael@0: } michael@0: michael@0: DWORD SetObjectIntegrityLabel(HANDLE handle, SE_OBJECT_TYPE type, michael@0: const wchar_t* ace_access, michael@0: const wchar_t* integrity_level_sid) { michael@0: // Build the SDDL string for the label. michael@0: std::wstring sddl = L"S:("; // SDDL for a SACL. michael@0: sddl += SDDL_MANDATORY_LABEL; // Ace Type is "Mandatory Label". michael@0: sddl += L";;"; // No Ace Flags. michael@0: sddl += ace_access; // Add the ACE access. michael@0: sddl += L";;;"; // No ObjectType and Inherited Object Type. michael@0: sddl += integrity_level_sid; // Trustee Sid. michael@0: sddl += L")"; michael@0: michael@0: DWORD error = ERROR_SUCCESS; michael@0: PSECURITY_DESCRIPTOR sec_desc = NULL; michael@0: michael@0: PACL sacl = NULL; michael@0: BOOL sacl_present = FALSE; michael@0: BOOL sacl_defaulted = FALSE; michael@0: michael@0: if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(), michael@0: SDDL_REVISION, michael@0: &sec_desc, NULL)) { michael@0: if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl, michael@0: &sacl_defaulted)) { michael@0: error = ::SetSecurityInfo(handle, type, michael@0: LABEL_SECURITY_INFORMATION, NULL, NULL, NULL, michael@0: sacl); michael@0: } else { michael@0: error = ::GetLastError(); michael@0: } michael@0: michael@0: ::LocalFree(sec_desc); michael@0: } else { michael@0: return::GetLastError(); michael@0: } michael@0: michael@0: return error; michael@0: } michael@0: michael@0: const wchar_t* GetIntegrityLevelString(IntegrityLevel integrity_level) { michael@0: switch (integrity_level) { michael@0: case INTEGRITY_LEVEL_SYSTEM: michael@0: return L"S-1-16-16384"; michael@0: case INTEGRITY_LEVEL_HIGH: michael@0: return L"S-1-16-12288"; michael@0: case INTEGRITY_LEVEL_MEDIUM: michael@0: return L"S-1-16-8192"; michael@0: case INTEGRITY_LEVEL_MEDIUM_LOW: michael@0: return L"S-1-16-6144"; michael@0: case INTEGRITY_LEVEL_LOW: michael@0: return L"S-1-16-4096"; michael@0: case INTEGRITY_LEVEL_BELOW_LOW: michael@0: return L"S-1-16-2048"; michael@0: case INTEGRITY_LEVEL_UNTRUSTED: michael@0: return L"S-1-16-0"; michael@0: case INTEGRITY_LEVEL_LAST: michael@0: return NULL; michael@0: } michael@0: michael@0: NOTREACHED(); michael@0: return NULL; michael@0: } michael@0: DWORD SetTokenIntegrityLevel(HANDLE token, IntegrityLevel integrity_level) { michael@0: if (base::win::GetVersion() < base::win::VERSION_VISTA) michael@0: return ERROR_SUCCESS; michael@0: michael@0: const wchar_t* integrity_level_str = GetIntegrityLevelString(integrity_level); michael@0: if (!integrity_level_str) { michael@0: // No mandatory level specified, we don't change it. michael@0: return ERROR_SUCCESS; michael@0: } michael@0: michael@0: PSID integrity_sid = NULL; michael@0: if (!::ConvertStringSidToSid(integrity_level_str, &integrity_sid)) michael@0: return ::GetLastError(); michael@0: michael@0: TOKEN_MANDATORY_LABEL label = {0}; michael@0: label.Label.Attributes = SE_GROUP_INTEGRITY; michael@0: label.Label.Sid = integrity_sid; michael@0: michael@0: DWORD size = sizeof(TOKEN_MANDATORY_LABEL) + ::GetLengthSid(integrity_sid); michael@0: BOOL result = ::SetTokenInformation(token, TokenIntegrityLevel, &label, michael@0: size); michael@0: ::LocalFree(integrity_sid); michael@0: michael@0: return result ? ERROR_SUCCESS : ::GetLastError(); michael@0: } michael@0: michael@0: DWORD SetProcessIntegrityLevel(IntegrityLevel integrity_level) { michael@0: if (base::win::GetVersion() < base::win::VERSION_VISTA) michael@0: return ERROR_SUCCESS; michael@0: michael@0: // We don't check for an invalid level here because we'll just let it michael@0: // fail on the SetTokenIntegrityLevel call later on. michael@0: if (integrity_level == INTEGRITY_LEVEL_LAST) { michael@0: // No mandatory level specified, we don't change it. michael@0: return ERROR_SUCCESS; michael@0: } michael@0: michael@0: HANDLE token_handle; michael@0: if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT, michael@0: &token_handle)) michael@0: return ::GetLastError(); michael@0: michael@0: base::win::ScopedHandle token(token_handle); michael@0: michael@0: return SetTokenIntegrityLevel(token.Get(), integrity_level); michael@0: } michael@0: michael@0: } // namespace sandbox