1// Copyright 2014 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 "chrome_elf/create_file/chrome_create_file.h"
6
7#include <string>
8
9#include "base/strings/string16.h"
10#include "chrome_elf/chrome_elf_constants.h"
11#include "chrome_elf/chrome_elf_util.h"
12#include "chrome_elf/ntdll_cache.h"
13#include "sandbox/win/src/interception_internal.h"
14#include "sandbox/win/src/nt_internals.h"
15
16namespace {
17
18// From ShlObj.h in the Windows SDK.
19#define CSIDL_LOCAL_APPDATA 0x001c
20
21typedef BOOL (WINAPI *PathIsUNCFunction)(
22  IN LPCWSTR path);
23
24typedef BOOL (WINAPI *PathAppendFunction)(
25  IN LPWSTR path,
26  IN LPCWSTR more);
27
28typedef BOOL (WINAPI *PathIsPrefixFunction)(
29  IN LPCWSTR prefix,
30  IN LPCWSTR path);
31
32typedef LPCWSTR (WINAPI *PathFindFileName)(
33  IN LPCWSTR path);
34
35typedef HRESULT (WINAPI *SHGetFolderPathFunction)(
36  IN HWND hwnd_owner,
37  IN int folder,
38  IN HANDLE token,
39  IN DWORD flags,
40  OUT LPWSTR path);
41
42PathIsUNCFunction g_path_is_unc_func;
43PathAppendFunction g_path_append_func;
44PathIsPrefixFunction g_path_is_prefix_func;
45PathFindFileName g_path_find_filename_func;
46SHGetFolderPathFunction g_get_folder_func;
47
48// Record the number of calls we've redirected so far.
49int g_redirect_count = 0;
50
51// Populates the g_*_func pointers to functions which will be used in
52// ShouldBypass(). Chrome_elf cannot have a load-time dependency on shell32 or
53// shlwapi as this would induce a load-time dependency on user32.dll. Instead,
54// the addresses of the functions we need are retrieved the first time this
55// method is called, and cached to avoid subsequent calls to GetProcAddress().
56// It is assumed that the host process will never unload these functions.
57// Returns true if all the functions needed are present.
58bool PopulateShellFunctions() {
59  // Early exit if functions have already been populated.
60  if (g_path_is_unc_func && g_path_append_func &&
61      g_path_is_prefix_func && g_get_folder_func) {
62    return true;
63  }
64
65  // Get the addresses of the functions we need and store them for future use.
66  // These handles are intentionally leaked to ensure that these modules do not
67  // get unloaded.
68  HMODULE shell32 = ::LoadLibrary(L"shell32.dll");
69  HMODULE shlwapi = ::LoadLibrary(L"shlwapi.dll");
70
71  if (!shlwapi || !shell32)
72    return false;
73
74  g_path_is_unc_func = reinterpret_cast<PathIsUNCFunction>(
75      ::GetProcAddress(shlwapi, "PathIsUNCW"));
76  g_path_append_func = reinterpret_cast<PathAppendFunction>(
77      ::GetProcAddress(shlwapi, "PathAppendW"));
78  g_path_is_prefix_func = reinterpret_cast<PathIsPrefixFunction>(
79      ::GetProcAddress(shlwapi, "PathIsPrefixW"));
80  g_path_find_filename_func = reinterpret_cast<PathFindFileName>(
81      ::GetProcAddress(shlwapi, "PathFindFileNameW"));
82  g_get_folder_func = reinterpret_cast<SHGetFolderPathFunction>(
83      ::GetProcAddress(shell32, "SHGetFolderPathW"));
84
85  return g_path_is_unc_func && g_path_append_func && g_path_is_prefix_func &&
86      g_path_find_filename_func && g_get_folder_func;
87}
88
89}  // namespace
90
91// Turn off optimization to make sure these calls don't get inlined.
92#pragma optimize("", off)
93// Wrapper method for kernel32!CreateFile, to avoid setting off caller
94// mitigation detectors.
95HANDLE CreateFileWImpl(LPCWSTR file_name,
96                       DWORD desired_access,
97                       DWORD share_mode,
98                       LPSECURITY_ATTRIBUTES security_attributes,
99                       DWORD creation_disposition,
100                       DWORD flags_and_attributes,
101                       HANDLE template_file) {
102  return CreateFile(file_name,
103                    desired_access,
104                    share_mode,
105                    security_attributes,
106                    creation_disposition,
107                    flags_and_attributes,
108                    template_file);
109
110}
111
112HANDLE WINAPI CreateFileWRedirect(
113    LPCWSTR file_name,
114    DWORD desired_access,
115    DWORD share_mode,
116    LPSECURITY_ATTRIBUTES security_attributes,
117    DWORD creation_disposition,
118    DWORD flags_and_attributes,
119    HANDLE template_file) {
120  if (ShouldBypass(file_name)) {
121    ++g_redirect_count;
122    return CreateFileNTDLL(file_name,
123                           desired_access,
124                           share_mode,
125                           security_attributes,
126                           creation_disposition,
127                           flags_and_attributes,
128                           template_file);
129  }
130  return CreateFileWImpl(file_name,
131                         desired_access,
132                         share_mode,
133                         security_attributes,
134                         creation_disposition,
135                         flags_and_attributes,
136                         template_file);
137}
138#pragma optimize("", on)
139
140int GetRedirectCount() {
141  return g_redirect_count;
142}
143
144HANDLE CreateFileNTDLL(
145    LPCWSTR file_name,
146    DWORD desired_access,
147    DWORD share_mode,
148    LPSECURITY_ATTRIBUTES security_attributes,
149    DWORD creation_disposition,
150    DWORD flags_and_attributes,
151    HANDLE template_file) {
152  HANDLE file_handle = INVALID_HANDLE_VALUE;
153  NTSTATUS result = STATUS_UNSUCCESSFUL;
154  IO_STATUS_BLOCK io_status_block = {};
155  ULONG flags = 0;
156
157  // Convert from Win32 domain to to NT creation disposition values.
158  switch (creation_disposition) {
159    case CREATE_NEW:
160      creation_disposition = FILE_CREATE;
161      break;
162    case CREATE_ALWAYS:
163      creation_disposition = FILE_OVERWRITE_IF;
164      break;
165    case OPEN_EXISTING:
166      creation_disposition = FILE_OPEN;
167      break;
168    case OPEN_ALWAYS:
169      creation_disposition = FILE_OPEN_IF;
170      break;
171    case TRUNCATE_EXISTING:
172      creation_disposition = FILE_OVERWRITE;
173      break;
174    default:
175      SetLastError(ERROR_INVALID_PARAMETER);
176      return INVALID_HANDLE_VALUE;
177  }
178
179  // Translate the flags that need no validation:
180  if (!(flags_and_attributes & FILE_FLAG_OVERLAPPED))
181    flags |= FILE_SYNCHRONOUS_IO_NONALERT;
182
183  if (flags_and_attributes & FILE_FLAG_WRITE_THROUGH)
184    flags |= FILE_WRITE_THROUGH;
185
186  if (flags_and_attributes & FILE_FLAG_RANDOM_ACCESS)
187    flags |= FILE_RANDOM_ACCESS;
188
189  if (flags_and_attributes & FILE_FLAG_SEQUENTIAL_SCAN)
190    flags |= FILE_SEQUENTIAL_ONLY;
191
192  if (flags_and_attributes & FILE_FLAG_DELETE_ON_CLOSE) {
193    flags |= FILE_DELETE_ON_CLOSE;
194    desired_access |= DELETE;
195  }
196
197  if (flags_and_attributes & FILE_FLAG_BACKUP_SEMANTICS)
198    flags |= FILE_OPEN_FOR_BACKUP_INTENT;
199  else
200    flags |= FILE_NON_DIRECTORY_FILE;
201
202
203  if (flags_and_attributes & FILE_FLAG_OPEN_REPARSE_POINT)
204    flags |= FILE_OPEN_REPARSE_POINT;
205
206  if (flags_and_attributes & FILE_FLAG_OPEN_NO_RECALL)
207    flags |= FILE_OPEN_NO_RECALL;
208
209  if (!g_ntdll_lookup["RtlInitUnicodeString"])
210    return INVALID_HANDLE_VALUE;
211
212  NtCreateFileFunction create_file;
213  char thunk_buffer[sizeof(sandbox::ThunkData)] = {};
214
215  if (g_nt_thunk_storage.data[0] != 0) {
216    create_file = reinterpret_cast<NtCreateFileFunction>(&g_nt_thunk_storage);
217    // Copy the thunk data to a buffer on the stack for debugging purposes.
218    memcpy(&thunk_buffer, &g_nt_thunk_storage, sizeof(sandbox::ThunkData));
219  } else if (g_ntdll_lookup["NtCreateFile"]) {
220    create_file =
221        reinterpret_cast<NtCreateFileFunction>(g_ntdll_lookup["NtCreateFile"]);
222  } else {
223    return INVALID_HANDLE_VALUE;
224  }
225
226  RtlInitUnicodeStringFunction init_unicode_string =
227      reinterpret_cast<RtlInitUnicodeStringFunction>(
228          g_ntdll_lookup["RtlInitUnicodeString"]);
229
230  UNICODE_STRING path_unicode_string;
231
232  // Format the path into an NT path. Arguably this should be done with
233  // RtlDosPathNameToNtPathName_U, but afaict this is equivalent for
234  // local paths. Using this with a UNC path name will almost certainly
235  // break in interesting ways.
236  base::string16 filename_string(L"\\??\\");
237  filename_string += file_name;
238
239  init_unicode_string(&path_unicode_string, filename_string.c_str());
240
241  OBJECT_ATTRIBUTES path_attributes = {};
242  InitializeObjectAttributes(&path_attributes,
243                             &path_unicode_string,
244                             OBJ_CASE_INSENSITIVE,
245                             NULL,   // No Root Directory
246                             NULL);  // No Security Descriptor
247
248  // Set desired_access, and flags_and_attributes to match those
249  // set by kernel32!CreateFile.
250  desired_access |= 0x100080;
251  flags_and_attributes &= 0x2FFA7;
252
253  result = create_file(&file_handle,
254                       desired_access,
255                       &path_attributes,
256                       &io_status_block,
257                       0,  // Allocation size
258                       flags_and_attributes,
259                       share_mode,
260                       creation_disposition,
261                       flags,
262                       NULL,
263                       0);
264
265  if (result != STATUS_SUCCESS) {
266    if (result == STATUS_OBJECT_NAME_COLLISION &&
267        creation_disposition == FILE_CREATE) {
268      SetLastError(ERROR_FILE_EXISTS);
269    }
270    return INVALID_HANDLE_VALUE;
271  }
272
273  if (creation_disposition == FILE_OPEN_IF) {
274    SetLastError(io_status_block.Information == FILE_OPENED ?
275        ERROR_ALREADY_EXISTS : ERROR_SUCCESS);
276  } else if (creation_disposition == FILE_OVERWRITE_IF) {
277    SetLastError(io_status_block.Information == FILE_OVERWRITTEN ?
278        ERROR_ALREADY_EXISTS : ERROR_SUCCESS);
279  } else {
280    SetLastError(ERROR_SUCCESS);
281  }
282
283  return file_handle;
284}
285
286bool ShouldBypass(LPCWSTR file_path) {
287  // Do not redirect in non-browser processes.
288  if (IsNonBrowserProcess())
289    return false;
290
291  // If the shell functions are not present, forward the call to kernel32.
292  if (!PopulateShellFunctions())
293    return false;
294
295  // Forward all UNC filepaths to kernel32.
296  if (g_path_is_unc_func(file_path))
297    return false;
298
299  wchar_t local_appdata_path[MAX_PATH];
300
301  // Get the %LOCALAPPDATA% Path and append the location of our UserData
302  // directory to it.
303  HRESULT appdata_result = g_get_folder_func(
304      NULL, CSIDL_LOCAL_APPDATA, NULL, 0, local_appdata_path);
305
306  wchar_t buffer[MAX_PATH] = {};
307  if (!GetModuleFileNameW(NULL, buffer, MAX_PATH))
308    return false;
309
310  bool is_canary = IsCanary(buffer);
311
312  // If getting the %LOCALAPPDATA% path or appending to it failed, then forward
313  // the call to kernel32.
314  if (!SUCCEEDED(appdata_result) ||
315      !g_path_append_func(local_appdata_path, is_canary ?
316          kCanaryAppDataDirName : kAppDataDirName) ||
317      !g_path_append_func(local_appdata_path, kUserDataDirName)) {
318    return false;
319  }
320
321  LPCWSTR file_name = g_path_find_filename_func(file_path);
322
323  bool in_userdata_dir = !!g_path_is_prefix_func(local_appdata_path, file_path);
324  bool is_settings_file = wcscmp(file_name, kPreferencesFilename) == 0 ||
325      wcscmp(file_name, kLocalStateFilename) == 0;
326
327  // Check if we are trying to access the Preferences in the UserData dir. If
328  // so, then redirect the call to bypass kernel32.
329  return in_userdata_dir && is_settings_file;
330}
331