| // Copyright (c) 2013 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/process_thread_interception.h" |
| |
| #include <stdint.h> |
| |
| #include "base/win/windows_version.h" |
| #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 { |
| |
| SANDBOX_INTERCEPT NtExports g_nt; |
| |
| // 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; |
| |
| do { |
| if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) |
| break; |
| if (!client_id) |
| break; |
| |
| uint32_t thread_id = 0; |
| bool should_break = false; |
| __try { |
| // We support only the calls for the current process |
| if (client_id->UniqueProcess) |
| should_break = true; |
| |
| // Object attributes should be nullptr or empty. |
| if (!should_break && object_attributes) { |
| if (object_attributes->Attributes || object_attributes->ObjectName || |
| object_attributes->RootDirectory || |
| object_attributes->SecurityDescriptor || |
| object_attributes->SecurityQualityOfService) { |
| should_break = true; |
| } |
| } |
| |
| thread_id = static_cast<uint32_t>( |
| reinterpret_cast<ULONG_PTR>(client_id->UniqueThread)); |
| } __except (EXCEPTION_EXECUTE_HANDLER) { |
| break; |
| } |
| |
| if (should_break) |
| break; |
| |
| if (!ValidParameter(thread, sizeof(HANDLE), WRITE)) |
| break; |
| |
| void* memory = GetGlobalIPCMemory(); |
| if (!memory) |
| break; |
| |
| SharedMemIPCClient ipc(memory); |
| CrossCallReturn answer = {0}; |
| ResultCode code = CrossCall(ipc, IPC_NTOPENTHREAD_TAG, desired_access, |
| thread_id, &answer); |
| if (SBOX_ALL_OK != code) |
| break; |
| |
| 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. |
| break; |
| |
| __try { |
| // Write the output parameters. |
| *thread = answer.handle; |
| } __except (EXCEPTION_EXECUTE_HANDLER) { |
| break; |
| } |
| |
| return answer.nt_status; |
| } while (false); |
| |
| return status; |
| } |
| |
| // Hooks NtOpenProcess and proxy the call to the broker if it's trying to |
| // open 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; |
| |
| do { |
| if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) |
| break; |
| if (!client_id) |
| break; |
| |
| uint32_t process_id = 0; |
| bool should_break = false; |
| __try { |
| // Object attributes should be nullptr or empty. |
| if (!should_break && object_attributes) { |
| if (object_attributes->Attributes || object_attributes->ObjectName || |
| object_attributes->RootDirectory || |
| object_attributes->SecurityDescriptor || |
| object_attributes->SecurityQualityOfService) { |
| should_break = true; |
| } |
| } |
| |
| process_id = static_cast<uint32_t>( |
| reinterpret_cast<ULONG_PTR>(client_id->UniqueProcess)); |
| } __except (EXCEPTION_EXECUTE_HANDLER) { |
| break; |
| } |
| |
| if (should_break) |
| break; |
| |
| if (!ValidParameter(process, sizeof(HANDLE), WRITE)) |
| break; |
| |
| void* memory = GetGlobalIPCMemory(); |
| if (!memory) |
| break; |
| |
| SharedMemIPCClient ipc(memory); |
| CrossCallReturn answer = {0}; |
| ResultCode code = CrossCall(ipc, IPC_NTOPENPROCESS_TAG, desired_access, |
| process_id, &answer); |
| if (SBOX_ALL_OK != code) |
| break; |
| |
| if (!NT_SUCCESS(answer.nt_status)) |
| return answer.nt_status; |
| |
| __try { |
| // Write the output parameters. |
| *process = answer.handle; |
| } __except (EXCEPTION_EXECUTE_HANDLER) { |
| break; |
| } |
| |
| return answer.nt_status; |
| } while (false); |
| |
| return status; |
| } |
| |
| NTSTATUS WINAPI |
| TargetNtOpenProcessToken(NtOpenProcessTokenFunction orig_OpenProcessToken, |
| HANDLE process, |
| ACCESS_MASK desired_access, |
| PHANDLE token) { |
| NTSTATUS status = orig_OpenProcessToken(process, desired_access, token); |
| if (NT_SUCCESS(status)) |
| return status; |
| |
| do { |
| if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) |
| break; |
| |
| if (CURRENT_PROCESS != process) |
| break; |
| |
| if (!ValidParameter(token, sizeof(HANDLE), WRITE)) |
| break; |
| |
| void* memory = GetGlobalIPCMemory(); |
| if (!memory) |
| break; |
| |
| SharedMemIPCClient ipc(memory); |
| CrossCallReturn answer = {0}; |
| ResultCode code = CrossCall(ipc, IPC_NTOPENPROCESSTOKEN_TAG, process, |
| desired_access, &answer); |
| if (SBOX_ALL_OK != code) |
| break; |
| |
| if (!NT_SUCCESS(answer.nt_status)) |
| return answer.nt_status; |
| |
| __try { |
| // Write the output parameters. |
| *token = answer.handle; |
| } __except (EXCEPTION_EXECUTE_HANDLER) { |
| break; |
| } |
| |
| return answer.nt_status; |
| } while (false); |
| |
| return status; |
| } |
| |
| NTSTATUS WINAPI |
| TargetNtOpenProcessTokenEx(NtOpenProcessTokenExFunction orig_OpenProcessTokenEx, |
| HANDLE process, |
| ACCESS_MASK desired_access, |
| ULONG handle_attributes, |
| PHANDLE token) { |
| NTSTATUS status = orig_OpenProcessTokenEx(process, desired_access, |
| handle_attributes, token); |
| if (NT_SUCCESS(status)) |
| return status; |
| |
| do { |
| if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) |
| break; |
| |
| if (CURRENT_PROCESS != process) |
| break; |
| |
| if (!ValidParameter(token, sizeof(HANDLE), WRITE)) |
| break; |
| |
| void* memory = GetGlobalIPCMemory(); |
| if (!memory) |
| break; |
| |
| SharedMemIPCClient ipc(memory); |
| CrossCallReturn answer = {0}; |
| ResultCode code = CrossCall(ipc, IPC_NTOPENPROCESSTOKENEX_TAG, process, |
| desired_access, handle_attributes, &answer); |
| if (SBOX_ALL_OK != code) |
| break; |
| |
| if (!NT_SUCCESS(answer.nt_status)) |
| return answer.nt_status; |
| |
| __try { |
| // Write the output parameters. |
| *token = answer.handle; |
| } __except (EXCEPTION_EXECUTE_HANDLER) { |
| break; |
| } |
| |
| return answer.nt_status; |
| } while (false); |
| |
| return status; |
| } |
| |
| BOOL WINAPI TargetCreateProcessW(CreateProcessWFunction orig_CreateProcessW, |
| LPCWSTR application_name, |
| LPWSTR command_line, |
| LPSECURITY_ATTRIBUTES process_attributes, |
| LPSECURITY_ATTRIBUTES thread_attributes, |
| BOOL inherit_handles, |
| DWORD flags, |
| LPVOID environment, |
| LPCWSTR current_directory, |
| LPSTARTUPINFOW startup_info, |
| LPPROCESS_INFORMATION process_information) { |
| if (SandboxFactory::GetTargetServices()->GetState()->IsCsrssConnected() && |
| orig_CreateProcessW(application_name, command_line, process_attributes, |
| thread_attributes, inherit_handles, flags, |
| environment, current_directory, startup_info, |
| process_information)) { |
| return true; |
| } |
| |
| // We don't trust that the IPC can work this early. |
| if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) |
| return false; |
| |
| // Don't call GetLastError before InitCalled() succeeds because kernel32 may |
| // not be mapped yet. |
| DWORD original_error = ::GetLastError(); |
| |
| do { |
| if (!ValidParameter(process_information, sizeof(PROCESS_INFORMATION), |
| WRITE)) |
| break; |
| |
| void* memory = GetGlobalIPCMemory(); |
| if (!memory) |
| break; |
| |
| const wchar_t* cur_dir = nullptr; |
| |
| wchar_t this_current_directory[MAX_PATH]; |
| DWORD result = ::GetCurrentDirectory(MAX_PATH, this_current_directory); |
| if (0 != result && result < MAX_PATH) |
| cur_dir = this_current_directory; |
| |
| SharedMemIPCClient ipc(memory); |
| CrossCallReturn answer = {0}; |
| |
| InOutCountedBuffer proc_info(process_information, |
| sizeof(PROCESS_INFORMATION)); |
| |
| ResultCode code = |
| CrossCall(ipc, IPC_CREATEPROCESSW_TAG, application_name, command_line, |
| cur_dir, current_directory, proc_info, &answer); |
| if (SBOX_ALL_OK != code) |
| break; |
| |
| ::SetLastError(answer.win32_result); |
| if (ERROR_SUCCESS != answer.win32_result) |
| return false; |
| |
| return true; |
| } while (false); |
| |
| ::SetLastError(original_error); |
| return false; |
| } |
| |
| BOOL WINAPI TargetCreateProcessA(CreateProcessAFunction orig_CreateProcessA, |
| LPCSTR application_name, |
| LPSTR command_line, |
| LPSECURITY_ATTRIBUTES process_attributes, |
| LPSECURITY_ATTRIBUTES thread_attributes, |
| BOOL inherit_handles, |
| DWORD flags, |
| LPVOID environment, |
| LPCSTR current_directory, |
| LPSTARTUPINFOA startup_info, |
| LPPROCESS_INFORMATION process_information) { |
| if (SandboxFactory::GetTargetServices()->GetState()->IsCsrssConnected() && |
| orig_CreateProcessA(application_name, command_line, process_attributes, |
| thread_attributes, inherit_handles, flags, |
| environment, current_directory, startup_info, |
| process_information)) { |
| return true; |
| } |
| |
| // We don't trust that the IPC can work this early. |
| if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) |
| return false; |
| |
| // Don't call GetLastError before InitCalled() succeeds because kernel32 may |
| // not be mapped yet. |
| DWORD original_error = ::GetLastError(); |
| |
| do { |
| if (!ValidParameter(process_information, sizeof(PROCESS_INFORMATION), |
| WRITE)) |
| break; |
| |
| void* memory = GetGlobalIPCMemory(); |
| if (!memory) |
| break; |
| |
| // Convert the input params to unicode. |
| UNICODE_STRING* cmd_unicode = nullptr; |
| UNICODE_STRING* app_unicode = nullptr; |
| UNICODE_STRING* cwd_unicode = nullptr; |
| if (command_line) { |
| cmd_unicode = AnsiToUnicode(command_line); |
| if (!cmd_unicode) |
| break; |
| } |
| |
| if (application_name) { |
| app_unicode = AnsiToUnicode(application_name); |
| if (!app_unicode) { |
| operator delete(cmd_unicode, NT_ALLOC); |
| break; |
| } |
| } |
| |
| if (current_directory) { |
| cwd_unicode = AnsiToUnicode(current_directory); |
| if (!cwd_unicode) { |
| operator delete(cmd_unicode, NT_ALLOC); |
| operator delete(app_unicode, NT_ALLOC); |
| break; |
| } |
| } |
| |
| const wchar_t* cmd_line = cmd_unicode ? cmd_unicode->Buffer : nullptr; |
| const wchar_t* app_name = app_unicode ? app_unicode->Buffer : nullptr; |
| const wchar_t* cwd = cwd_unicode ? cwd_unicode->Buffer : nullptr; |
| const wchar_t* cur_dir = nullptr; |
| |
| wchar_t target_current_directory[MAX_PATH]; |
| DWORD result = ::GetCurrentDirectory(MAX_PATH, target_current_directory); |
| if (0 != result && result < MAX_PATH) |
| cur_dir = target_current_directory; |
| |
| SharedMemIPCClient ipc(memory); |
| CrossCallReturn answer = {0}; |
| |
| InOutCountedBuffer proc_info(process_information, |
| sizeof(PROCESS_INFORMATION)); |
| |
| ResultCode code = CrossCall(ipc, IPC_CREATEPROCESSW_TAG, app_name, cmd_line, |
| cur_dir, cwd, proc_info, &answer); |
| |
| operator delete(cmd_unicode, NT_ALLOC); |
| operator delete(app_unicode, NT_ALLOC); |
| operator delete(cwd_unicode, NT_ALLOC); |
| |
| if (SBOX_ALL_OK != code) |
| break; |
| |
| ::SetLastError(answer.win32_result); |
| if (ERROR_SUCCESS != answer.win32_result) |
| return false; |
| |
| return true; |
| } while (false); |
| |
| ::SetLastError(original_error); |
| return false; |
| } |
| |
| 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, IPC_CREATETHREAD_TAG, |
| 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 |