1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/security/sandbox/win/src/win_utils.cc Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,323 @@ 1.4 +// Copyright (c) 2011 The Chromium Authors. All rights reserved. 1.5 +// Use of this source code is governed by a BSD-style license that can be 1.6 +// found in the LICENSE file. 1.7 + 1.8 +#include "sandbox/win/src/win_utils.h" 1.9 + 1.10 +#include <map> 1.11 + 1.12 +#include "base/logging.h" 1.13 +#include "base/memory/scoped_ptr.h" 1.14 +#include "sandbox/win/src/internal_types.h" 1.15 +#include "sandbox/win/src/nt_internals.h" 1.16 + 1.17 +namespace { 1.18 + 1.19 +// Holds the information about a known registry key. 1.20 +struct KnownReservedKey { 1.21 + const wchar_t* name; 1.22 + HKEY key; 1.23 +}; 1.24 + 1.25 +// Contains all the known registry key by name and by handle. 1.26 +const KnownReservedKey kKnownKey[] = { 1.27 + { L"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT }, 1.28 + { L"HKEY_CURRENT_USER", HKEY_CURRENT_USER }, 1.29 + { L"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE}, 1.30 + { L"HKEY_USERS", HKEY_USERS}, 1.31 + { L"HKEY_PERFORMANCE_DATA", HKEY_PERFORMANCE_DATA}, 1.32 + { L"HKEY_PERFORMANCE_TEXT", HKEY_PERFORMANCE_TEXT}, 1.33 + { L"HKEY_PERFORMANCE_NLSTEXT", HKEY_PERFORMANCE_NLSTEXT}, 1.34 + { L"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG}, 1.35 + { L"HKEY_DYN_DATA", HKEY_DYN_DATA} 1.36 +}; 1.37 + 1.38 +// Returns true if the provided path points to a pipe. 1.39 +bool IsPipe(const std::wstring& path) { 1.40 + size_t start = 0; 1.41 + if (0 == path.compare(0, sandbox::kNTPrefixLen, sandbox::kNTPrefix)) 1.42 + start = sandbox::kNTPrefixLen; 1.43 + 1.44 + const wchar_t kPipe[] = L"pipe\\"; 1.45 + return (0 == path.compare(start, arraysize(kPipe) - 1, kPipe)); 1.46 +} 1.47 + 1.48 +} // namespace 1.49 + 1.50 +namespace sandbox { 1.51 + 1.52 +HKEY GetReservedKeyFromName(const std::wstring& name) { 1.53 + for (size_t i = 0; i < arraysize(kKnownKey); ++i) { 1.54 + if (name == kKnownKey[i].name) 1.55 + return kKnownKey[i].key; 1.56 + } 1.57 + 1.58 + return NULL; 1.59 +} 1.60 + 1.61 +bool ResolveRegistryName(std::wstring name, std::wstring* resolved_name) { 1.62 + for (size_t i = 0; i < arraysize(kKnownKey); ++i) { 1.63 + if (name.find(kKnownKey[i].name) == 0) { 1.64 + HKEY key; 1.65 + DWORD disposition; 1.66 + if (ERROR_SUCCESS != ::RegCreateKeyEx(kKnownKey[i].key, L"", 0, NULL, 0, 1.67 + MAXIMUM_ALLOWED, NULL, &key, 1.68 + &disposition)) 1.69 + return false; 1.70 + 1.71 + bool result = GetPathFromHandle(key, resolved_name); 1.72 + ::RegCloseKey(key); 1.73 + 1.74 + if (!result) 1.75 + return false; 1.76 + 1.77 + *resolved_name += name.substr(wcslen(kKnownKey[i].name)); 1.78 + return true; 1.79 + } 1.80 + } 1.81 + 1.82 + return false; 1.83 +} 1.84 + 1.85 +DWORD IsReparsePoint(const std::wstring& full_path, bool* result) { 1.86 + std::wstring path = full_path; 1.87 + 1.88 + // Remove the nt prefix. 1.89 + if (0 == path.compare(0, kNTPrefixLen, kNTPrefix)) 1.90 + path = path.substr(kNTPrefixLen); 1.91 + 1.92 + // Check if it's a pipe. We can't query the attributes of a pipe. 1.93 + if (IsPipe(path)) { 1.94 + *result = FALSE; 1.95 + return ERROR_SUCCESS; 1.96 + } 1.97 + 1.98 + std::wstring::size_type last_pos = std::wstring::npos; 1.99 + 1.100 + do { 1.101 + path = path.substr(0, last_pos); 1.102 + 1.103 + DWORD attributes = ::GetFileAttributes(path.c_str()); 1.104 + if (INVALID_FILE_ATTRIBUTES == attributes) { 1.105 + DWORD error = ::GetLastError(); 1.106 + if (error != ERROR_FILE_NOT_FOUND && 1.107 + error != ERROR_PATH_NOT_FOUND && 1.108 + error != ERROR_INVALID_NAME) { 1.109 + // Unexpected error. 1.110 + NOTREACHED(); 1.111 + return error; 1.112 + } 1.113 + } else if (FILE_ATTRIBUTE_REPARSE_POINT & attributes) { 1.114 + // This is a reparse point. 1.115 + *result = true; 1.116 + return ERROR_SUCCESS; 1.117 + } 1.118 + 1.119 + last_pos = path.rfind(L'\\'); 1.120 + } while (last_pos != std::wstring::npos); 1.121 + 1.122 + *result = false; 1.123 + return ERROR_SUCCESS; 1.124 +} 1.125 + 1.126 +// We get a |full_path| of the form \??\c:\some\foo\bar, and the name that 1.127 +// we'll get from |handle| will be \device\harddiskvolume1\some\foo\bar. 1.128 +bool SameObject(HANDLE handle, const wchar_t* full_path) { 1.129 + std::wstring path(full_path); 1.130 + DCHECK(!path.empty()); 1.131 + 1.132 + // Check if it's a pipe. 1.133 + if (IsPipe(path)) 1.134 + return true; 1.135 + 1.136 + std::wstring actual_path; 1.137 + if (!GetPathFromHandle(handle, &actual_path)) 1.138 + return false; 1.139 + 1.140 + // This may end with a backslash. 1.141 + const wchar_t kBackslash = '\\'; 1.142 + if (path[path.length() - 1] == kBackslash) 1.143 + path = path.substr(0, path.length() - 1); 1.144 + 1.145 + // Perfect match (case-insesitive check). 1.146 + if (0 == _wcsicmp(actual_path.c_str(), path.c_str())) 1.147 + return true; 1.148 + 1.149 + // Look for the drive letter. 1.150 + size_t colon_pos = path.find(L':'); 1.151 + if (colon_pos == 0 || colon_pos == std::wstring::npos) 1.152 + return false; 1.153 + 1.154 + // Only one character for the drive. 1.155 + if (colon_pos > 1 && path[colon_pos - 2] != kBackslash) 1.156 + return false; 1.157 + 1.158 + // We only need 3 chars, but let's alloc a buffer for four. 1.159 + wchar_t drive[4] = {0}; 1.160 + wchar_t vol_name[MAX_PATH]; 1.161 + memcpy(drive, &path[colon_pos - 1], 2 * sizeof(*drive)); 1.162 + 1.163 + // We'll get a double null terminated string. 1.164 + DWORD vol_length = ::QueryDosDeviceW(drive, vol_name, MAX_PATH); 1.165 + if (vol_length < 2 || vol_length == MAX_PATH) 1.166 + return false; 1.167 + 1.168 + // Ignore the nulls at the end. 1.169 + vol_length = static_cast<DWORD>(wcslen(vol_name)); 1.170 + 1.171 + // The two paths should be the same length. 1.172 + if (vol_length + path.size() - (colon_pos + 1) != actual_path.size()) 1.173 + return false; 1.174 + 1.175 + // Check up to the drive letter. 1.176 + if (0 != _wcsnicmp(actual_path.c_str(), vol_name, vol_length)) 1.177 + return false; 1.178 + 1.179 + // Check the path after the drive letter. 1.180 + if (0 != _wcsicmp(&actual_path[vol_length], &path[colon_pos + 1])) 1.181 + return false; 1.182 + 1.183 + return true; 1.184 +} 1.185 + 1.186 +bool ConvertToLongPath(const std::wstring& short_path, 1.187 + std::wstring* long_path) { 1.188 + // Check if the path is a NT path. 1.189 + bool is_nt_path = false; 1.190 + std::wstring path = short_path; 1.191 + if (0 == path.compare(0, kNTPrefixLen, kNTPrefix)) { 1.192 + path = path.substr(kNTPrefixLen); 1.193 + is_nt_path = true; 1.194 + } 1.195 + 1.196 + DWORD size = MAX_PATH; 1.197 + scoped_ptr<wchar_t[]> long_path_buf(new wchar_t[size]); 1.198 + 1.199 + DWORD return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(), 1.200 + size); 1.201 + while (return_value >= size) { 1.202 + size *= 2; 1.203 + long_path_buf.reset(new wchar_t[size]); 1.204 + return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(), size); 1.205 + } 1.206 + 1.207 + DWORD last_error = ::GetLastError(); 1.208 + if (0 == return_value && (ERROR_FILE_NOT_FOUND == last_error || 1.209 + ERROR_PATH_NOT_FOUND == last_error || 1.210 + ERROR_INVALID_NAME == last_error)) { 1.211 + // The file does not exist, but maybe a sub path needs to be expanded. 1.212 + std::wstring::size_type last_slash = path.rfind(L'\\'); 1.213 + if (std::wstring::npos == last_slash) 1.214 + return false; 1.215 + 1.216 + std::wstring begin = path.substr(0, last_slash); 1.217 + std::wstring end = path.substr(last_slash); 1.218 + if (!ConvertToLongPath(begin, &begin)) 1.219 + return false; 1.220 + 1.221 + // Ok, it worked. Let's reset the return value. 1.222 + path = begin + end; 1.223 + return_value = 1; 1.224 + } else if (0 != return_value) { 1.225 + path = long_path_buf.get(); 1.226 + } 1.227 + 1.228 + if (return_value != 0) { 1.229 + if (is_nt_path) { 1.230 + *long_path = kNTPrefix; 1.231 + *long_path += path; 1.232 + } else { 1.233 + *long_path = path; 1.234 + } 1.235 + 1.236 + return true; 1.237 + } 1.238 + 1.239 + return false; 1.240 +} 1.241 + 1.242 +bool GetPathFromHandle(HANDLE handle, std::wstring* path) { 1.243 + NtQueryObjectFunction NtQueryObject = NULL; 1.244 + ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject); 1.245 + 1.246 + OBJECT_NAME_INFORMATION initial_buffer; 1.247 + OBJECT_NAME_INFORMATION* name = &initial_buffer; 1.248 + ULONG size = sizeof(initial_buffer); 1.249 + // Query the name information a first time to get the size of the name. 1.250 + NTSTATUS status = NtQueryObject(handle, ObjectNameInformation, name, size, 1.251 + &size); 1.252 + 1.253 + scoped_ptr<OBJECT_NAME_INFORMATION> name_ptr; 1.254 + if (size) { 1.255 + name = reinterpret_cast<OBJECT_NAME_INFORMATION*>(new BYTE[size]); 1.256 + name_ptr.reset(name); 1.257 + 1.258 + // Query the name information a second time to get the name of the 1.259 + // object referenced by the handle. 1.260 + status = NtQueryObject(handle, ObjectNameInformation, name, size, &size); 1.261 + } 1.262 + 1.263 + if (STATUS_SUCCESS != status) 1.264 + return false; 1.265 + 1.266 + path->assign(name->ObjectName.Buffer, name->ObjectName.Length / 1.267 + sizeof(name->ObjectName.Buffer[0])); 1.268 + return true; 1.269 +} 1.270 + 1.271 +bool GetNtPathFromWin32Path(const std::wstring& path, std::wstring* nt_path) { 1.272 + HANDLE file = ::CreateFileW(path.c_str(), 0, 1.273 + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, 1.274 + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); 1.275 + if (file == INVALID_HANDLE_VALUE) 1.276 + return false; 1.277 + bool rv = GetPathFromHandle(file, nt_path); 1.278 + ::CloseHandle(file); 1.279 + return rv; 1.280 +} 1.281 + 1.282 +bool WriteProtectedChildMemory(HANDLE child_process, void* address, 1.283 + const void* buffer, size_t length) { 1.284 + // First, remove the protections. 1.285 + DWORD old_protection; 1.286 + if (!::VirtualProtectEx(child_process, address, length, 1.287 + PAGE_WRITECOPY, &old_protection)) 1.288 + return false; 1.289 + 1.290 + SIZE_T written; 1.291 + bool ok = ::WriteProcessMemory(child_process, address, buffer, length, 1.292 + &written) && (length == written); 1.293 + 1.294 + // Always attempt to restore the original protection. 1.295 + if (!::VirtualProtectEx(child_process, address, length, 1.296 + old_protection, &old_protection)) 1.297 + return false; 1.298 + 1.299 + return ok; 1.300 +} 1.301 + 1.302 +}; // namespace sandbox 1.303 + 1.304 +// TODO(jschuh): http://crbug.com/11789 1.305 +// I'm guessing we have a race where some "security" software is messing 1.306 +// with ntdll/imports underneath us. So, we retry a few times, and in the 1.307 +// worst case we sleep briefly before a few more attempts. (Normally sleeping 1.308 +// would be very bad, but it's better than crashing in this case.) 1.309 +void ResolveNTFunctionPtr(const char* name, void* ptr) { 1.310 + const int max_tries = 5; 1.311 + const int sleep_threshold = 2; 1.312 + 1.313 + static HMODULE ntdll = ::GetModuleHandle(sandbox::kNtdllName); 1.314 + 1.315 + FARPROC* function_ptr = reinterpret_cast<FARPROC*>(ptr); 1.316 + *function_ptr = ::GetProcAddress(ntdll, name); 1.317 + 1.318 + for (int tries = 1; !(*function_ptr) && tries < max_tries; ++tries) { 1.319 + if (tries >= sleep_threshold) 1.320 + ::Sleep(1); 1.321 + ntdll = ::GetModuleHandle(sandbox::kNtdllName); 1.322 + *function_ptr = ::GetProcAddress(ntdll, name); 1.323 + } 1.324 + 1.325 + CHECK(*function_ptr); 1.326 +}