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_interception.h"
6
7#include "sandbox/win/src/crosscall_client.h"
8#include "sandbox/win/src/ipc_tags.h"
9#include "sandbox/win/src/policy_params.h"
10#include "sandbox/win/src/policy_target.h"
11#include "sandbox/win/src/sandbox_factory.h"
12#include "sandbox/win/src/sandbox_nt_util.h"
13#include "sandbox/win/src/sharedmem_ipc_client.h"
14#include "sandbox/win/src/target_services.h"
15
16namespace sandbox {
17
18SANDBOX_INTERCEPT NtExports g_nt;
19
20// Hooks NtOpenThread and proxy the call to the broker if it's trying to
21// open a thread in the same process.
22NTSTATUS WINAPI TargetNtOpenThread(NtOpenThreadFunction orig_OpenThread,
23                                   PHANDLE thread, ACCESS_MASK desired_access,
24                                   POBJECT_ATTRIBUTES object_attributes,
25                                   PCLIENT_ID client_id) {
26  NTSTATUS status = orig_OpenThread(thread, desired_access, object_attributes,
27                                    client_id);
28  if (NT_SUCCESS(status))
29    return status;
30
31  do {
32    if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled())
33      break;
34    if (!client_id)
35      break;
36
37    uint32 thread_id = 0;
38    bool should_break = false;
39    __try {
40      // We support only the calls for the current process
41      if (NULL != client_id->UniqueProcess)
42        should_break = true;
43
44      // Object attributes should be NULL or empty.
45      if (!should_break && NULL != object_attributes) {
46        if (0 != object_attributes->Attributes ||
47            NULL != object_attributes->ObjectName ||
48            NULL != object_attributes->RootDirectory ||
49            NULL != object_attributes->SecurityDescriptor ||
50            NULL != object_attributes->SecurityQualityOfService) {
51          should_break = true;
52        }
53      }
54
55      thread_id = static_cast<uint32>(
56                      reinterpret_cast<ULONG_PTR>(client_id->UniqueThread));
57    } __except(EXCEPTION_EXECUTE_HANDLER) {
58      break;
59    }
60
61    if (should_break)
62      break;
63
64    if (!ValidParameter(thread, sizeof(HANDLE), WRITE))
65      break;
66
67    void* memory = GetGlobalIPCMemory();
68    if (NULL == memory)
69      break;
70
71    SharedMemIPCClient ipc(memory);
72    CrossCallReturn answer = {0};
73    ResultCode code = CrossCall(ipc, IPC_NTOPENTHREAD_TAG, desired_access,
74                                thread_id, &answer);
75    if (SBOX_ALL_OK != code)
76      break;
77
78    if (!NT_SUCCESS(answer.nt_status))
79      // The nt_status here is most likely STATUS_INVALID_CID because
80      // in the broker we set the process id in the CID (client ID) param
81      // to be the current process. If you try to open a thread from another
82      // process you will get this INVALID_CID error. On the other hand, if you
83      // try to open a thread in your own process, it should return success.
84      // We don't want to return STATUS_INVALID_CID here, so we return the
85      // return of the original open thread status, which is most likely
86      // STATUS_ACCESS_DENIED.
87      break;
88
89    __try {
90      // Write the output parameters.
91      *thread = answer.handle;
92    } __except(EXCEPTION_EXECUTE_HANDLER) {
93      break;
94    }
95
96    return answer.nt_status;
97  } while (false);
98
99  return status;
100}
101
102// Hooks NtOpenProcess and proxy the call to the broker if it's trying to
103// open the current process.
104NTSTATUS WINAPI TargetNtOpenProcess(NtOpenProcessFunction orig_OpenProcess,
105                                   PHANDLE process, ACCESS_MASK desired_access,
106                                   POBJECT_ATTRIBUTES object_attributes,
107                                   PCLIENT_ID client_id) {
108  NTSTATUS status = orig_OpenProcess(process, desired_access, object_attributes,
109                                     client_id);
110  if (NT_SUCCESS(status))
111    return status;
112
113  do {
114    if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled())
115      break;
116    if (!client_id)
117      break;
118
119    uint32 process_id = 0;
120    bool should_break = false;
121    __try {
122      // Object attributes should be NULL or empty.
123      if (!should_break && NULL != object_attributes) {
124        if (0 != object_attributes->Attributes ||
125            NULL != object_attributes->ObjectName ||
126            NULL != object_attributes->RootDirectory ||
127            NULL != object_attributes->SecurityDescriptor ||
128            NULL != object_attributes->SecurityQualityOfService) {
129          should_break = true;
130        }
131      }
132
133      process_id = static_cast<uint32>(
134                      reinterpret_cast<ULONG_PTR>(client_id->UniqueProcess));
135    } __except(EXCEPTION_EXECUTE_HANDLER) {
136      break;
137    }
138
139    if (should_break)
140      break;
141
142    if (!ValidParameter(process, sizeof(HANDLE), WRITE))
143      break;
144
145    void* memory = GetGlobalIPCMemory();
146    if (NULL == memory)
147      break;
148
149    SharedMemIPCClient ipc(memory);
150    CrossCallReturn answer = {0};
151    ResultCode code = CrossCall(ipc, IPC_NTOPENPROCESS_TAG, desired_access,
152                                process_id, &answer);
153    if (SBOX_ALL_OK != code)
154      break;
155
156    if (!NT_SUCCESS(answer.nt_status))
157      return answer.nt_status;
158
159    __try {
160      // Write the output parameters.
161      *process = answer.handle;
162    } __except(EXCEPTION_EXECUTE_HANDLER) {
163      break;
164    }
165
166    return answer.nt_status;
167  } while (false);
168
169  return status;
170}
171
172
173NTSTATUS WINAPI TargetNtOpenProcessToken(
174    NtOpenProcessTokenFunction orig_OpenProcessToken, HANDLE process,
175    ACCESS_MASK desired_access, PHANDLE token) {
176  NTSTATUS status = orig_OpenProcessToken(process, desired_access, token);
177  if (NT_SUCCESS(status))
178    return status;
179
180  do {
181    if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled())
182      break;
183
184    if (CURRENT_PROCESS != process)
185      break;
186
187    if (!ValidParameter(token, sizeof(HANDLE), WRITE))
188      break;
189
190    void* memory = GetGlobalIPCMemory();
191    if (NULL == memory)
192      break;
193
194    SharedMemIPCClient ipc(memory);
195    CrossCallReturn answer = {0};
196    ResultCode code = CrossCall(ipc, IPC_NTOPENPROCESSTOKEN_TAG, process,
197                                desired_access, &answer);
198    if (SBOX_ALL_OK != code)
199      break;
200
201    if (!NT_SUCCESS(answer.nt_status))
202      return answer.nt_status;
203
204    __try {
205      // Write the output parameters.
206      *token = answer.handle;
207    } __except(EXCEPTION_EXECUTE_HANDLER) {
208      break;
209    }
210
211    return answer.nt_status;
212  } while (false);
213
214  return status;
215}
216
217NTSTATUS WINAPI TargetNtOpenProcessTokenEx(
218    NtOpenProcessTokenExFunction orig_OpenProcessTokenEx, HANDLE process,
219    ACCESS_MASK desired_access, ULONG handle_attributes, PHANDLE token) {
220  NTSTATUS status = orig_OpenProcessTokenEx(process, desired_access,
221                                            handle_attributes, token);
222  if (NT_SUCCESS(status))
223    return status;
224
225  do {
226    if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled())
227      break;
228
229    if (CURRENT_PROCESS != process)
230      break;
231
232    if (!ValidParameter(token, sizeof(HANDLE), WRITE))
233      break;
234
235    void* memory = GetGlobalIPCMemory();
236    if (NULL == memory)
237      break;
238
239    SharedMemIPCClient ipc(memory);
240    CrossCallReturn answer = {0};
241    ResultCode code = CrossCall(ipc, IPC_NTOPENPROCESSTOKENEX_TAG, process,
242                                desired_access, handle_attributes, &answer);
243    if (SBOX_ALL_OK != code)
244      break;
245
246    if (!NT_SUCCESS(answer.nt_status))
247      return answer.nt_status;
248
249    __try {
250      // Write the output parameters.
251      *token = answer.handle;
252    } __except(EXCEPTION_EXECUTE_HANDLER) {
253      break;
254    }
255
256    return answer.nt_status;
257  } while (false);
258
259  return status;
260}
261
262BOOL WINAPI TargetCreateProcessW(CreateProcessWFunction orig_CreateProcessW,
263                                 LPCWSTR application_name, LPWSTR command_line,
264                                 LPSECURITY_ATTRIBUTES process_attributes,
265                                 LPSECURITY_ATTRIBUTES thread_attributes,
266                                 BOOL inherit_handles, DWORD flags,
267                                 LPVOID environment, LPCWSTR current_directory,
268                                 LPSTARTUPINFOW startup_info,
269                                 LPPROCESS_INFORMATION process_information) {
270  if (orig_CreateProcessW(application_name, command_line, process_attributes,
271                          thread_attributes, inherit_handles, flags,
272                          environment, current_directory, startup_info,
273                          process_information)) {
274    return TRUE;
275  }
276
277  // We don't trust that the IPC can work this early.
278  if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled())
279    return FALSE;
280
281  DWORD original_error = ::GetLastError();
282
283  do {
284    if (!ValidParameter(process_information, sizeof(PROCESS_INFORMATION),
285                        WRITE))
286      break;
287
288    void* memory = GetGlobalIPCMemory();
289    if (NULL == memory)
290      break;
291
292    const wchar_t* cur_dir = NULL;
293
294    wchar_t current_directory[MAX_PATH];
295    DWORD result = ::GetCurrentDirectory(MAX_PATH, current_directory);
296    if (0 != result && result < MAX_PATH)
297      cur_dir = current_directory;
298
299    SharedMemIPCClient ipc(memory);
300    CrossCallReturn answer = {0};
301
302    InOutCountedBuffer proc_info(process_information,
303                                 sizeof(PROCESS_INFORMATION));
304
305    ResultCode code = CrossCall(ipc, IPC_CREATEPROCESSW_TAG, application_name,
306                                command_line, cur_dir, proc_info, &answer);
307    if (SBOX_ALL_OK != code)
308      break;
309
310    ::SetLastError(answer.win32_result);
311    if (ERROR_SUCCESS != answer.win32_result)
312      return FALSE;
313
314    return TRUE;
315  } while (false);
316
317  ::SetLastError(original_error);
318  return FALSE;
319}
320
321BOOL WINAPI TargetCreateProcessA(CreateProcessAFunction orig_CreateProcessA,
322                                 LPCSTR application_name, LPSTR command_line,
323                                 LPSECURITY_ATTRIBUTES process_attributes,
324                                 LPSECURITY_ATTRIBUTES thread_attributes,
325                                 BOOL inherit_handles, DWORD flags,
326                                 LPVOID environment, LPCSTR current_directory,
327                                 LPSTARTUPINFOA startup_info,
328                                 LPPROCESS_INFORMATION process_information) {
329  if (orig_CreateProcessA(application_name, command_line, process_attributes,
330                          thread_attributes, inherit_handles, flags,
331                          environment, current_directory, startup_info,
332                          process_information)) {
333    return TRUE;
334  }
335
336  // We don't trust that the IPC can work this early.
337  if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled())
338    return FALSE;
339
340  DWORD original_error = ::GetLastError();
341
342  do {
343    if (!ValidParameter(process_information, sizeof(PROCESS_INFORMATION),
344                        WRITE))
345      break;
346
347    void* memory = GetGlobalIPCMemory();
348    if (NULL == memory)
349      break;
350
351    // Convert the input params to unicode.
352    UNICODE_STRING *cmd_unicode = NULL;
353    UNICODE_STRING *app_unicode = NULL;
354    if (command_line) {
355      cmd_unicode = AnsiToUnicode(command_line);
356      if (!cmd_unicode)
357        break;
358    }
359
360    if (application_name) {
361      app_unicode = AnsiToUnicode(application_name);
362      if (!app_unicode) {
363        operator delete(cmd_unicode, NT_ALLOC);
364        break;
365      }
366    }
367
368    const wchar_t* cmd_line = cmd_unicode ? cmd_unicode->Buffer : NULL;
369    const wchar_t* app_name = app_unicode ? app_unicode->Buffer : NULL;
370    const wchar_t* cur_dir = NULL;
371
372    wchar_t current_directory[MAX_PATH];
373    DWORD result = ::GetCurrentDirectory(MAX_PATH, current_directory);
374    if (0 != result && result < MAX_PATH)
375      cur_dir = current_directory;
376
377    SharedMemIPCClient ipc(memory);
378    CrossCallReturn answer = {0};
379
380    InOutCountedBuffer proc_info(process_information,
381                                 sizeof(PROCESS_INFORMATION));
382
383    ResultCode code = CrossCall(ipc, IPC_CREATEPROCESSW_TAG, app_name,
384                                cmd_line, cur_dir, proc_info, &answer);
385
386    operator delete(cmd_unicode, NT_ALLOC);
387    operator delete(app_unicode, NT_ALLOC);
388
389    if (SBOX_ALL_OK != code)
390      break;
391
392    ::SetLastError(answer.win32_result);
393    if (ERROR_SUCCESS != answer.win32_result)
394      return FALSE;
395
396    return TRUE;
397  } while (false);
398
399  ::SetLastError(original_error);
400  return FALSE;
401}
402
403// Creates a thread without registering with CSRSS. This is required if we
404// closed the CSRSS ALPC port after lockdown.
405HANDLE WINAPI TargetCreateThread(CreateThreadFunction orig_CreateThread,
406                                 LPSECURITY_ATTRIBUTES thread_attributes,
407                                 SIZE_T stack_size,
408                                 LPTHREAD_START_ROUTINE start_address,
409                                 PVOID parameter,
410                                 DWORD creation_flags,
411                                 LPDWORD thread_id) {
412// Try the normal CreateThread; switch to RtlCreateUserThread if needed.
413  static bool use_create_thread = true;
414  HANDLE thread;
415  if (use_create_thread) {
416    thread = orig_CreateThread(thread_attributes, stack_size, start_address,
417                               parameter, creation_flags, thread_id);
418    if (thread)
419      return thread;
420  }
421
422  PSECURITY_DESCRIPTOR sd =
423      thread_attributes ? thread_attributes->lpSecurityDescriptor : NULL;
424  CLIENT_ID client_id;
425
426  NTSTATUS result = g_nt.RtlCreateUserThread(NtCurrentProcess, sd,
427                                             creation_flags & CREATE_SUSPENDED,
428                                             0, stack_size, 0, start_address,
429                                             parameter, &thread, &client_id);
430  if (!NT_SUCCESS(result))
431    return 0;
432
433  // CSRSS is closed if we got here, so use RtlCreateUserThread from here on.
434  use_create_thread = false;
435  if (thread_id)
436    *thread_id = HandleToUlong(client_id.UniqueThread);
437  return thread;
438}
439
440// Cache the default LCID to avoid pinging CSRSS after lockdown.
441// TODO(jschuh): This approach will miss a default locale changes after
442// lockdown. In the future we may want to have the broker check instead.
443LCID WINAPI TargetGetUserDefaultLCID(
444    GetUserDefaultLCIDFunction orig_GetUserDefaultLCID) {
445  static LCID default_lcid = orig_GetUserDefaultLCID();
446  return default_lcid;
447}
448
449}  // namespace sandbox
450