1// Copyright (c) 2011 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/win_utils.h"
6
7#include <map>
8
9#include "base/memory/scoped_ptr.h"
10#include "sandbox/win/src/internal_types.h"
11#include "sandbox/win/src/nt_internals.h"
12#include "sandbox/win/src/sandbox_nt_util.h"
13
14namespace {
15
16// Holds the information about a known registry key.
17struct KnownReservedKey {
18  const wchar_t* name;
19  HKEY key;
20};
21
22// Contains all the known registry key by name and by handle.
23const KnownReservedKey kKnownKey[] = {
24    { L"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT },
25    { L"HKEY_CURRENT_USER", HKEY_CURRENT_USER },
26    { L"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE},
27    { L"HKEY_USERS", HKEY_USERS},
28    { L"HKEY_PERFORMANCE_DATA", HKEY_PERFORMANCE_DATA},
29    { L"HKEY_PERFORMANCE_TEXT", HKEY_PERFORMANCE_TEXT},
30    { L"HKEY_PERFORMANCE_NLSTEXT", HKEY_PERFORMANCE_NLSTEXT},
31    { L"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG},
32    { L"HKEY_DYN_DATA", HKEY_DYN_DATA}
33};
34
35// Returns true if the provided path points to a pipe.
36bool IsPipe(const base::string16& path) {
37  size_t start = 0;
38  if (0 == path.compare(0, sandbox::kNTPrefixLen, sandbox::kNTPrefix))
39    start = sandbox::kNTPrefixLen;
40
41  const wchar_t kPipe[] = L"pipe\\";
42  return (0 == path.compare(start, arraysize(kPipe) - 1, kPipe));
43}
44
45}  // namespace
46
47namespace sandbox {
48
49HKEY GetReservedKeyFromName(const base::string16& name) {
50  for (size_t i = 0; i < arraysize(kKnownKey); ++i) {
51    if (name == kKnownKey[i].name)
52      return kKnownKey[i].key;
53  }
54
55  return NULL;
56}
57
58bool ResolveRegistryName(base::string16 name, base::string16* resolved_name) {
59  for (size_t i = 0; i < arraysize(kKnownKey); ++i) {
60    if (name.find(kKnownKey[i].name) == 0) {
61      HKEY key;
62      DWORD disposition;
63      if (ERROR_SUCCESS != ::RegCreateKeyEx(kKnownKey[i].key, L"", 0, NULL, 0,
64                                            MAXIMUM_ALLOWED, NULL, &key,
65                                            &disposition))
66        return false;
67
68      bool result = GetPathFromHandle(key, resolved_name);
69      ::RegCloseKey(key);
70
71      if (!result)
72        return false;
73
74      *resolved_name += name.substr(wcslen(kKnownKey[i].name));
75      return true;
76    }
77  }
78
79  return false;
80}
81
82DWORD IsReparsePoint(const base::string16& full_path, bool* result) {
83  base::string16 path = full_path;
84
85  // Remove the nt prefix.
86  if (0 == path.compare(0, kNTPrefixLen, kNTPrefix))
87    path = path.substr(kNTPrefixLen);
88
89  // Check if it's a pipe. We can't query the attributes of a pipe.
90  if (IsPipe(path)) {
91    *result = FALSE;
92    return ERROR_SUCCESS;
93  }
94
95  base::string16::size_type last_pos = base::string16::npos;
96
97  do {
98    path = path.substr(0, last_pos);
99
100    DWORD attributes = ::GetFileAttributes(path.c_str());
101    if (INVALID_FILE_ATTRIBUTES == attributes) {
102      DWORD error = ::GetLastError();
103      if (error != ERROR_FILE_NOT_FOUND &&
104          error != ERROR_PATH_NOT_FOUND &&
105          error != ERROR_INVALID_NAME) {
106        // Unexpected error.
107        NOTREACHED_NT();
108        return error;
109      }
110    } else if (FILE_ATTRIBUTE_REPARSE_POINT & attributes) {
111      // This is a reparse point.
112      *result = true;
113      return ERROR_SUCCESS;
114    }
115
116    last_pos = path.rfind(L'\\');
117  } while (last_pos != base::string16::npos);
118
119  *result = false;
120  return ERROR_SUCCESS;
121}
122
123// We get a |full_path| of the form \??\c:\some\foo\bar, and the name that
124// we'll get from |handle| will be \device\harddiskvolume1\some\foo\bar.
125bool SameObject(HANDLE handle, const wchar_t* full_path) {
126  base::string16 path(full_path);
127  DCHECK_NT(!path.empty());
128
129  // Check if it's a pipe.
130  if (IsPipe(path))
131    return true;
132
133  base::string16 actual_path;
134  if (!GetPathFromHandle(handle, &actual_path))
135    return false;
136
137  // This may end with a backslash.
138  const wchar_t kBackslash = '\\';
139  if (path[path.length() - 1] == kBackslash)
140    path = path.substr(0, path.length() - 1);
141
142  // Perfect match (case-insesitive check).
143  if (0 == _wcsicmp(actual_path.c_str(), path.c_str()))
144    return true;
145
146  // Look for the drive letter.
147  size_t colon_pos = path.find(L':');
148  if (colon_pos == 0 || colon_pos == base::string16::npos)
149    return false;
150
151  // Only one character for the drive.
152  if (colon_pos > 1 && path[colon_pos - 2] != kBackslash)
153    return false;
154
155  // We only need 3 chars, but let's alloc a buffer for four.
156  wchar_t drive[4] = {0};
157  wchar_t vol_name[MAX_PATH];
158  memcpy(drive, &path[colon_pos - 1], 2 * sizeof(*drive));
159
160  // We'll get a double null terminated string.
161  DWORD vol_length = ::QueryDosDeviceW(drive, vol_name, MAX_PATH);
162  if (vol_length < 2 || vol_length == MAX_PATH)
163    return false;
164
165  // Ignore the nulls at the end.
166  vol_length = static_cast<DWORD>(wcslen(vol_name));
167
168  // The two paths should be the same length.
169  if (vol_length + path.size() - (colon_pos + 1) != actual_path.size())
170    return false;
171
172  // Check up to the drive letter.
173  if (0 != _wcsnicmp(actual_path.c_str(), vol_name, vol_length))
174    return false;
175
176  // Check the path after the drive letter.
177  if (0 != _wcsicmp(&actual_path[vol_length], &path[colon_pos + 1]))
178    return false;
179
180  return true;
181}
182
183bool ConvertToLongPath(const base::string16& short_path,
184                       base::string16* long_path) {
185  // Check if the path is a NT path.
186  bool is_nt_path = false;
187  base::string16 path = short_path;
188  if (0 == path.compare(0, kNTPrefixLen, kNTPrefix)) {
189    path = path.substr(kNTPrefixLen);
190    is_nt_path = true;
191  }
192
193  DWORD size = MAX_PATH;
194  scoped_ptr<wchar_t[]> long_path_buf(new wchar_t[size]);
195
196  DWORD return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(),
197                                         size);
198  while (return_value >= size) {
199    size *= 2;
200    long_path_buf.reset(new wchar_t[size]);
201    return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(), size);
202  }
203
204  DWORD last_error = ::GetLastError();
205  if (0 == return_value && (ERROR_FILE_NOT_FOUND == last_error ||
206                            ERROR_PATH_NOT_FOUND == last_error ||
207                            ERROR_INVALID_NAME == last_error)) {
208    // The file does not exist, but maybe a sub path needs to be expanded.
209    base::string16::size_type last_slash = path.rfind(L'\\');
210    if (base::string16::npos == last_slash)
211      return false;
212
213    base::string16 begin = path.substr(0, last_slash);
214    base::string16 end = path.substr(last_slash);
215    if (!ConvertToLongPath(begin, &begin))
216      return false;
217
218    // Ok, it worked. Let's reset the return value.
219    path = begin + end;
220    return_value = 1;
221  } else if (0 != return_value) {
222    path = long_path_buf.get();
223  }
224
225  if (return_value != 0) {
226    if (is_nt_path) {
227      *long_path = kNTPrefix;
228      *long_path += path;
229    } else {
230      *long_path = path;
231    }
232
233    return true;
234  }
235
236  return false;
237}
238
239bool GetPathFromHandle(HANDLE handle, base::string16* path) {
240  NtQueryObjectFunction NtQueryObject = NULL;
241  ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject);
242
243  OBJECT_NAME_INFORMATION initial_buffer;
244  OBJECT_NAME_INFORMATION* name = &initial_buffer;
245  ULONG size = sizeof(initial_buffer);
246  // Query the name information a first time to get the size of the name.
247  NTSTATUS status = NtQueryObject(handle, ObjectNameInformation, name, size,
248                                  &size);
249
250  scoped_ptr<OBJECT_NAME_INFORMATION> name_ptr;
251  if (size) {
252    name = reinterpret_cast<OBJECT_NAME_INFORMATION*>(new BYTE[size]);
253    name_ptr.reset(name);
254
255    // Query the name information a second time to get the name of the
256    // object referenced by the handle.
257    status = NtQueryObject(handle, ObjectNameInformation, name, size, &size);
258  }
259
260  if (STATUS_SUCCESS != status)
261    return false;
262
263  path->assign(name->ObjectName.Buffer, name->ObjectName.Length /
264                                        sizeof(name->ObjectName.Buffer[0]));
265  return true;
266}
267
268bool GetNtPathFromWin32Path(const base::string16& path,
269                            base::string16* nt_path) {
270  HANDLE file = ::CreateFileW(path.c_str(), 0,
271    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
272    OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
273  if (file == INVALID_HANDLE_VALUE)
274    return false;
275  bool rv = GetPathFromHandle(file, nt_path);
276  ::CloseHandle(file);
277  return rv;
278}
279
280bool WriteProtectedChildMemory(HANDLE child_process, void* address,
281                               const void* buffer, size_t length) {
282  // First, remove the protections.
283  DWORD old_protection;
284  if (!::VirtualProtectEx(child_process, address, length,
285                          PAGE_WRITECOPY, &old_protection))
286    return false;
287
288  SIZE_T written;
289  bool ok = ::WriteProcessMemory(child_process, address, buffer, length,
290                                 &written) && (length == written);
291
292  // Always attempt to restore the original protection.
293  if (!::VirtualProtectEx(child_process, address, length,
294                          old_protection, &old_protection))
295    return false;
296
297  return ok;
298}
299
300};  // namespace sandbox
301
302// TODO(jschuh): http://crbug.com/11789
303// I'm guessing we have a race where some "security" software is messing
304// with ntdll/imports underneath us. So, we retry a few times, and in the
305// worst case we sleep briefly before a few more attempts. (Normally sleeping
306// would be very bad, but it's better than crashing in this case.)
307void ResolveNTFunctionPtr(const char* name, void* ptr) {
308  const int max_tries = 5;
309  const int sleep_threshold = 2;
310
311  static HMODULE ntdll = ::GetModuleHandle(sandbox::kNtdllName);
312
313  FARPROC* function_ptr = reinterpret_cast<FARPROC*>(ptr);
314  *function_ptr = ::GetProcAddress(ntdll, name);
315
316  for (int tries = 1; !(*function_ptr) && tries < max_tries; ++tries) {
317    if (tries >= sleep_threshold)
318      ::Sleep(1);
319    ntdll = ::GetModuleHandle(sandbox::kNtdllName);
320    *function_ptr = ::GetProcAddress(ntdll, name);
321  }
322
323  CHECK_NT(*function_ptr);
324}
325