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