| 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/src/win_utils.h" | |
| 6 | |
| 7 #include <map> | |
| 8 | |
| 9 #include "base/logging.h" | |
| 10 #include "base/memory/scoped_ptr.h" | |
| 11 #include "sandbox/src/internal_types.h" | |
| 12 #include "sandbox/src/nt_internals.h" | |
| 13 | |
| 14 namespace { | |
| 15 | |
| 16 // Holds the information about a known registry key. | |
| 17 struct KnownReservedKey { | |
| 18 const wchar_t* name; | |
| 19 HKEY key; | |
| 20 }; | |
| 21 | |
| 22 // Contains all the known registry key by name and by handle. | |
| 23 const 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. | |
| 36 bool IsPipe(const std::wstring& 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 | |
| 47 namespace sandbox { | |
| 48 | |
| 49 HKEY GetReservedKeyFromName(const std::wstring& 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 | |
| 58 bool ResolveRegistryName(std::wstring name, std::wstring* 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 | |
| 82 DWORD IsReparsePoint(const std::wstring& full_path, bool* result) { | |
| 83 std::wstring 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 std::wstring::size_type last_pos = std::wstring::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(); | |
| 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 != std::wstring::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. | |
| 125 bool SameObject(HANDLE handle, const wchar_t* full_path) { | |
| 126 std::wstring path(full_path); | |
| 127 DCHECK(!path.empty()); | |
| 128 | |
| 129 // Check if it's a pipe. | |
| 130 if (IsPipe(path)) | |
| 131 return true; | |
| 132 | |
| 133 std::wstring 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 == std::wstring::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 | |
| 183 bool ConvertToLongPath(const std::wstring& short_path, | |
| 184 std::wstring* long_path) { | |
| 185 // Check if the path is a NT path. | |
| 186 bool is_nt_path = false; | |
| 187 std::wstring 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_array<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 std::wstring::size_type last_slash = path.rfind(L'\\'); | |
| 210 if (std::wstring::npos == last_slash) | |
| 211 return false; | |
| 212 | |
| 213 std::wstring begin = path.substr(0, last_slash); | |
| 214 std::wstring 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 | |
| 239 bool GetPathFromHandle(HANDLE handle, std::wstring* 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 | |
| 268 bool GetNtPathFromWin32Path(const std::wstring& path, std::wstring* nt_path) { | |
| 269 HANDLE file = ::CreateFileW(path.c_str(), 0, | |
| 270 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, | |
| 271 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); | |
| 272 if (file == INVALID_HANDLE_VALUE) | |
| 273 return false; | |
| 274 bool rv = GetPathFromHandle(file, nt_path); | |
| 275 ::CloseHandle(file); | |
| 276 return rv; | |
| 277 } | |
| 278 | |
| 279 bool WriteProtectedChildMemory(HANDLE child_process, void* address, | |
| 280 const void* buffer, size_t length) { | |
| 281 // First, remove the protections. | |
| 282 DWORD old_protection; | |
| 283 if (!::VirtualProtectEx(child_process, address, length, | |
| 284 PAGE_WRITECOPY, &old_protection)) | |
| 285 return false; | |
| 286 | |
| 287 SIZE_T written; | |
| 288 bool ok = ::WriteProcessMemory(child_process, address, buffer, length, | |
| 289 &written) && (length == written); | |
| 290 | |
| 291 // Always attempt to restore the original protection. | |
| 292 if (!::VirtualProtectEx(child_process, address, length, | |
| 293 old_protection, &old_protection)) | |
| 294 return false; | |
| 295 | |
| 296 return ok; | |
| 297 } | |
| 298 | |
| 299 }; // namespace sandbox | |
| 300 | |
| 301 // TODO(jschuh): http://crbug.com/11789 | |
| 302 // I'm guessing we have a race where some "security" software is messing | |
| 303 // with ntdll/imports underneath us. So, we retry a few times, and in the | |
| 304 // worst case we sleep briefly before a few more attempts. (Normally sleeping | |
| 305 // would be very bad, but it's better than crashing in this case.) | |
| 306 void ResolveNTFunctionPtr(const char* name, void* ptr) { | |
| 307 const int max_tries = 5; | |
| 308 const int sleep_threshold = 2; | |
| 309 | |
| 310 static HMODULE ntdll = ::GetModuleHandle(sandbox::kNtdllName); | |
| 311 | |
| 312 FARPROC* function_ptr = reinterpret_cast<FARPROC*>(ptr); | |
| 313 *function_ptr = ::GetProcAddress(ntdll, name); | |
| 314 | |
| 315 for (int tries = 1; !(*function_ptr) && tries < max_tries; ++tries) { | |
| 316 if (tries >= sleep_threshold) | |
| 317 ::Sleep(1); | |
| 318 ntdll = ::GetModuleHandle(sandbox::kNtdllName); | |
| 319 *function_ptr = ::GetProcAddress(ntdll, name); | |
| 320 } | |
| 321 | |
| 322 CHECK(*function_ptr); | |
| 323 } | |
| OLD | NEW |