summaryrefslogtreecommitdiffstats
path: root/chrome_elf/create_file/chrome_create_file.cc
blob: a39cd3e128123d74f030940415ed2261ef66142f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
// Copyright 2014 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 "chrome_elf/create_file/chrome_create_file.h"

#include <string>

#include "base/strings/string16.h"
#include "chrome_elf/chrome_elf_constants.h"
#include "chrome_elf/ntdll_cache.h"
#include "sandbox/win/src/nt_internals.h"

namespace {

// From ShlObj.h in the Windows SDK.
#define CSIDL_LOCAL_APPDATA 0x001c

typedef BOOL (WINAPI *PathIsUNCFunction)(
  IN LPCWSTR path);

typedef BOOL (WINAPI *PathAppendFunction)(
  IN LPWSTR path,
  IN LPCWSTR more);

typedef BOOL (WINAPI *PathIsPrefixFunction)(
  IN LPCWSTR prefix,
  IN LPCWSTR path);

typedef LPCWSTR (WINAPI *PathFindFileName)(
  IN LPCWSTR path);

typedef HRESULT (WINAPI *SHGetFolderPathFunction)(
  IN HWND hwnd_owner,
  IN int folder,
  IN HANDLE token,
  IN DWORD flags,
  OUT LPWSTR path);

PathIsUNCFunction g_path_is_unc_func;
PathAppendFunction g_path_append_func;
PathIsPrefixFunction g_path_is_prefix_func;
PathFindFileName g_path_find_filename_func;
SHGetFolderPathFunction g_get_folder_func;

// Populates the g_*_func pointers to functions which will be used in
// ShouldBypass(). Chrome_elf cannot have a load-time dependency on shell32 or
// shlwapi as this would induce a load-time dependency on user32.dll. Instead,
// the addresses of the functions we need are retrieved the first time this
// method is called, and cached to avoid subsequent calls to GetProcAddress().
// It is assumed that the host process will never unload these functions.
// Returns true if all the functions needed are present.
bool PopulateShellFunctions() {
  // Early exit if functions have already been populated.
  if (g_path_is_unc_func && g_path_append_func &&
      g_path_is_prefix_func && g_get_folder_func) {
    return true;
  }

  // Get the addresses of the functions we need and store them for future use.
  // These handles are intentionally leaked to ensure that these modules do not
  // get unloaded.
  HMODULE shell32 = ::LoadLibrary(L"shell32.dll");
  HMODULE shlwapi = ::LoadLibrary(L"shlwapi.dll");

  if (!shlwapi || !shell32)
    return false;

  g_path_is_unc_func = reinterpret_cast<PathIsUNCFunction>(
      ::GetProcAddress(shlwapi, "PathIsUNCW"));
  g_path_append_func = reinterpret_cast<PathAppendFunction>(
      ::GetProcAddress(shlwapi, "PathAppendW"));
  g_path_is_prefix_func = reinterpret_cast<PathIsPrefixFunction>(
      ::GetProcAddress(shlwapi, "PathIsPrefixW"));
  g_path_find_filename_func = reinterpret_cast<PathFindFileName>(
      ::GetProcAddress(shlwapi, "PathFindFileNameW"));
  g_get_folder_func = reinterpret_cast<SHGetFolderPathFunction>(
      ::GetProcAddress(shell32, "SHGetFolderPathW"));

  return g_path_is_unc_func && g_path_append_func && g_path_is_prefix_func &&
      g_path_find_filename_func && g_get_folder_func;
}

}  // namespace

HANDLE WINAPI CreateFileWRedirect(
    LPCWSTR file_name,
    DWORD desired_access,
    DWORD share_mode,
    LPSECURITY_ATTRIBUTES security_attributes,
    DWORD creation_disposition,
    DWORD flags_and_attributes,
    HANDLE template_file) {
  if (ShouldBypass(file_name)) {
    return CreateFileNTDLL(file_name,
                           desired_access,
                           share_mode,
                           security_attributes,
                           creation_disposition,
                           flags_and_attributes,
                           template_file);
  }
  return CreateFile(file_name,
                    desired_access,
                    share_mode,
                    security_attributes,
                    creation_disposition,
                    flags_and_attributes,
                    template_file);

}

