// 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 #include #include #include "sandbox/win/src/restricted_token_utils.h" #include "base/logging.h" #include "base/win/scoped_handle.h" #include "base/win/scoped_process_information.h" #include "base/win/windows_version.h" #include "sandbox/win/src/job.h" #include "sandbox/win/src/restricted_token.h" #include "sandbox/win/src/security_level.h" #include "sandbox/win/src/sid.h" namespace sandbox { DWORD CreateRestrictedToken(HANDLE *token_handle, TokenLevel security_level, IntegrityLevel integrity_level, TokenType token_type) { if (!token_handle) return ERROR_BAD_ARGUMENTS; RestrictedToken restricted_token; restricted_token.Init(NULL); // Initialized with the current process token std::vector privilege_exceptions; std::vector 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; 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_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); restricted_token.AddRestrictingSid(WinBuiltinUsersSid); restricted_token.AddRestrictingSid(WinWorldSid); restricted_token.AddRestrictingSid(WinRestrictedCodeSid); restricted_token.AddRestrictingSidCurrentUser(); restricted_token.AddRestrictingSidLogonSession(); 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); restricted_token.AddRestrictingSid(WinBuiltinUsersSid); restricted_token.AddRestrictingSid(WinWorldSid); restricted_token.AddRestrictingSid(WinRestrictedCodeSid); // 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. if (base::win::GetVersion() >= base::win::VERSION_VISTA) restricted_token.AddRestrictingSidLogonSession(); break; } case USER_RESTRICTED: { privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); restricted_token.AddUserSidForDenyOnly(); restricted_token.AddRestrictingSid(WinRestrictedCodeSid); break; } case USER_LOCKDOWN: { restricted_token.AddUserSidForDenyOnly(); restricted_token.AddRestrictingSid(WinNullSid); 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.GetRestrictedTokenHandle(token_handle); break; } case IMPERSONATION: { err_code = restricted_token.GetRestrictedTokenHandleForImpersonation( token_handle); break; } default: { err_code = ERROR_BAD_ARGUMENTS; break; } } return err_code; } DWORD StartRestrictedProcessInJob(wchar_t *command_line, TokenLevel primary_level, TokenLevel impersonation_level, JobLevel job_level, HANDLE *const job_handle_ret) { Job job; DWORD err_code = job.Init(job_level, NULL, 0); if (ERROR_SUCCESS != err_code) return err_code; if (JOB_UNPROTECTED != job_level) { // Share the Desktop handle to be able to use MessageBox() in the sandboxed // application. err_code = job.UserHandleGrantAccess(GetDesktopWindow()); if (ERROR_SUCCESS != err_code) return err_code; } // Create the primary (restricted) token for the process HANDLE primary_token_handle = NULL; err_code = CreateRestrictedToken(&primary_token_handle, primary_level, INTEGRITY_LEVEL_LAST, PRIMARY); if (ERROR_SUCCESS != err_code) { return err_code; } base::win::ScopedHandle primary_token(primary_token_handle); // Create the impersonation token (restricted) to be able to start the // process. HANDLE impersonation_token_handle; err_code = CreateRestrictedToken(&impersonation_token_handle, impersonation_level, INTEGRITY_LEVEL_LAST, IMPERSONATION); if (ERROR_SUCCESS != err_code) { return err_code; } base::win::ScopedHandle impersonation_token(impersonation_token_handle); // Start the process STARTUPINFO startup_info = {0}; PROCESS_INFORMATION temp_process_info = {}; DWORD flags = CREATE_SUSPENDED; if (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; } if (!::CreateProcessAsUser(primary_token.Get(), NULL, // No application name. command_line, NULL, // No security attribute. NULL, // No thread attribute. FALSE, // Do not inherit handles. flags, NULL, // Use the environment of the caller. NULL, // Use current directory of the caller. &startup_info, &temp_process_info)) { return ::GetLastError(); } base::win::ScopedProcessInformation process_info(temp_process_info); // Change the token of the main thread of the new process for the // impersonation token with more rights. { HANDLE temp_thread = process_info.thread_handle(); if (!::SetThreadToken(&temp_thread, impersonation_token.Get())) { ::TerminateProcess(process_info.process_handle(), 0); // exit code return ::GetLastError(); } } err_code = job.AssignProcessToJob(process_info.process_handle()); if (ERROR_SUCCESS != err_code) { ::TerminateProcess(process_info.process_handle(), 0); // exit code return ::GetLastError(); } // Start the application ::ResumeThread(process_info.thread_handle()); (*job_handle_ret) = job.Detach(); return ERROR_SUCCESS; } 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. base::string16 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 = NULL; PACL sacl = NULL; BOOL sacl_present = FALSE; BOOL sacl_defaulted = FALSE; if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(), SDDL_REVISION, &sec_desc, NULL)) { if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl, &sacl_defaulted)) { error = ::SetSecurityInfo(handle, type, LABEL_SECURITY_INFORMATION, NULL, NULL, NULL, 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 NULL; } NOTREACHED(); return NULL; } DWORD SetTokenIntegrityLevel(HANDLE token, IntegrityLevel integrity_level) { if (base::win::GetVersion() < base::win::VERSION_VISTA) return ERROR_SUCCESS; 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 = NULL; if (!::ConvertStringSidToSid(integrity_level_str, &integrity_sid)) return ::GetLastError(); TOKEN_MANDATORY_LABEL label = {0}; 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); ::LocalFree(integrity_sid); return result ? ERROR_SUCCESS : ::GetLastError(); } DWORD SetProcessIntegrityLevel(IntegrityLevel integrity_level) { if (base::win::GetVersion() < base::win::VERSION_VISTA) return ERROR_SUCCESS; // 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); } } // namespace sandbox