Roll src/third_party/WebKit e0eac24:489c548 (svn 193311:193320)
[chromium-blink-merge.git] / sandbox / win / src / win_utils.cc
blobd2b507d3e6a848e79bb2016bf6541f5a18bb88bc
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.
5 #include "sandbox/win/src/win_utils.h"
7 #include <map>
9 #include "base/memory/scoped_ptr.h"
10 #include "base/strings/string_util.h"
11 #include "base/win/pe_image.h"
12 #include "sandbox/win/src/internal_types.h"
13 #include "sandbox/win/src/nt_internals.h"
14 #include "sandbox/win/src/sandbox_nt_util.h"
16 namespace {
18 // Holds the information about a known registry key.
19 struct KnownReservedKey {
20 const wchar_t* name;
21 HKEY key;
24 // Contains all the known registry key by name and by handle.
25 const KnownReservedKey kKnownKey[] = {
26 { L"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT },
27 { L"HKEY_CURRENT_USER", HKEY_CURRENT_USER },
28 { L"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE},
29 { L"HKEY_USERS", HKEY_USERS},
30 { L"HKEY_PERFORMANCE_DATA", HKEY_PERFORMANCE_DATA},
31 { L"HKEY_PERFORMANCE_TEXT", HKEY_PERFORMANCE_TEXT},
32 { L"HKEY_PERFORMANCE_NLSTEXT", HKEY_PERFORMANCE_NLSTEXT},
33 { L"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG},
34 { L"HKEY_DYN_DATA", HKEY_DYN_DATA}
37 // These functions perform case independent path comparisons.
38 bool EqualPath(const base::string16& first, const base::string16& second) {
39 return _wcsicmp(first.c_str(), second.c_str()) == 0;
42 bool EqualPath(const base::string16& first, size_t first_offset,
43 const base::string16& second, size_t second_offset) {
44 return _wcsicmp(first.c_str() + first_offset,
45 second.c_str() + second_offset) == 0;
48 bool EqualPath(const base::string16& first,
49 const wchar_t* second, size_t second_len) {
50 return _wcsnicmp(first.c_str(), second, second_len) == 0;
53 bool EqualPath(const base::string16& first, size_t first_offset,
54 const wchar_t* second, size_t second_len) {
55 return _wcsnicmp(first.c_str() + first_offset, second, second_len) == 0;
58 // Returns true if |path| starts with "\??\" and returns a path without that
59 // component.
60 bool IsNTPath(const base::string16& path, base::string16* trimmed_path ) {
61 if ((path.size() < sandbox::kNTPrefixLen) ||
62 (0 != path.compare(0, sandbox::kNTPrefixLen, sandbox::kNTPrefix))) {
63 *trimmed_path = path;
64 return false;
67 *trimmed_path = path.substr(sandbox::kNTPrefixLen);
68 return true;
71 // Returns true if |path| starts with "\Device\" and returns a path without that
72 // component.
73 bool IsDevicePath(const base::string16& path, base::string16* trimmed_path ) {
74 if ((path.size() < sandbox::kNTDevicePrefixLen) ||
75 (!EqualPath(path, sandbox::kNTDevicePrefix,
76 sandbox::kNTDevicePrefixLen))) {
77 *trimmed_path = path;
78 return false;
81 *trimmed_path = path.substr(sandbox::kNTDevicePrefixLen);
82 return true;
85 bool StartsWithDriveLetter(const base::string16& path) {
86 if (path.size() < 3)
87 return false;
89 if (path[1] != L':' || path[2] != L'\\')
90 return false;
92 return (path[0] >= 'a' && path[0] <= 'z') ||
93 (path[0] >= 'A' && path[0] <= 'Z');
96 const wchar_t kNTDotPrefix[] = L"\\\\.\\";
97 const size_t kNTDotPrefixLen = arraysize(kNTDotPrefix) - 1;
99 // Removes "\\\\.\\" from the path.
100 void RemoveImpliedDevice(base::string16* path) {
101 if (0 == path->compare(0, kNTDotPrefixLen, kNTDotPrefix))
102 *path = path->substr(kNTDotPrefixLen);
105 } // namespace
107 namespace sandbox {
109 // Returns true if the provided path points to a pipe.
110 bool IsPipe(const base::string16& path) {
111 size_t start = 0;
112 if (0 == path.compare(0, sandbox::kNTPrefixLen, sandbox::kNTPrefix))
113 start = sandbox::kNTPrefixLen;
115 const wchar_t kPipe[] = L"pipe\\";
116 if (path.size() < start + arraysize(kPipe) - 1)
117 return false;
119 return EqualPath(path, start, kPipe, arraysize(kPipe) - 1);
122 HKEY GetReservedKeyFromName(const base::string16& name) {
123 for (size_t i = 0; i < arraysize(kKnownKey); ++i) {
124 if (name == kKnownKey[i].name)
125 return kKnownKey[i].key;
128 return NULL;
131 bool ResolveRegistryName(base::string16 name, base::string16* resolved_name) {
132 for (size_t i = 0; i < arraysize(kKnownKey); ++i) {
133 if (name.find(kKnownKey[i].name) == 0) {
134 HKEY key;
135 DWORD disposition;
136 if (ERROR_SUCCESS != ::RegCreateKeyEx(kKnownKey[i].key, L"", 0, NULL, 0,
137 MAXIMUM_ALLOWED, NULL, &key,
138 &disposition))
139 return false;
141 bool result = GetPathFromHandle(key, resolved_name);
142 ::RegCloseKey(key);
144 if (!result)
145 return false;
147 *resolved_name += name.substr(wcslen(kKnownKey[i].name));
148 return true;
152 return false;
155 // |full_path| can have any of the following forms:
156 // \??\c:\some\foo\bar
157 // \Device\HarddiskVolume0\some\foo\bar
158 // \??\HarddiskVolume0\some\foo\bar
159 DWORD IsReparsePoint(const base::string16& full_path, bool* result) {
160 // Check if it's a pipe. We can't query the attributes of a pipe.
161 if (IsPipe(full_path)) {
162 *result = FALSE;
163 return ERROR_SUCCESS;
166 base::string16 path;
167 bool nt_path = IsNTPath(full_path, &path);
168 bool has_drive = StartsWithDriveLetter(path);
169 bool is_device_path = IsDevicePath(path, &path);
171 if (!has_drive && !is_device_path && !nt_path)
172 return ERROR_INVALID_NAME;
174 bool added_implied_device = false;
175 if (!has_drive) {
176 path = base::string16(kNTDotPrefix) + path;
177 added_implied_device = true;
180 base::string16::size_type last_pos = base::string16::npos;
181 bool passed_once = false;
183 do {
184 path = path.substr(0, last_pos);
186 DWORD attributes = ::GetFileAttributes(path.c_str());
187 if (INVALID_FILE_ATTRIBUTES == attributes) {
188 DWORD error = ::GetLastError();
189 if (error != ERROR_FILE_NOT_FOUND &&
190 error != ERROR_PATH_NOT_FOUND &&
191 error != ERROR_INVALID_NAME) {
192 // Unexpected error.
193 if (passed_once && added_implied_device &&
194 (path.rfind(L'\\') == kNTDotPrefixLen - 1)) {
195 break;
197 NOTREACHED_NT();
198 return error;
200 } else if (FILE_ATTRIBUTE_REPARSE_POINT & attributes) {
201 // This is a reparse point.
202 *result = true;
203 return ERROR_SUCCESS;
206 passed_once = true;
207 last_pos = path.rfind(L'\\');
208 } while (last_pos > 2); // Skip root dir.
210 *result = false;
211 return ERROR_SUCCESS;
214 // We get a |full_path| of the forms accepted by IsReparsePoint(), and the name
215 // we'll get from |handle| will be \device\harddiskvolume1\some\foo\bar.
216 bool SameObject(HANDLE handle, const wchar_t* full_path) {
217 // Check if it's a pipe.
218 if (IsPipe(full_path))
219 return true;
221 base::string16 actual_path;
222 if (!GetPathFromHandle(handle, &actual_path))
223 return false;
225 base::string16 path(full_path);
226 DCHECK_NT(!path.empty());
228 // This may end with a backslash.
229 const wchar_t kBackslash = '\\';
230 if (path[path.length() - 1] == kBackslash)
231 path = path.substr(0, path.length() - 1);
233 // Perfect match (case-insesitive check).
234 if (EqualPath(actual_path, path))
235 return true;
237 bool nt_path = IsNTPath(path, &path);
238 bool has_drive = StartsWithDriveLetter(path);
240 if (!has_drive && nt_path) {
241 base::string16 simple_actual_path;
242 if (!IsDevicePath(actual_path, &simple_actual_path))
243 return false;
245 // Perfect match (case-insesitive check).
246 return (EqualPath(simple_actual_path, path));
249 if (!has_drive)
250 return false;
252 // We only need 3 chars, but let's alloc a buffer for four.
253 wchar_t drive[4] = {0};
254 wchar_t vol_name[MAX_PATH];
255 memcpy(drive, &path[0], 2 * sizeof(*drive));
257 // We'll get a double null terminated string.
258 DWORD vol_length = ::QueryDosDeviceW(drive, vol_name, MAX_PATH);
259 if (vol_length < 2 || vol_length == MAX_PATH)
260 return false;
262 // Ignore the nulls at the end.
263 vol_length = static_cast<DWORD>(wcslen(vol_name));
265 // The two paths should be the same length.
266 if (vol_length + path.size() - 2 != actual_path.size())
267 return false;
269 // Check up to the drive letter.
270 if (!EqualPath(actual_path, vol_name, vol_length))
271 return false;
273 // Check the path after the drive letter.
274 if (!EqualPath(actual_path, vol_length, path, 2))
275 return false;
277 return true;
280 // Paths like \Device\HarddiskVolume0\some\foo\bar are assumed to be already
281 // expanded.
282 bool ConvertToLongPath(const base::string16& short_path,
283 base::string16* long_path) {
284 if (IsPipe(short_path)) {
285 // TODO(rvargas): Change the signature to use a single argument.
286 long_path->assign(short_path);
287 return true;
290 base::string16 path;
291 if (IsDevicePath(short_path, &path))
292 return false;
294 bool is_nt_path = IsNTPath(path, &path);
295 bool added_implied_device = false;
296 if (!StartsWithDriveLetter(path) && is_nt_path) {
297 path = base::string16(kNTDotPrefix) + path;
298 added_implied_device = true;
301 DWORD size = MAX_PATH;
302 scoped_ptr<wchar_t[]> long_path_buf(new wchar_t[size]);
304 DWORD return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(),
305 size);
306 while (return_value >= size) {
307 size *= 2;
308 long_path_buf.reset(new wchar_t[size]);
309 return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(), size);
312 DWORD last_error = ::GetLastError();
313 if (0 == return_value && (ERROR_FILE_NOT_FOUND == last_error ||
314 ERROR_PATH_NOT_FOUND == last_error ||
315 ERROR_INVALID_NAME == last_error)) {
316 // The file does not exist, but maybe a sub path needs to be expanded.
317 base::string16::size_type last_slash = path.rfind(L'\\');
318 if (base::string16::npos == last_slash)
319 return false;
321 base::string16 begin = path.substr(0, last_slash);
322 base::string16 end = path.substr(last_slash);
323 if (!ConvertToLongPath(begin, &begin))
324 return false;
326 // Ok, it worked. Let's reset the return value.
327 path = begin + end;
328 return_value = 1;
329 } else if (0 != return_value) {
330 path = long_path_buf.get();
333 if (return_value != 0) {
334 if (added_implied_device)
335 RemoveImpliedDevice(&path);
337 if (is_nt_path) {
338 *long_path = kNTPrefix;
339 *long_path += path;
340 } else {
341 *long_path = path;
344 return true;
347 return false;
350 bool GetPathFromHandle(HANDLE handle, base::string16* path) {
351 NtQueryObjectFunction NtQueryObject = NULL;
352 ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject);
354 OBJECT_NAME_INFORMATION initial_buffer;
355 OBJECT_NAME_INFORMATION* name = &initial_buffer;
356 ULONG size = sizeof(initial_buffer);
357 // Query the name information a first time to get the size of the name.
358 NTSTATUS status = NtQueryObject(handle, ObjectNameInformation, name, size,
359 &size);
361 scoped_ptr<OBJECT_NAME_INFORMATION> name_ptr;
362 if (size) {
363 name = reinterpret_cast<OBJECT_NAME_INFORMATION*>(new BYTE[size]);
364 name_ptr.reset(name);
366 // Query the name information a second time to get the name of the
367 // object referenced by the handle.
368 status = NtQueryObject(handle, ObjectNameInformation, name, size, &size);
371 if (STATUS_SUCCESS != status)
372 return false;
374 path->assign(name->ObjectName.Buffer, name->ObjectName.Length /
375 sizeof(name->ObjectName.Buffer[0]));
376 return true;
379 bool GetNtPathFromWin32Path(const base::string16& path,
380 base::string16* nt_path) {
381 HANDLE file = ::CreateFileW(path.c_str(), 0,
382 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
383 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
384 if (file == INVALID_HANDLE_VALUE)
385 return false;
386 bool rv = GetPathFromHandle(file, nt_path);
387 ::CloseHandle(file);
388 return rv;
391 bool WriteProtectedChildMemory(HANDLE child_process, void* address,
392 const void* buffer, size_t length) {
393 // First, remove the protections.
394 DWORD old_protection;
395 if (!::VirtualProtectEx(child_process, address, length,
396 PAGE_WRITECOPY, &old_protection))
397 return false;
399 SIZE_T written;
400 bool ok = ::WriteProcessMemory(child_process, address, buffer, length,
401 &written) && (length == written);
403 // Always attempt to restore the original protection.
404 if (!::VirtualProtectEx(child_process, address, length,
405 old_protection, &old_protection))
406 return false;
408 return ok;
411 }; // namespace sandbox
413 void ResolveNTFunctionPtr(const char* name, void* ptr) {
414 static volatile HMODULE ntdll = NULL;
416 if (!ntdll) {
417 HMODULE ntdll_local = ::GetModuleHandle(sandbox::kNtdllName);
418 // Use PEImage to sanity-check that we have a valid ntdll handle.
419 base::win::PEImage ntdll_peimage(ntdll_local);
420 CHECK_NT(ntdll_peimage.VerifyMagic());
421 // Race-safe way to set static ntdll.
422 ::InterlockedCompareExchangePointer(
423 reinterpret_cast<PVOID volatile*>(&ntdll), ntdll_local, NULL);
427 CHECK_NT(ntdll);
428 FARPROC* function_ptr = reinterpret_cast<FARPROC*>(ptr);
429 *function_ptr = ::GetProcAddress(ntdll, name);
430 CHECK_NT(*function_ptr);