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 <map> 8 9 #include "base/memory/scoped_ptr.h" 10 #include "base/win/pe_image.h" 11 #include "sandbox/win/src/internal_types.h" 12 #include "sandbox/win/src/nt_internals.h" 13 #include "sandbox/win/src/sandbox_nt_util.h" 14 15 namespace { 16 17 // Holds the information about a known registry key. 18 struct KnownReservedKey { 19 const wchar_t* name; 20 HKEY key; 21 }; 22 23 // Contains all the known registry key by name and by handle. 24 const KnownReservedKey kKnownKey[] = { 25 { L"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT }, 26 { L"HKEY_CURRENT_USER", HKEY_CURRENT_USER }, 27 { L"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE}, 28 { L"HKEY_USERS", HKEY_USERS}, 29 { L"HKEY_PERFORMANCE_DATA", HKEY_PERFORMANCE_DATA}, 30 { L"HKEY_PERFORMANCE_TEXT", HKEY_PERFORMANCE_TEXT}, 31 { L"HKEY_PERFORMANCE_NLSTEXT", HKEY_PERFORMANCE_NLSTEXT}, 32 { L"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG}, 33 { L"HKEY_DYN_DATA", HKEY_DYN_DATA} 34 }; 35 36 // Returns true if the provided path points to a pipe. 37 bool IsPipe(const base::string16& path) { 38 size_t start = 0; 39 if (0 == path.compare(0, sandbox::kNTPrefixLen, sandbox::kNTPrefix)) 40 start = sandbox::kNTPrefixLen; 41 42 const wchar_t kPipe[] = L"pipe\\"; 43 return (0 == path.compare(start, arraysize(kPipe) - 1, kPipe)); 44 } 45 46 } // namespace 47 48 namespace sandbox { 49 50 HKEY GetReservedKeyFromName(const base::string16& name) { 51 for (size_t i = 0; i < arraysize(kKnownKey); ++i) { 52 if (name == kKnownKey[i].name) 53 return kKnownKey[i].key; 54 } 55 56 return NULL; 57 } 58 59 bool ResolveRegistryName(base::string16 name, base::string16* resolved_name) { 60 for (size_t i = 0; i < arraysize(kKnownKey); ++i) { 61 if (name.find(kKnownKey[i].name) == 0) { 62 HKEY key; 63 DWORD disposition; 64 if (ERROR_SUCCESS != ::RegCreateKeyEx(kKnownKey[i].key, L"", 0, NULL, 0, 65 MAXIMUM_ALLOWED, NULL, &key, 66 &disposition)) 67 return false; 68 69 bool result = GetPathFromHandle(key, resolved_name); 70 ::RegCloseKey(key); 71 72 if (!result) 73 return false; 74 75 *resolved_name += name.substr(wcslen(kKnownKey[i].name)); 76 return true; 77 } 78 } 79 80 return false; 81 } 82 83 DWORD IsReparsePoint(const base::string16& full_path, bool* result) { 84 base::string16 path = full_path; 85 86 // Remove the nt prefix. 87 if (0 == path.compare(0, kNTPrefixLen, kNTPrefix)) 88 path = path.substr(kNTPrefixLen); 89 90 // Check if it's a pipe. We can't query the attributes of a pipe. 91 if (IsPipe(path)) { 92 *result = FALSE; 93 return ERROR_SUCCESS; 94 } 95 96 base::string16::size_type last_pos = base::string16::npos; 97 98 do { 99 path = path.substr(0, last_pos); 100 101 DWORD attributes = ::GetFileAttributes(path.c_str()); 102 if (INVALID_FILE_ATTRIBUTES == attributes) { 103 DWORD error = ::GetLastError(); 104 if (error != ERROR_FILE_NOT_FOUND && 105 error != ERROR_PATH_NOT_FOUND && 106 error != ERROR_INVALID_NAME) { 107 // Unexpected error. 108 NOTREACHED_NT(); 109 return error; 110 } 111 } else if (FILE_ATTRIBUTE_REPARSE_POINT & attributes) { 112 // This is a reparse point. 113 *result = true; 114 return ERROR_SUCCESS; 115 } 116 117 last_pos = path.rfind(L'\\'); 118 } while (last_pos > 2); // Skip root dir. 119 120 *result = false; 121 return ERROR_SUCCESS; 122 } 123 124 // We get a |full_path| of the form \??\c:\some\foo\bar, and the name that 125 // we'll get from |handle| will be \device\harddiskvolume1\some\foo\bar. 126 bool SameObject(HANDLE handle, const wchar_t* full_path) { 127 base::string16 path(full_path); 128 DCHECK_NT(!path.empty()); 129 130 // Check if it's a pipe. 131 if (IsPipe(path)) 132 return true; 133 134 base::string16 actual_path; 135 if (!GetPathFromHandle(handle, &actual_path)) 136 return false; 137 138 // This may end with a backslash. 139 const wchar_t kBackslash = '\\'; 140 if (path[path.length() - 1] == kBackslash) 141 path = path.substr(0, path.length() - 1); 142 143 // Perfect match (case-insesitive check). 144 if (0 == _wcsicmp(actual_path.c_str(), path.c_str())) 145 return true; 146 147 // Look for the drive letter. 148 size_t colon_pos = path.find(L':'); 149 if (colon_pos == 0 || colon_pos == base::string16::npos) 150 return false; 151 152 // Only one character for the drive. 153 if (colon_pos > 1 && path[colon_pos - 2] != kBackslash) 154 return false; 155 156 // We only need 3 chars, but let's alloc a buffer for four. 157 wchar_t drive[4] = {0}; 158 wchar_t vol_name[MAX_PATH]; 159 memcpy(drive, &path[colon_pos - 1], 2 * sizeof(*drive)); 160 161 // We'll get a double null terminated string. 162 DWORD vol_length = ::QueryDosDeviceW(drive, vol_name, MAX_PATH); 163 if (vol_length < 2 || vol_length == MAX_PATH) 164 return false; 165 166 // Ignore the nulls at the end. 167 vol_length = static_cast<DWORD>(wcslen(vol_name)); 168 169 // The two paths should be the same length. 170 if (vol_length + path.size() - (colon_pos + 1) != actual_path.size()) 171 return false; 172 173 // Check up to the drive letter. 174 if (0 != _wcsnicmp(actual_path.c_str(), vol_name, vol_length)) 175 return false; 176 177 // Check the path after the drive letter. 178 if (0 != _wcsicmp(&actual_path[vol_length], &path[colon_pos + 1])) 179 return false; 180 181 return true; 182 } 183 184 bool ConvertToLongPath(const base::string16& short_path, 185 base::string16* long_path) { 186 // Check if the path is a NT path. 187 bool is_nt_path = false; 188 base::string16 path = short_path; 189 if (0 == path.compare(0, kNTPrefixLen, kNTPrefix)) { 190 path = path.substr(kNTPrefixLen); 191 is_nt_path = true; 192 } 193 194 DWORD size = MAX_PATH; 195 scoped_ptr<wchar_t[]> long_path_buf(new wchar_t[size]); 196 197 DWORD return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(), 198 size); 199 while (return_value >= size) { 200 size *= 2; 201 long_path_buf.reset(new wchar_t[size]); 202 return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(), size); 203 } 204 205 DWORD last_error = ::GetLastError(); 206 if (0 == return_value && (ERROR_FILE_NOT_FOUND == last_error || 207 ERROR_PATH_NOT_FOUND == last_error || 208 ERROR_INVALID_NAME == last_error)) { 209 // The file does not exist, but maybe a sub path needs to be expanded. 210 base::string16::size_type last_slash = path.rfind(L'\\'); 211 if (base::string16::npos == last_slash) 212 return false; 213 214 base::string16 begin = path.substr(0, last_slash); 215 base::string16 end = path.substr(last_slash); 216 if (!ConvertToLongPath(begin, &begin)) 217 return false; 218 219 // Ok, it worked. Let's reset the return value. 220 path = begin + end; 221 return_value = 1; 222 } else if (0 != return_value) { 223 path = long_path_buf.get(); 224 } 225 226 if (return_value != 0) { 227 if (is_nt_path) { 228 *long_path = kNTPrefix; 229 *long_path += path; 230 } else { 231 *long_path = path; 232 } 233 234 return true; 235 } 236 237 return false; 238 } 239 240 bool GetPathFromHandle(HANDLE handle, base::string16* path) { 241 NtQueryObjectFunction NtQueryObject = NULL; 242 ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject); 243 244 OBJECT_NAME_INFORMATION initial_buffer; 245 OBJECT_NAME_INFORMATION* name = &initial_buffer; 246 ULONG size = sizeof(initial_buffer); 247 // Query the name information a first time to get the size of the name. 248 NTSTATUS status = NtQueryObject(handle, ObjectNameInformation, name, size, 249 &size); 250 251 scoped_ptr<OBJECT_NAME_INFORMATION> name_ptr; 252 if (size) { 253 name = reinterpret_cast<OBJECT_NAME_INFORMATION*>(new BYTE[size]); 254 name_ptr.reset(name); 255 256 // Query the name information a second time to get the name of the 257 // object referenced by the handle. 258 status = NtQueryObject(handle, ObjectNameInformation, name, size, &size); 259 } 260 261 if (STATUS_SUCCESS != status) 262 return false; 263 264 path->assign(name->ObjectName.Buffer, name->ObjectName.Length / 265 sizeof(name->ObjectName.Buffer[0])); 266 return true; 267 } 268 269 bool GetNtPathFromWin32Path(const base::string16& path, 270 base::string16* nt_path) { 271 HANDLE file = ::CreateFileW(path.c_str(), 0, 272 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, 273 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); 274 if (file == INVALID_HANDLE_VALUE) 275 return false; 276 bool rv = GetPathFromHandle(file, nt_path); 277 ::CloseHandle(file); 278 return rv; 279 } 280 281 bool WriteProtectedChildMemory(HANDLE child_process, void* address, 282 const void* buffer, size_t length) { 283 // First, remove the protections. 284 DWORD old_protection; 285 if (!::VirtualProtectEx(child_process, address, length, 286 PAGE_WRITECOPY, &old_protection)) 287 return false; 288 289 SIZE_T written; 290 bool ok = ::WriteProcessMemory(child_process, address, buffer, length, 291 &written) && (length == written); 292 293 // Always attempt to restore the original protection. 294 if (!::VirtualProtectEx(child_process, address, length, 295 old_protection, &old_protection)) 296 return false; 297 298 return ok; 299 } 300 301 }; // namespace sandbox 302 303 void ResolveNTFunctionPtr(const char* name, void* ptr) { 304 static volatile HMODULE ntdll = NULL; 305 306 if (!ntdll) { 307 HMODULE ntdll_local = ::GetModuleHandle(sandbox::kNtdllName); 308 // Use PEImage to sanity-check that we have a valid ntdll handle. 309 base::win::PEImage ntdll_peimage(ntdll_local); 310 CHECK_NT(ntdll_peimage.VerifyMagic()); 311 // Race-safe way to set static ntdll. 312 ::InterlockedCompareExchangePointer( 313 reinterpret_cast<PVOID volatile*>(&ntdll), ntdll_local, NULL); 314 315 } 316 317 CHECK_NT(ntdll); 318 FARPROC* function_ptr = reinterpret_cast<FARPROC*>(ptr); 319 *function_ptr = ::GetProcAddress(ntdll, name); 320 CHECK_NT(*function_ptr); 321 } 322