HANDLE CreateFileNTDLL(
    LPCWSTR file_name,
    DWORD desired_access,
    DWORD share_mode,
    LPSECURITY_ATTRIBUTES security_attributes,
    DWORD creation_disposition,
    DWORD flags_and_attributes,
    HANDLE template_file) {
  HANDLE file_handle = INVALID_HANDLE_VALUE;
  NTSTATUS result = STATUS_UNSUCCESSFUL;
  IO_STATUS_BLOCK io_status_block = {};
  ULONG flags = 0;

  // Convert from Win32 domain to to NT creation disposition values.
  switch (creation_disposition) {
    case CREATE_NEW:
      creation_disposition = FILE_CREATE;
      break;
    case CREATE_ALWAYS:
      creation_disposition = FILE_OVERWRITE_IF;
      break;
    case OPEN_EXISTING:
      creation_disposition = FILE_OPEN;
      break;
    case OPEN_ALWAYS:
      creation_disposition = FILE_OPEN_IF;
      break;
    case TRUNCATE_EXISTING:
      creation_disposition = FILE_OVERWRITE;
      break;
    default:
      SetLastError(ERROR_INVALID_PARAMETER);
      return INVALID_HANDLE_VALUE;
  }

  // Translate the flags that need no validation:
  if (!(flags_and_attributes & FILE_FLAG_OVERLAPPED))
    flags |= FILE_SYNCHRONOUS_IO_NONALERT;

  if (flags_and_attributes & FILE_FLAG_WRITE_THROUGH)
    flags |= FILE_WRITE_THROUGH;

  if (flags_and_attributes & FILE_FLAG_RANDOM_ACCESS)
    flags |= FILE_RANDOM_ACCESS;

  if (flags_and_attributes & FILE_FLAG_SEQUENTIAL_SCAN)
    flags |= FILE_SEQUENTIAL_ONLY;

  if (flags_and_attributes & FILE_FLAG_DELETE_ON_CLOSE) {
    flags |= FILE_DELETE_ON_CLOSE;
    desired_access |= DELETE;
  }

  if (flags_and_attributes & FILE_FLAG_BACKUP_SEMANTICS)
    flags |= FILE_OPEN_FOR_BACKUP_INTENT;
  else
    flags |= FILE_NON_DIRECTORY_FILE;


  if (flags_and_attributes & FILE_FLAG_OPEN_REPARSE_POINT)
    flags |= FILE_OPEN_REPARSE_POINT;

  if (flags_and_attributes & FILE_FLAG_OPEN_NO_RECALL)
    flags |= FILE_OPEN_NO_RECALL;

  if (!g_ntdll_lookup["NtCreateFile"] ||
      !g_ntdll_lookup["RtlInitUnicodeString"]) {
    return INVALID_HANDLE_VALUE;
  }

  NtCreateFileFunction create_file =
      reinterpret_cast<NtCreateFileFunction>(g_ntdll_lookup["NtCreateFile"]);

  RtlInitUnicodeStringFunction init_unicode_string =
      reinterpret_cast<RtlInitUnicodeStringFunction>(
          g_ntdll_lookup["RtlInitUnicodeString"]);

  UNICODE_STRING path_unicode_string;

  // Format the path into an NT path. Arguably this should be done with
  // RtlDosPathNameToNtPathName_U, but afaict this is equivalent for
  // local paths. Using this with a UNC path name will almost certainly
  // break in interesting ways.
  base::string16 filename_string(L"\\??\\");
  filename_string += file_name;

  init_unicode_string(&path_unicode_string, filename_string.c_str());

  OBJECT_ATTRIBUTES path_attributes = {};
  InitializeObjectAttributes(&path_attributes,
                             &path_unicode_string,
                             OBJ_CASE_INSENSITIVE,
                             NULL,   // No Root Directory
                             NULL);  // No Security Descriptor

  // Set desired_access, and flags_and_attributes to match those
  // set by kernel32!CreateFile.
  desired_access |= 0x100080;
  flags_and_attributes &= 0x2FFA7;

  result = create_file(&file_handle,
                       desired_access,
                       &path_attributes,
                       &io_status_block,
                       0,  // Allocation size
                       flags_and_attributes,
                       share_mode,
                       creation_disposition,
                       flags,
                       NULL,
                       0);

  if (result != STATUS_SUCCESS) {
    if (result == STATUS_OBJECT_NAME_COLLISION &&
        creation_disposition == FILE_CREATE) {
      SetLastError(ERROR_FILE_EXISTS);
    }
    return INVALID_HANDLE_VALUE;
  }

  if (creation_disposition == FILE_OPEN_IF) {
    SetLastError(io_status_block.Information == FILE_OPENED ?
        ERROR_ALREADY_EXISTS : ERROR_SUCCESS);
  } else if (creation_disposition == FILE_OVERWRITE_IF) {
    SetLastError(io_status_block.Information == FILE_OVERWRITTEN ?
        ERROR_ALREADY_EXISTS : ERROR_SUCCESS);
  } else {
    SetLastError(ERROR_SUCCESS);
  }

  return file_handle;
}

bool ShouldBypass(LPCWSTR file_path) {
  // If the shell functions are not present, forward the call to kernel32.
  if (!PopulateShellFunctions())
    return false;

  // Forward all UNC filepaths to kernel32.
  if (g_path_is_unc_func(file_path))
    return false;

  wchar_t local_appdata_path[MAX_PATH];

  // Get the %LOCALAPPDATA% Path and append the location of our UserData
  // directory to it.
  HRESULT appdata_result = g_get_folder_func(
      NULL, CSIDL_LOCAL_APPDATA, NULL, 0, local_appdata_path);

  // If getting the %LOCALAPPDATA% path or appending to it failed, then forward
  // the call to kernel32.
  if (!SUCCEEDED(appdata_result) ||
      !g_path_append_func(local_appdata_path, kAppDataDirName) ||
      !g_path_append_func(local_appdata_path, kUserDataDirName)) {
    return false;
  }

  LPCWSTR file_name = g_path_find_filename_func(file_path);

  bool in_userdata_dir = !!g_path_is_prefix_func(local_appdata_path, file_path);
  bool is_settings_file = wcscmp(file_name, kPreferencesFilename) == 0 ||
      wcscmp(file_name, kLocalStateFilename) == 0;

  // Check if we are trying to access the Preferences in the UserData dir. If
  // so, then redirect the call to bypass kernel32.
  return in_userdata_dir && is_settings_file;
}