diff options
Diffstat (limited to 'security/sandbox/chromium/sandbox/win/src/target_process.cc')
-rw-r--r-- | security/sandbox/chromium/sandbox/win/src/target_process.cc | 393 |
1 files changed, 393 insertions, 0 deletions
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 |