// Copyright 2013 The Chromium Authors
// 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 <optional>
#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 {
NTSTATUS DuplicateObject(HANDLE handle,
ACCESS_MASK desired_access,
PHANDLE out_handle) {
if (desired_access & MAXIMUM_ALLOWED) {
desired_access |= GENERIC_ALL;
}
return GetNtExports()->DuplicateObject(CURRENT_PROCESS, handle,
CURRENT_PROCESS, out_handle,
desired_access, 0, 0);
}
template <typename T>
std::optional<T> CaptureParameter(const T* parameter) {
if (parameter) {
__try {
return *parameter;
} __except (EXCEPTION_EXECUTE_HANDLER) {
}
}
return std::nullopt;
}
bool ValidObjectAttributes(const OBJECT_ATTRIBUTES* object_attributes) {
if (!object_attributes) {
return true;
}
std::optional<OBJECT_ATTRIBUTES> valid_obj_attr =
CaptureParameter(object_attributes);
return valid_obj_attr.has_value() && !valid_obj_attr->Attributes &&
!valid_obj_attr->ObjectName && !valid_obj_attr->RootDirectory &&
!valid_obj_attr->SecurityDescriptor &&
!valid_obj_attr->SecurityQualityOfService;
}
NTSTATUS CallNtOpenProcessTokenEx(NTSTATUS status,
HANDLE process,
ACCESS_MASK desired_access,
ULONG handle_attributes,
PHANDLE token) {
if (NT_SUCCESS(status)) {
return status;
}
if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) {
return status;
}
if (CURRENT_PROCESS != process ||
!ValidParameter(token, sizeof(HANDLE), WRITE)) {
return status;
}
void* memory = GetGlobalIPCMemory();
if (!memory) {
return status;
}
SharedMemIPCClient ipc(memory);
CrossCallReturn answer = {0};
ResultCode code = CrossCall(ipc, IpcTag::NTOPENPROCESSTOKENEX, process,
desired_access, handle_attributes, &answer);
if (SBOX_ALL_OK != code) {
return status;
}
if (!NT_SUCCESS(answer.nt_status)) {
return answer.nt_status;
}
__try {
// Write the output parameters.
*token = answer.handle;
} __except (EXCEPTION_EXECUTE_HANDLER) {
return status;
}
return answer.nt_status;
}
} // namespace
// 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;
}
if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) {
return status;
}
if (!ValidParameter(thread, sizeof(HANDLE), WRITE) ||
!ValidObjectAttributes(object_attributes)) {
return status;
}
std::optional<CLIENT_ID> valid_client_id = CaptureParameter(client_id);
if (!valid_client_id.has_value() || valid_client_id->UniqueProcess) {
return status;
}
if (valid_client_id->UniqueThread == GetCurrentClientId().UniqueThread) {
return DuplicateObject(CURRENT_THREAD, desired_access, thread);
}
void* memory = GetGlobalIPCMemory();
if (!memory) {
return status;
}
SharedMemIPCClient ipc(memory);
CrossCallReturn answer = {0};
ResultCode code = CrossCall(ipc, IpcTag::NTOPENTHREAD, desired_access,
static_cast<uint32_t>(reinterpret_cast<ULONG_PTR>(
valid_client_id->UniqueThread)),
&answer);
if (SBOX_ALL_OK != code) {
return status;
}
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.
return status;
}
__try {
// Write the output parameters.
*thread = answer.handle;
} __except (EXCEPTION_EXECUTE_HANDLER) {
return status;
}
return answer.nt_status;
}
// Hooks NtOpenProcess and duplicates the current process handle if opening 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;
}
if (!ValidObjectAttributes(object_attributes)) {
return status;
}
std::optional<CLIENT_ID> valid_client_id = CaptureParameter(client_id);
if (!valid_client_id.has_value() ||
valid_client_id->UniqueProcess != GetCurrentClientId().UniqueProcess) {
return status;
}
return DuplicateObject(CURRENT_PROCESS, desired_access, process);
}
NTSTATUS WINAPI
TargetNtOpenProcessToken(NtOpenProcessTokenFunction orig_OpenProcessToken,
HANDLE process,
ACCESS_MASK desired_access,
PHANDLE token) {
// NtOpenProcessToken is just NtOpenProcessTokenEx with handle_attributes set
// to 0.
return CallNtOpenProcessTokenEx(
orig_OpenProcessToken(process, desired_access, token), process,
desired_access, 0, token);
}
NTSTATUS WINAPI
TargetNtOpenProcessTokenEx(NtOpenProcessTokenExFunction orig_OpenProcessTokenEx,
HANDLE process,
ACCESS_MASK desired_access,
ULONG handle_attributes,
PHANDLE token) {
return CallNtOpenProcessTokenEx(
orig_OpenProcessTokenEx(process, desired_access, handle_attributes,
token),
process, desired_access, handle_attributes, token);
}
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