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