Revert "Fix broken channel icon in chrome://help on CrOS" and try again
[chromium-blink-merge.git] / sandbox / win / src / win_utils.cc
blobe09c680c700ab4ded195d5e5088f6f44e265a46b
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) {
160 // Check if it's a pipe. We can't query the attributes of a pipe.
161 if (IsPipe(full_path))
162 return ERROR_NOT_A_REPARSE_POINT;
164 base::string16 path;
165 bool nt_path = IsNTPath(full_path, &path);
166 bool has_drive = StartsWithDriveLetter(path);
167 bool is_device_path = IsDevicePath(path, &path);
169 if (!has_drive && !is_device_path && !nt_path)
170 return ERROR_INVALID_NAME;
172 bool added_implied_device = false;
173 if (!has_drive) {
174 path = base::string16(kNTDotPrefix) + path;
175 added_implied_device = true;
178 base::string16::size_type last_pos = base::string16::npos;
179 bool passed_once = false;
181 do {
182 path = path.substr(0, last_pos);
184 DWORD attributes = ::GetFileAttributes(path.c_str());
185 if (INVALID_FILE_ATTRIBUTES == attributes) {
186 DWORD error = ::GetLastError();
187 if (error != ERROR_FILE_NOT_FOUND &&
188 error != ERROR_PATH_NOT_FOUND &&
189 error != ERROR_INVALID_NAME) {
190 // Unexpected error.
191 if (passed_once && added_implied_device &&
192 (path.rfind(L'\\') == kNTDotPrefixLen - 1)) {
193 break;
195 NOTREACHED_NT();
196 return error;
198 } else if (FILE_ATTRIBUTE_REPARSE_POINT & attributes) {
199 // This is a reparse point.
200 return ERROR_SUCCESS;
203 passed_once = true;
204 last_pos = path.rfind(L'\\');
205 } while (last_pos > 2); // Skip root dir.
207 return ERROR_NOT_A_REPARSE_POINT;
210 // We get a |full_path| of the forms accepted by IsReparsePoint(), and the name
211 // we'll get from |handle| will be \device\harddiskvolume1\some\foo\bar.
212 bool SameObject(HANDLE handle, const wchar_t* full_path) {
213 // Check if it's a pipe.
214 if (IsPipe(full_path))
215 return true;
217 base::string16 actual_path;
218 if (!GetPathFromHandle(handle, &actual_path))
219 return false;
221 base::string16 path(full_path);
222 DCHECK_NT(!path.empty());
224 // This may end with a backslash.
225 const wchar_t kBackslash = '\\';
226 if (path[path.length() - 1] == kBackslash)
227 path = path.substr(0, path.length() - 1);
229 // Perfect match (case-insesitive check).
230 if (EqualPath(actual_path, path))
231 return true;
233 bool nt_path = IsNTPath(path, &path);
234 bool has_drive = StartsWithDriveLetter(path);
236 if (!has_drive && nt_path) {
237 base::string16 simple_actual_path;
238 if (!IsDevicePath(actual_path, &simple_actual_path))
239 return false;
241 // Perfect match (case-insesitive check).
242 return (EqualPath(simple_actual_path, path));
245 if (!has_drive)
246 return false;
248 // We only need 3 chars, but let's alloc a buffer for four.
249 wchar_t drive[4] = {0};
250 wchar_t vol_name[MAX_PATH];
251 memcpy(drive, &path[0], 2 * sizeof(*drive));
253 // We'll get a double null terminated string.
254 DWORD vol_length = ::QueryDosDeviceW(drive, vol_name, MAX_PATH);
255 if (vol_length < 2 || vol_length == MAX_PATH)
256 return false;
258 // Ignore the nulls at the end.
259 vol_length = static_cast<DWORD>(wcslen(vol_name));
261 // The two paths should be the same length.
262 if (vol_length + path.size() - 2 != actual_path.size())
263 return false;
265 // Check up to the drive letter.
266 if (!EqualPath(actual_path, vol_name, vol_length))
267 return false;
269 // Check the path after the drive letter.
270 if (!EqualPath(actual_path, vol_length, path, 2))
271 return false;
273 return true;
276 // Paths like \Device\HarddiskVolume0\some\foo\bar are assumed to be already
277 // expanded.
278 bool ConvertToLongPath(base::string16* path) {
279 if (IsPipe(*path))
280 return true;
282 base::string16 temp_path;
283 if (IsDevicePath(*path, &temp_path))
284 return false;
286 bool is_nt_path = IsNTPath(temp_path, &temp_path);
287 bool added_implied_device = false;
288 if (!StartsWithDriveLetter(temp_path) && is_nt_path) {
289 temp_path = base::string16(kNTDotPrefix) + temp_path;
290 added_implied_device = true;
293 DWORD size = MAX_PATH;
294 scoped_ptr<wchar_t[]> long_path_buf(new wchar_t[size]);
296 DWORD return_value = ::GetLongPathName(temp_path.c_str(), long_path_buf.get(),
297 size);
298 while (return_value >= size) {
299 size *= 2;
300 long_path_buf.reset(new wchar_t[size]);
301 return_value = ::GetLongPathName(temp_path.c_str(), long_path_buf.get(),
302 size);
305 DWORD last_error = ::GetLastError();
306 if (0 == return_value && (ERROR_FILE_NOT_FOUND == last_error ||
307 ERROR_PATH_NOT_FOUND == last_error ||
308 ERROR_INVALID_NAME == last_error)) {
309 // The file does not exist, but maybe a sub path needs to be expanded.
310 base::string16::size_type last_slash = temp_path.rfind(L'\\');
311 if (base::string16::npos == last_slash)
312 return false;
314 base::string16 begin = temp_path.substr(0, last_slash);
315 base::string16 end = temp_path.substr(last_slash);
316 if (!ConvertToLongPath(&begin))
317 return false;
319 // Ok, it worked. Let's reset the return value.
320 temp_path = begin + end;
321 return_value = 1;
322 } else if (0 != return_value) {
323 temp_path = long_path_buf.get();
326 if (return_value != 0) {
327 if (added_implied_device)
328 RemoveImpliedDevice(&temp_path);
330 if (is_nt_path) {
331 *path = kNTPrefix;
332 *path += temp_path;
333 } else {
334 *path = temp_path;
337 return true;
340 return false;
343 bool GetPathFromHandle(HANDLE handle, base::string16* path) {
344 NtQueryObjectFunction NtQueryObject = NULL;
345 ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject);
347 OBJECT_NAME_INFORMATION initial_buffer;
348 OBJECT_NAME_INFORMATION* name = &initial_buffer;
349 ULONG size = sizeof(initial_buffer);
350 // Query the name information a first time to get the size of the name.
351 // Windows XP requires that the size of the buffer passed in here be != 0.
352 NTSTATUS status = NtQueryObject(handle, ObjectNameInformation, name, size,
353 &size);
355 scoped_ptr<BYTE[]> name_ptr;
356 if (size) {
357 name_ptr.reset(new BYTE[size]);
358 name = reinterpret_cast<OBJECT_NAME_INFORMATION*>(name_ptr.get());
360 // Query the name information a second time to get the name of the
361 // object referenced by the handle.
362 status = NtQueryObject(handle, ObjectNameInformation, name, size, &size);
365 if (STATUS_SUCCESS != status)
366 return false;
368 path->assign(name->ObjectName.Buffer, name->ObjectName.Length /
369 sizeof(name->ObjectName.Buffer[0]));
370 return true;
373 bool GetNtPathFromWin32Path(const base::string16& path,
374 base::string16* nt_path) {
375 HANDLE file = ::CreateFileW(path.c_str(), 0,
376 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
377 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
378 if (file == INVALID_HANDLE_VALUE)
379 return false;
380 bool rv = GetPathFromHandle(file, nt_path);
381 ::CloseHandle(file);
382 return rv;
385 bool WriteProtectedChildMemory(HANDLE child_process, void* address,
386 const void* buffer, size_t length) {
387 // First, remove the protections.
388 DWORD old_protection;
389 if (!::VirtualProtectEx(child_process, address, length,
390 PAGE_WRITECOPY, &old_protection))
391 return false;
393 SIZE_T written;
394 bool ok = ::WriteProcessMemory(child_process, address, buffer, length,
395 &written) && (length == written);
397 // Always attempt to restore the original protection.
398 if (!::VirtualProtectEx(child_process, address, length,
399 old_protection, &old_protection))
400 return false;
402 return ok;
405 }; // namespace sandbox
407 void ResolveNTFunctionPtr(const char* name, void* ptr) {
408 static volatile HMODULE ntdll = NULL;
410 if (!ntdll) {
411 HMODULE ntdll_local = ::GetModuleHandle(sandbox::kNtdllName);
412 // Use PEImage to sanity-check that we have a valid ntdll handle.
413 base::win::PEImage ntdll_peimage(ntdll_local);
414 CHECK_NT(ntdll_peimage.VerifyMagic());
415 // Race-safe way to set static ntdll.
416 ::InterlockedCompareExchangePointer(
417 reinterpret_cast<PVOID volatile*>(&ntdll), ntdll_local, NULL);
421 CHECK_NT(ntdll);
422 FARPROC* function_ptr = reinterpret_cast<FARPROC*>(ptr);
423 *function_ptr = ::GetProcAddress(ntdll, name);
424 CHECK_NT(*function_ptr);