// 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_dispatcher.h" #include "base/basictypes.h" #include "base/logging.h" #include "sandbox/win/src/crosscall_client.h" #include "sandbox/win/src/interception.h" #include "sandbox/win/src/interceptors.h" #include "sandbox/win/src/ipc_tags.h" #include "sandbox/win/src/policy_broker.h" #include "sandbox/win/src/policy_params.h" #include "sandbox/win/src/process_thread_interception.h" #include "sandbox/win/src/process_thread_policy.h" #include "sandbox/win/src/sandbox.h" namespace { // Extracts the application name from a command line. // // The application name is the first element of the command line. If // there is no quotes, the first element is delimited by the first space. // If there are quotes, the first element is delimited by the quotes. // // The create process call is smarter than us. It tries really hard to launch // the process even if the command line is wrong. For example: // "c:\program files\test param" will first try to launch c:\program.exe then // c:\program files\test.exe. We don't do that, we stop after at the first // space when there is no quotes. base::string16 GetPathFromCmdLine(const base::string16 &cmd_line) { base::string16 exe_name; // Check if it starts with '"'. if (cmd_line[0] == L'\"') { // Find the position of the second '"', this terminates the path. base::string16::size_type pos = cmd_line.find(L'\"', 1); if (base::string16::npos == pos) return cmd_line; exe_name = cmd_line.substr(1, pos - 1); } else { // There is no '"', that means that the appname is terminated at the // first space. base::string16::size_type pos = cmd_line.find(L' '); if (base::string16::npos == pos) { // There is no space, the cmd_line contains only the app_name exe_name = cmd_line; } else { exe_name = cmd_line.substr(0, pos); } } return exe_name; } // Returns true is the path in parameter is relative. False if it's // absolute. bool IsPathRelative(const base::string16 &path) { // A path is Relative if it's not a UNC path beginnning with \\ or a // path beginning with a drive. (i.e. X:\) if (path.find(L"\\\\") == 0 || path.find(L":\\") == 1) return false; return true; } // Converts a relative path to an absolute path. bool ConvertToAbsolutePath(const base::string16& child_current_directory, bool use_env_path, base::string16 *path) { wchar_t file_buffer[MAX_PATH]; wchar_t *file_part = NULL; // Here we should start by looking at the path where the child application was // started. We don't have this information yet. DWORD result = 0; if (use_env_path) { // Try with the complete path result = ::SearchPath(NULL, path->c_str(), NULL, MAX_PATH, file_buffer, &file_part); } if (0 == result) { // Try with the current directory of the child result = ::SearchPath(child_current_directory.c_str(), path->c_str(), NULL, MAX_PATH, file_buffer, &file_part); } if (0 == result || result >= MAX_PATH) return false; *path = file_buffer; return true; } } // namespace namespace sandbox { ThreadProcessDispatcher::ThreadProcessDispatcher(PolicyBase* policy_base) : policy_base_(policy_base) { static const IPCCall open_thread = { {IPC_NTOPENTHREAD_TAG, ULONG_TYPE, ULONG_TYPE}, reinterpret_cast( &ThreadProcessDispatcher::NtOpenThread) }; static const IPCCall open_process = { {IPC_NTOPENPROCESS_TAG, ULONG_TYPE, ULONG_TYPE}, reinterpret_cast( &ThreadProcessDispatcher::NtOpenProcess) }; static const IPCCall process_token = { {IPC_NTOPENPROCESSTOKEN_TAG, VOIDPTR_TYPE, ULONG_TYPE}, reinterpret_cast( &ThreadProcessDispatcher::NtOpenProcessToken) }; static const IPCCall process_tokenex = { {IPC_NTOPENPROCESSTOKENEX_TAG, VOIDPTR_TYPE, ULONG_TYPE, ULONG_TYPE}, reinterpret_cast( &ThreadProcessDispatcher::NtOpenProcessTokenEx) }; static const IPCCall create_params = { {IPC_CREATEPROCESSW_TAG, WCHAR_TYPE, WCHAR_TYPE, WCHAR_TYPE, INOUTPTR_TYPE}, reinterpret_cast( &ThreadProcessDispatcher::CreateProcessW) }; ipc_calls_.push_back(open_thread); ipc_calls_.push_back(open_process); ipc_calls_.push_back(process_token); ipc_calls_.push_back(process_tokenex); ipc_calls_.push_back(create_params); } bool ThreadProcessDispatcher::SetupService(InterceptionManager* manager, int service) { switch (service) { case IPC_NTOPENTHREAD_TAG: case IPC_NTOPENPROCESS_TAG: case IPC_NTOPENPROCESSTOKEN_TAG: case IPC_NTOPENPROCESSTOKENEX_TAG: // There is no explicit policy for these services. NOTREACHED(); return false; case IPC_CREATEPROCESSW_TAG: return INTERCEPT_EAT(manager, kKerneldllName, CreateProcessW, CREATE_PROCESSW_ID, 44) && INTERCEPT_EAT(manager, L"kernel32.dll", CreateProcessA, CREATE_PROCESSA_ID, 44); default: return false; } } bool ThreadProcessDispatcher::NtOpenThread(IPCInfo* ipc, DWORD desired_access, DWORD thread_id) { HANDLE handle; NTSTATUS ret = ProcessPolicy::OpenThreadAction(*ipc->client_info, desired_access, thread_id, &handle); ipc->return_info.nt_status = ret; ipc->return_info.handle = handle; return true; } bool ThreadProcessDispatcher::NtOpenProcess(IPCInfo* ipc, DWORD desired_access, DWORD process_id) { HANDLE handle; NTSTATUS ret = ProcessPolicy::OpenProcessAction(*ipc->client_info, desired_access, process_id, &handle); ipc->return_info.nt_status = ret; ipc->return_info.handle = handle; return true; } bool ThreadProcessDispatcher::NtOpenProcessToken(IPCInfo* ipc, HANDLE process, DWORD desired_access) { HANDLE handle; NTSTATUS ret = ProcessPolicy::OpenProcessTokenAction(*ipc->client_info, process, desired_access, &handle); ipc->return_info.nt_status = ret; ipc->return_info.handle = handle; return true; } bool ThreadProcessDispatcher::NtOpenProcessTokenEx(IPCInfo* ipc, HANDLE process, DWORD desired_access, DWORD attributes) { HANDLE handle; NTSTATUS ret = ProcessPolicy::OpenProcessTokenExAction(*ipc->client_info, process, desired_access, attributes, &handle); ipc->return_info.nt_status = ret; ipc->return_info.handle = handle; return true; } bool ThreadProcessDispatcher::CreateProcessW(IPCInfo* ipc, base::string16* name, base::string16* cmd_line, base::string16* cur_dir, CountedBuffer* info) { if (sizeof(PROCESS_INFORMATION) != info->Size()) return false; // Check if there is an application name. base::string16 exe_name; if (!name->empty()) exe_name = *name; else exe_name = GetPathFromCmdLine(*cmd_line); if (IsPathRelative(exe_name)) { if (!ConvertToAbsolutePath(*cur_dir, name->empty(), &exe_name)) { // Cannot find the path. Maybe the file does not exist. ipc->return_info.win32_result = ERROR_FILE_NOT_FOUND; return true; } } const wchar_t* const_exe_name = exe_name.c_str(); CountedParameterSet params; params[NameBased::NAME] = ParamPickerMake(const_exe_name); EvalResult eval = policy_base_->EvalPolicy(IPC_CREATEPROCESSW_TAG, params.GetBase()); PROCESS_INFORMATION* proc_info = reinterpret_cast(info->Buffer()); // Here we force the app_name to be the one we used for the policy lookup. // If our logic was wrong, at least we wont allow create a random process. DWORD ret = ProcessPolicy::CreateProcessWAction(eval, *ipc->client_info, exe_name, *cmd_line, proc_info); ipc->return_info.win32_result = ret; return true; } } // namespace sandbox