michael@0: // Copyright (c) 2011 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "sandbox/win/src/win_utils.h" michael@0: michael@0: #include michael@0: michael@0: #include "base/logging.h" michael@0: #include "base/memory/scoped_ptr.h" michael@0: #include "sandbox/win/src/internal_types.h" michael@0: #include "sandbox/win/src/nt_internals.h" michael@0: michael@0: namespace { michael@0: michael@0: // Holds the information about a known registry key. michael@0: struct KnownReservedKey { michael@0: const wchar_t* name; michael@0: HKEY key; michael@0: }; michael@0: michael@0: // Contains all the known registry key by name and by handle. michael@0: const KnownReservedKey kKnownKey[] = { michael@0: { L"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT }, michael@0: { L"HKEY_CURRENT_USER", HKEY_CURRENT_USER }, michael@0: { L"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE}, michael@0: { L"HKEY_USERS", HKEY_USERS}, michael@0: { L"HKEY_PERFORMANCE_DATA", HKEY_PERFORMANCE_DATA}, michael@0: { L"HKEY_PERFORMANCE_TEXT", HKEY_PERFORMANCE_TEXT}, michael@0: { L"HKEY_PERFORMANCE_NLSTEXT", HKEY_PERFORMANCE_NLSTEXT}, michael@0: { L"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG}, michael@0: { L"HKEY_DYN_DATA", HKEY_DYN_DATA} michael@0: }; michael@0: michael@0: // Returns true if the provided path points to a pipe. michael@0: bool IsPipe(const std::wstring& path) { michael@0: size_t start = 0; michael@0: if (0 == path.compare(0, sandbox::kNTPrefixLen, sandbox::kNTPrefix)) michael@0: start = sandbox::kNTPrefixLen; michael@0: michael@0: const wchar_t kPipe[] = L"pipe\\"; michael@0: return (0 == path.compare(start, arraysize(kPipe) - 1, kPipe)); michael@0: } michael@0: michael@0: } // namespace michael@0: michael@0: namespace sandbox { michael@0: michael@0: HKEY GetReservedKeyFromName(const std::wstring& name) { michael@0: for (size_t i = 0; i < arraysize(kKnownKey); ++i) { michael@0: if (name == kKnownKey[i].name) michael@0: return kKnownKey[i].key; michael@0: } michael@0: michael@0: return NULL; michael@0: } michael@0: michael@0: bool ResolveRegistryName(std::wstring name, std::wstring* resolved_name) { michael@0: for (size_t i = 0; i < arraysize(kKnownKey); ++i) { michael@0: if (name.find(kKnownKey[i].name) == 0) { michael@0: HKEY key; michael@0: DWORD disposition; michael@0: if (ERROR_SUCCESS != ::RegCreateKeyEx(kKnownKey[i].key, L"", 0, NULL, 0, michael@0: MAXIMUM_ALLOWED, NULL, &key, michael@0: &disposition)) michael@0: return false; michael@0: michael@0: bool result = GetPathFromHandle(key, resolved_name); michael@0: ::RegCloseKey(key); michael@0: michael@0: if (!result) michael@0: return false; michael@0: michael@0: *resolved_name += name.substr(wcslen(kKnownKey[i].name)); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: DWORD IsReparsePoint(const std::wstring& full_path, bool* result) { michael@0: std::wstring path = full_path; michael@0: michael@0: // Remove the nt prefix. michael@0: if (0 == path.compare(0, kNTPrefixLen, kNTPrefix)) michael@0: path = path.substr(kNTPrefixLen); michael@0: michael@0: // Check if it's a pipe. We can't query the attributes of a pipe. michael@0: if (IsPipe(path)) { michael@0: *result = FALSE; michael@0: return ERROR_SUCCESS; michael@0: } michael@0: michael@0: std::wstring::size_type last_pos = std::wstring::npos; michael@0: michael@0: do { michael@0: path = path.substr(0, last_pos); michael@0: michael@0: DWORD attributes = ::GetFileAttributes(path.c_str()); michael@0: if (INVALID_FILE_ATTRIBUTES == attributes) { michael@0: DWORD error = ::GetLastError(); michael@0: if (error != ERROR_FILE_NOT_FOUND && michael@0: error != ERROR_PATH_NOT_FOUND && michael@0: error != ERROR_INVALID_NAME) { michael@0: // Unexpected error. michael@0: NOTREACHED(); michael@0: return error; michael@0: } michael@0: } else if (FILE_ATTRIBUTE_REPARSE_POINT & attributes) { michael@0: // This is a reparse point. michael@0: *result = true; michael@0: return ERROR_SUCCESS; michael@0: } michael@0: michael@0: last_pos = path.rfind(L'\\'); michael@0: } while (last_pos != std::wstring::npos); michael@0: michael@0: *result = false; michael@0: return ERROR_SUCCESS; michael@0: } michael@0: michael@0: // We get a |full_path| of the form \??\c:\some\foo\bar, and the name that michael@0: // we'll get from |handle| will be \device\harddiskvolume1\some\foo\bar. michael@0: bool SameObject(HANDLE handle, const wchar_t* full_path) { michael@0: std::wstring path(full_path); michael@0: DCHECK(!path.empty()); michael@0: michael@0: // Check if it's a pipe. michael@0: if (IsPipe(path)) michael@0: return true; michael@0: michael@0: std::wstring actual_path; michael@0: if (!GetPathFromHandle(handle, &actual_path)) michael@0: return false; michael@0: michael@0: // This may end with a backslash. michael@0: const wchar_t kBackslash = '\\'; michael@0: if (path[path.length() - 1] == kBackslash) michael@0: path = path.substr(0, path.length() - 1); michael@0: michael@0: // Perfect match (case-insesitive check). michael@0: if (0 == _wcsicmp(actual_path.c_str(), path.c_str())) michael@0: return true; michael@0: michael@0: // Look for the drive letter. michael@0: size_t colon_pos = path.find(L':'); michael@0: if (colon_pos == 0 || colon_pos == std::wstring::npos) michael@0: return false; michael@0: michael@0: // Only one character for the drive. michael@0: if (colon_pos > 1 && path[colon_pos - 2] != kBackslash) michael@0: return false; michael@0: michael@0: // We only need 3 chars, but let's alloc a buffer for four. michael@0: wchar_t drive[4] = {0}; michael@0: wchar_t vol_name[MAX_PATH]; michael@0: memcpy(drive, &path[colon_pos - 1], 2 * sizeof(*drive)); michael@0: michael@0: // We'll get a double null terminated string. michael@0: DWORD vol_length = ::QueryDosDeviceW(drive, vol_name, MAX_PATH); michael@0: if (vol_length < 2 || vol_length == MAX_PATH) michael@0: return false; michael@0: michael@0: // Ignore the nulls at the end. michael@0: vol_length = static_cast(wcslen(vol_name)); michael@0: michael@0: // The two paths should be the same length. michael@0: if (vol_length + path.size() - (colon_pos + 1) != actual_path.size()) michael@0: return false; michael@0: michael@0: // Check up to the drive letter. michael@0: if (0 != _wcsnicmp(actual_path.c_str(), vol_name, vol_length)) michael@0: return false; michael@0: michael@0: // Check the path after the drive letter. michael@0: if (0 != _wcsicmp(&actual_path[vol_length], &path[colon_pos + 1])) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool ConvertToLongPath(const std::wstring& short_path, michael@0: std::wstring* long_path) { michael@0: // Check if the path is a NT path. michael@0: bool is_nt_path = false; michael@0: std::wstring path = short_path; michael@0: if (0 == path.compare(0, kNTPrefixLen, kNTPrefix)) { michael@0: path = path.substr(kNTPrefixLen); michael@0: is_nt_path = true; michael@0: } michael@0: michael@0: DWORD size = MAX_PATH; michael@0: scoped_ptr long_path_buf(new wchar_t[size]); michael@0: michael@0: DWORD return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(), michael@0: size); michael@0: while (return_value >= size) { michael@0: size *= 2; michael@0: long_path_buf.reset(new wchar_t[size]); michael@0: return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(), size); michael@0: } michael@0: michael@0: DWORD last_error = ::GetLastError(); michael@0: if (0 == return_value && (ERROR_FILE_NOT_FOUND == last_error || michael@0: ERROR_PATH_NOT_FOUND == last_error || michael@0: ERROR_INVALID_NAME == last_error)) { michael@0: // The file does not exist, but maybe a sub path needs to be expanded. michael@0: std::wstring::size_type last_slash = path.rfind(L'\\'); michael@0: if (std::wstring::npos == last_slash) michael@0: return false; michael@0: michael@0: std::wstring begin = path.substr(0, last_slash); michael@0: std::wstring end = path.substr(last_slash); michael@0: if (!ConvertToLongPath(begin, &begin)) michael@0: return false; michael@0: michael@0: // Ok, it worked. Let's reset the return value. michael@0: path = begin + end; michael@0: return_value = 1; michael@0: } else if (0 != return_value) { michael@0: path = long_path_buf.get(); michael@0: } michael@0: michael@0: if (return_value != 0) { michael@0: if (is_nt_path) { michael@0: *long_path = kNTPrefix; michael@0: *long_path += path; michael@0: } else { michael@0: *long_path = path; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool GetPathFromHandle(HANDLE handle, std::wstring* path) { michael@0: NtQueryObjectFunction NtQueryObject = NULL; michael@0: ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject); michael@0: michael@0: OBJECT_NAME_INFORMATION initial_buffer; michael@0: OBJECT_NAME_INFORMATION* name = &initial_buffer; michael@0: ULONG size = sizeof(initial_buffer); michael@0: // Query the name information a first time to get the size of the name. michael@0: NTSTATUS status = NtQueryObject(handle, ObjectNameInformation, name, size, michael@0: &size); michael@0: michael@0: scoped_ptr name_ptr; michael@0: if (size) { michael@0: name = reinterpret_cast(new BYTE[size]); michael@0: name_ptr.reset(name); michael@0: michael@0: // Query the name information a second time to get the name of the michael@0: // object referenced by the handle. michael@0: status = NtQueryObject(handle, ObjectNameInformation, name, size, &size); michael@0: } michael@0: michael@0: if (STATUS_SUCCESS != status) michael@0: return false; michael@0: michael@0: path->assign(name->ObjectName.Buffer, name->ObjectName.Length / michael@0: sizeof(name->ObjectName.Buffer[0])); michael@0: return true; michael@0: } michael@0: michael@0: bool GetNtPathFromWin32Path(const std::wstring& path, std::wstring* nt_path) { michael@0: HANDLE file = ::CreateFileW(path.c_str(), 0, michael@0: FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, michael@0: OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); michael@0: if (file == INVALID_HANDLE_VALUE) michael@0: return false; michael@0: bool rv = GetPathFromHandle(file, nt_path); michael@0: ::CloseHandle(file); michael@0: return rv; michael@0: } michael@0: michael@0: bool WriteProtectedChildMemory(HANDLE child_process, void* address, michael@0: const void* buffer, size_t length) { michael@0: // First, remove the protections. michael@0: DWORD old_protection; michael@0: if (!::VirtualProtectEx(child_process, address, length, michael@0: PAGE_WRITECOPY, &old_protection)) michael@0: return false; michael@0: michael@0: SIZE_T written; michael@0: bool ok = ::WriteProcessMemory(child_process, address, buffer, length, michael@0: &written) && (length == written); michael@0: michael@0: // Always attempt to restore the original protection. michael@0: if (!::VirtualProtectEx(child_process, address, length, michael@0: old_protection, &old_protection)) michael@0: return false; michael@0: michael@0: return ok; michael@0: } michael@0: michael@0: }; // namespace sandbox michael@0: michael@0: // TODO(jschuh): http://crbug.com/11789 michael@0: // I'm guessing we have a race where some "security" software is messing michael@0: // with ntdll/imports underneath us. So, we retry a few times, and in the michael@0: // worst case we sleep briefly before a few more attempts. (Normally sleeping michael@0: // would be very bad, but it's better than crashing in this case.) michael@0: void ResolveNTFunctionPtr(const char* name, void* ptr) { michael@0: const int max_tries = 5; michael@0: const int sleep_threshold = 2; michael@0: michael@0: static HMODULE ntdll = ::GetModuleHandle(sandbox::kNtdllName); michael@0: michael@0: FARPROC* function_ptr = reinterpret_cast(ptr); michael@0: *function_ptr = ::GetProcAddress(ntdll, name); michael@0: michael@0: for (int tries = 1; !(*function_ptr) && tries < max_tries; ++tries) { michael@0: if (tries >= sleep_threshold) michael@0: ::Sleep(1); michael@0: ntdll = ::GetModuleHandle(sandbox::kNtdllName); michael@0: *function_ptr = ::GetProcAddress(ntdll, name); michael@0: } michael@0: michael@0: CHECK(*function_ptr); michael@0: }