1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "sandbox/win/src/process_thread_dispatcher.h"
6
7#include "base/basictypes.h"
8#include "base/logging.h"
9#include "sandbox/win/src/crosscall_client.h"
10#include "sandbox/win/src/interception.h"
11#include "sandbox/win/src/interceptors.h"
12#include "sandbox/win/src/ipc_tags.h"
13#include "sandbox/win/src/policy_broker.h"
14#include "sandbox/win/src/policy_params.h"
15#include "sandbox/win/src/process_thread_interception.h"
16#include "sandbox/win/src/process_thread_policy.h"
17#include "sandbox/win/src/sandbox.h"
18
19namespace {
20
21// Extracts the application name from a command line.
22//
23// The application name is the first element of the command line. If
24// there is no quotes, the first element is delimited by the first space.
25// If there are quotes, the first element is delimited by the quotes.
26//
27// The create process call is smarter than us. It tries really hard to launch
28// the process even if the command line is wrong. For example:
29// "c:\program files\test param" will first try to launch c:\program.exe then
30// c:\program files\test.exe. We don't do that, we stop after at the first
31// space when there is no quotes.
32base::string16 GetPathFromCmdLine(const base::string16 &cmd_line) {
33  base::string16 exe_name;
34  // Check if it starts with '"'.
35  if (cmd_line[0] == L'\"') {
36    // Find the position of the second '"', this terminates the path.
37    base::string16::size_type pos = cmd_line.find(L'\"', 1);
38    if (base::string16::npos == pos)
39      return cmd_line;
40    exe_name = cmd_line.substr(1, pos - 1);
41  } else {
42    // There is no '"', that means that the appname is terminated at the
43    // first space.
44    base::string16::size_type pos = cmd_line.find(L' ');
45    if (base::string16::npos == pos) {
46      // There is no space, the cmd_line contains only the app_name
47      exe_name = cmd_line;
48    } else {
49      exe_name = cmd_line.substr(0, pos);
50    }
51  }
52
53  return exe_name;
54}
55
56// Returns true is the path in parameter is relative. False if it's
57// absolute.
58bool IsPathRelative(const base::string16 &path) {
59  // A path is Relative if it's not a UNC path beginnning with \\ or a
60  // path beginning with a drive. (i.e. X:\)
61  if (path.find(L"\\\\") == 0 || path.find(L":\\") == 1)
62    return false;
63  return true;
64}
65
66// Converts a relative path to an absolute path.
67bool ConvertToAbsolutePath(const base::string16& child_current_directory,
68                           bool use_env_path, base::string16 *path) {
69  wchar_t file_buffer[MAX_PATH];
70  wchar_t *file_part = NULL;
71
72  // Here we should start by looking at the path where the child application was
73  // started. We don't have this information yet.
74  DWORD result = 0;
75  if (use_env_path) {
76    // Try with the complete path
77    result = ::SearchPath(NULL, path->c_str(), NULL, MAX_PATH, file_buffer,
78                          &file_part);
79  }
80
81  if (0 == result) {
82    // Try with the current directory of the child
83    result = ::SearchPath(child_current_directory.c_str(), path->c_str(), NULL,
84                          MAX_PATH, file_buffer, &file_part);
85  }
86
87  if (0 == result || result >= MAX_PATH)
88    return false;
89
90  *path = file_buffer;
91  return true;
92}
93
94}  // namespace
95namespace sandbox {
96
97ThreadProcessDispatcher::ThreadProcessDispatcher(PolicyBase* policy_base)
98    : policy_base_(policy_base) {
99  static const IPCCall open_thread = {
100    {IPC_NTOPENTHREAD_TAG, ULONG_TYPE, ULONG_TYPE},
101    reinterpret_cast<CallbackGeneric>(
102        &ThreadProcessDispatcher::NtOpenThread)
103  };
104
105  static const IPCCall open_process = {
106    {IPC_NTOPENPROCESS_TAG, ULONG_TYPE, ULONG_TYPE},
107    reinterpret_cast<CallbackGeneric>(
108        &ThreadProcessDispatcher::NtOpenProcess)
109  };
110
111  static const IPCCall process_token = {
112    {IPC_NTOPENPROCESSTOKEN_TAG, VOIDPTR_TYPE, ULONG_TYPE},
113    reinterpret_cast<CallbackGeneric>(
114        &ThreadProcessDispatcher::NtOpenProcessToken)
115  };
116
117  static const IPCCall process_tokenex = {
118    {IPC_NTOPENPROCESSTOKENEX_TAG, VOIDPTR_TYPE, ULONG_TYPE, ULONG_TYPE},
119    reinterpret_cast<CallbackGeneric>(
120        &ThreadProcessDispatcher::NtOpenProcessTokenEx)
121  };
122
123  static const IPCCall create_params = {
124    {IPC_CREATEPROCESSW_TAG, WCHAR_TYPE, WCHAR_TYPE, WCHAR_TYPE, INOUTPTR_TYPE},
125    reinterpret_cast<CallbackGeneric>(
126        &ThreadProcessDispatcher::CreateProcessW)
127  };
128
129  ipc_calls_.push_back(open_thread);
130  ipc_calls_.push_back(open_process);
131  ipc_calls_.push_back(process_token);
132  ipc_calls_.push_back(process_tokenex);
133  ipc_calls_.push_back(create_params);
134}
135
136bool ThreadProcessDispatcher::SetupService(InterceptionManager* manager,
137                                           int service) {
138  switch (service) {
139    case IPC_NTOPENTHREAD_TAG:
140    case IPC_NTOPENPROCESS_TAG:
141    case IPC_NTOPENPROCESSTOKEN_TAG:
142    case IPC_NTOPENPROCESSTOKENEX_TAG:
143      // There is no explicit policy for these services.
144      NOTREACHED();
145      return false;
146
147    case IPC_CREATEPROCESSW_TAG:
148      return INTERCEPT_EAT(manager, kKerneldllName, CreateProcessW,
149                           CREATE_PROCESSW_ID, 44) &&
150             INTERCEPT_EAT(manager, L"kernel32.dll", CreateProcessA,
151                           CREATE_PROCESSA_ID, 44);
152
153    default:
154      return false;
155  }
156}
157
158bool ThreadProcessDispatcher::NtOpenThread(IPCInfo* ipc, DWORD desired_access,
159                                           DWORD thread_id) {
160  HANDLE handle;
161  NTSTATUS ret = ProcessPolicy::OpenThreadAction(*ipc->client_info,
162                                                 desired_access, thread_id,
163                                                 &handle);
164  ipc->return_info.nt_status = ret;
165  ipc->return_info.handle = handle;
166  return true;
167}
168
169bool ThreadProcessDispatcher::NtOpenProcess(IPCInfo* ipc, DWORD desired_access,
170                                            DWORD process_id) {
171  HANDLE handle;
172  NTSTATUS ret = ProcessPolicy::OpenProcessAction(*ipc->client_info,
173                                                  desired_access, process_id,
174                                                  &handle);
175  ipc->return_info.nt_status = ret;
176  ipc->return_info.handle = handle;
177  return true;
178}
179
180bool ThreadProcessDispatcher::NtOpenProcessToken(IPCInfo* ipc, HANDLE process,
181                                                 DWORD desired_access) {
182  HANDLE handle;
183  NTSTATUS ret = ProcessPolicy::OpenProcessTokenAction(*ipc->client_info,
184                                                       process, desired_access,
185                                                       &handle);
186  ipc->return_info.nt_status = ret;
187  ipc->return_info.handle = handle;
188  return true;
189}
190
191bool ThreadProcessDispatcher::NtOpenProcessTokenEx(IPCInfo* ipc, HANDLE process,
192                                                   DWORD desired_access,
193                                                   DWORD attributes) {
194  HANDLE handle;
195  NTSTATUS ret = ProcessPolicy::OpenProcessTokenExAction(*ipc->client_info,
196                                                         process,
197                                                         desired_access,
198                                                         attributes, &handle);
199  ipc->return_info.nt_status = ret;
200  ipc->return_info.handle = handle;
201  return true;
202}
203
204bool ThreadProcessDispatcher::CreateProcessW(IPCInfo* ipc, base::string16* name,
205                                             base::string16* cmd_line,
206                                             base::string16* cur_dir,
207                                             CountedBuffer* info) {
208  if (sizeof(PROCESS_INFORMATION) != info->Size())
209    return false;
210
211  // Check if there is an application name.
212  base::string16 exe_name;
213  if (!name->empty())
214    exe_name = *name;
215  else
216    exe_name = GetPathFromCmdLine(*cmd_line);
217
218  if (IsPathRelative(exe_name)) {
219    if (!ConvertToAbsolutePath(*cur_dir, name->empty(), &exe_name)) {
220      // Cannot find the path. Maybe the file does not exist.
221      ipc->return_info.win32_result = ERROR_FILE_NOT_FOUND;
222      return true;
223    }
224  }
225
226  const wchar_t* const_exe_name = exe_name.c_str();
227  CountedParameterSet<NameBased> params;
228  params[NameBased::NAME] = ParamPickerMake(const_exe_name);
229
230  EvalResult eval = policy_base_->EvalPolicy(IPC_CREATEPROCESSW_TAG,
231                                             params.GetBase());
232
233  PROCESS_INFORMATION* proc_info =
234      reinterpret_cast<PROCESS_INFORMATION*>(info->Buffer());
235  // Here we force the app_name to be the one we used for the policy lookup.
236  // If our logic was wrong, at least we wont allow create a random process.
237  DWORD ret = ProcessPolicy::CreateProcessWAction(eval, *ipc->client_info,
238                                                  exe_name, *cmd_line,
239                                                  proc_info);
240
241  ipc->return_info.win32_result = ret;
242  return true;
243}
244
245}  // namespace sandbox
246