| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome_elf/create_file/chrome_create_file.h" |
| |
| #include <string> |
| |
| #include "base/strings/string16.h" |
| #include "chrome_elf/chrome_elf_constants.h" |
| #include "chrome_elf/chrome_elf_util.h" |
| #include "chrome_elf/ntdll_cache.h" |
| #include "sandbox/win/src/interception_internal.h" |
| #include "sandbox/win/src/nt_internals.h" |
| |
| namespace { |
| |
| // From ShlObj.h in the Windows SDK. |
| #define CSIDL_LOCAL_APPDATA 0x001c |
| |
| typedef BOOL (WINAPI *PathIsUNCFunction)( |
| IN LPCWSTR path); |
| |
| typedef BOOL (WINAPI *PathAppendFunction)( |
| IN LPWSTR path, |
| IN LPCWSTR more); |
| |
| typedef BOOL (WINAPI *PathIsPrefixFunction)( |
| IN LPCWSTR prefix, |
| IN LPCWSTR path); |
| |
| typedef LPCWSTR (WINAPI *PathFindFileName)( |
| IN LPCWSTR path); |
| |
| typedef HRESULT (WINAPI *SHGetFolderPathFunction)( |
| IN HWND hwnd_owner, |
| IN int folder, |
| IN HANDLE token, |
| IN DWORD flags, |
| OUT LPWSTR path); |
| |
| PathIsUNCFunction g_path_is_unc_func; |
| PathAppendFunction g_path_append_func; |
| PathIsPrefixFunction g_path_is_prefix_func; |
| PathFindFileName g_path_find_filename_func; |
| SHGetFolderPathFunction g_get_folder_func; |
| |
| // Record the number of calls we've redirected so far. |
| int g_redirect_count = 0; |
| |
| // Populates the g_*_func pointers to functions which will be used in |
| // ShouldBypass(). Chrome_elf cannot have a load-time dependency on shell32 or |
| // shlwapi as this would induce a load-time dependency on user32.dll. Instead, |
| // the addresses of the functions we need are retrieved the first time this |
| // method is called, and cached to avoid subsequent calls to GetProcAddress(). |
| // It is assumed that the host process will never unload these functions. |
| // Returns true if all the functions needed are present. |
| bool PopulateShellFunctions() { |
| // Early exit if functions have already been populated. |
| if (g_path_is_unc_func && g_path_append_func && |
| g_path_is_prefix_func && g_get_folder_func) { |
| return true; |
| } |
| |
| // Get the addresses of the functions we need and store them for future use. |
| // These handles are intentionally leaked to ensure that these modules do not |
| // get unloaded. |
| HMODULE shell32 = ::LoadLibrary(L"shell32.dll"); |
| HMODULE shlwapi = ::LoadLibrary(L"shlwapi.dll"); |
| |
| if (!shlwapi || !shell32) |
| return false; |
| |
| g_path_is_unc_func = reinterpret_cast<PathIsUNCFunction>( |
| ::GetProcAddress(shlwapi, "PathIsUNCW")); |
| g_path_append_func = reinterpret_cast<PathAppendFunction>( |
| ::GetProcAddress(shlwapi, "PathAppendW")); |
| g_path_is_prefix_func = reinterpret_cast<PathIsPrefixFunction>( |
| ::GetProcAddress(shlwapi, "PathIsPrefixW")); |
| g_path_find_filename_func = reinterpret_cast<PathFindFileName>( |
| ::GetProcAddress(shlwapi, "PathFindFileNameW")); |
| g_get_folder_func = reinterpret_cast<SHGetFolderPathFunction>( |
| ::GetProcAddress(shell32, "SHGetFolderPathW")); |
| |
| return g_path_is_unc_func && g_path_append_func && g_path_is_prefix_func && |
| g_path_find_filename_func && g_get_folder_func; |
| } |
| |
| } // namespace |
| |
| // Turn off optimization to make sure these calls don't get inlined. |
| #pragma optimize("", off) |
| // Wrapper method for kernel32!CreateFile, to avoid setting off caller |
| // mitigation detectors. |
| HANDLE CreateFileWImpl(LPCWSTR file_name, |
| DWORD desired_access, |
| DWORD share_mode, |
| LPSECURITY_ATTRIBUTES security_attributes, |
| DWORD creation_disposition, |
| DWORD flags_and_attributes, |
| HANDLE template_file) { |
| return CreateFile(file_name, |
| desired_access, |
| share_mode, |
| security_attributes, |
| creation_disposition, |
| flags_and_attributes, |
| template_file); |
| |
| } |
| |
| HANDLE WINAPI CreateFileWRedirect( |
| LPCWSTR file_name, |
| DWORD desired_access, |
| DWORD share_mode, |
| LPSECURITY_ATTRIBUTES security_attributes, |
| DWORD creation_disposition, |
| DWORD flags_and_attributes, |
| HANDLE template_file) { |
| if (ShouldBypass(file_name)) { |
| ++g_redirect_count; |
| return CreateFileNTDLL(file_name, |
| desired_access, |
| share_mode, |
| security_attributes, |
| creation_disposition, |
| flags_and_attributes, |
| template_file); |
| } |
| return CreateFileWImpl(file_name, |
| desired_access, |
| share_mode, |
| security_attributes, |
| creation_disposition, |
| flags_and_attributes, |
| template_file); |
| } |
| #pragma optimize("", on) |
| |
| int GetRedirectCount() { |
| return g_redirect_count; |
| } |
| |
| HANDLE CreateFileNTDLL( |
| LPCWSTR file_name, |
| DWORD desired_access, |
| DWORD share_mode, |
| LPSECURITY_ATTRIBUTES security_attributes, |
| DWORD creation_disposition, |
| DWORD flags_and_attributes, |
| HANDLE template_file) { |
| HANDLE file_handle = INVALID_HANDLE_VALUE; |
| NTSTATUS result = STATUS_UNSUCCESSFUL; |
| IO_STATUS_BLOCK io_status_block = {}; |
| ULONG flags = 0; |
| |
| // Convert from Win32 domain to to NT creation disposition values. |
| switch (creation_disposition) { |
| case CREATE_NEW: |
| creation_disposition = FILE_CREATE; |
| break; |
| case CREATE_ALWAYS: |
| creation_disposition = FILE_OVERWRITE_IF; |
| break; |
| case OPEN_EXISTING: |
| creation_disposition = FILE_OPEN; |
| break; |
| case OPEN_ALWAYS: |
| creation_disposition = FILE_OPEN_IF; |
| break; |
| case TRUNCATE_EXISTING: |
| creation_disposition = FILE_OVERWRITE; |
| break; |
| default: |
| SetLastError(ERROR_INVALID_PARAMETER); |
| return INVALID_HANDLE_VALUE; |
| } |
| |
| // Translate the flags that need no validation: |
| if (!(flags_and_attributes & FILE_FLAG_OVERLAPPED)) |
| flags |= FILE_SYNCHRONOUS_IO_NONALERT; |
| |
| if (flags_and_attributes & FILE_FLAG_WRITE_THROUGH) |
| flags |= FILE_WRITE_THROUGH; |
| |
| if (flags_and_attributes & FILE_FLAG_RANDOM_ACCESS) |
| flags |= FILE_RANDOM_ACCESS; |
| |
| if (flags_and_attributes & FILE_FLAG_SEQUENTIAL_SCAN) |
| flags |= FILE_SEQUENTIAL_ONLY; |
| |
| if (flags_and_attributes & FILE_FLAG_DELETE_ON_CLOSE) { |
| flags |= FILE_DELETE_ON_CLOSE; |
| desired_access |= DELETE; |
| } |
| |
| if (flags_and_attributes & FILE_FLAG_BACKUP_SEMANTICS) |
| flags |= FILE_OPEN_FOR_BACKUP_INTENT; |
| else |
| flags |= FILE_NON_DIRECTORY_FILE; |
| |
| |
| if (flags_and_attributes & FILE_FLAG_OPEN_REPARSE_POINT) |
| flags |= FILE_OPEN_REPARSE_POINT; |
| |
| if (flags_and_attributes & FILE_FLAG_OPEN_NO_RECALL) |
| flags |= FILE_OPEN_NO_RECALL; |
| |
| if (!g_ntdll_lookup["RtlInitUnicodeString"]) |
| return INVALID_HANDLE_VALUE; |
| |
| NtCreateFileFunction create_file; |
| char thunk_buffer[sizeof(sandbox::ThunkData)] = {}; |
| |
| if (g_nt_thunk_storage.data[0] != 0) { |
| create_file = reinterpret_cast<NtCreateFileFunction>(&g_nt_thunk_storage); |
| // Copy the thunk data to a buffer on the stack for debugging purposes. |
| memcpy(&thunk_buffer, &g_nt_thunk_storage, sizeof(sandbox::ThunkData)); |
| } else if (g_ntdll_lookup["NtCreateFile"]) { |
| create_file = |
| reinterpret_cast<NtCreateFileFunction>(g_ntdll_lookup["NtCreateFile"]); |
| } else { |
| return INVALID_HANDLE_VALUE; |
| } |
| |
| RtlInitUnicodeStringFunction init_unicode_string = |
| reinterpret_cast<RtlInitUnicodeStringFunction>( |
| g_ntdll_lookup["RtlInitUnicodeString"]); |
| |
| UNICODE_STRING path_unicode_string; |
| |
| // Format the path into an NT path. Arguably this should be done with |
| // RtlDosPathNameToNtPathName_U, but afaict this is equivalent for |
| // local paths. Using this with a UNC path name will almost certainly |
| // break in interesting ways. |
| base::string16 filename_string(L"\\??\\"); |
| filename_string += file_name; |
| |
| init_unicode_string(&path_unicode_string, filename_string.c_str()); |
| |
| OBJECT_ATTRIBUTES path_attributes = {}; |
| InitializeObjectAttributes(&path_attributes, |
| &path_unicode_string, |
| OBJ_CASE_INSENSITIVE, |
| NULL, // No Root Directory |
| NULL); // No Security Descriptor |
| |
| // Set desired_access, and flags_and_attributes to match those |
| // set by kernel32!CreateFile. |
| desired_access |= 0x100080; |
| flags_and_attributes &= 0x2FFA7; |
| |
| result = create_file(&file_handle, |
| desired_access, |
| &path_attributes, |
| &io_status_block, |
| 0, // Allocation size |
| flags_and_attributes, |
| share_mode, |
| creation_disposition, |
| flags, |
| NULL, |
| 0); |
| |
| if (result != STATUS_SUCCESS) { |
| if (result == STATUS_OBJECT_NAME_COLLISION && |
| creation_disposition == FILE_CREATE) { |
| SetLastError(ERROR_FILE_EXISTS); |
| } |
| return INVALID_HANDLE_VALUE; |
| } |
| |
| if (creation_disposition == FILE_OPEN_IF) { |
| SetLastError(io_status_block.Information == FILE_OPENED ? |
| ERROR_ALREADY_EXISTS : ERROR_SUCCESS); |
| } else if (creation_disposition == FILE_OVERWRITE_IF) { |
| SetLastError(io_status_block.Information == FILE_OVERWRITTEN ? |
| ERROR_ALREADY_EXISTS : ERROR_SUCCESS); |
| } else { |
| SetLastError(ERROR_SUCCESS); |
| } |
| |
| return file_handle; |
| } |
| |
| bool ShouldBypass(LPCWSTR file_path) { |
| // Do not redirect in non-browser processes. |
| if (IsNonBrowserProcess()) |
| return false; |
| |
| // If the shell functions are not present, forward the call to kernel32. |
| if (!PopulateShellFunctions()) |
| return false; |
| |
| // Forward all UNC filepaths to kernel32. |
| if (g_path_is_unc_func(file_path)) |
| return false; |
| |
| wchar_t local_appdata_path[MAX_PATH]; |
| |
| // Get the %LOCALAPPDATA% Path and append the location of our UserData |
| // directory to it. |
| HRESULT appdata_result = g_get_folder_func( |
| NULL, CSIDL_LOCAL_APPDATA, NULL, 0, local_appdata_path); |
| |
| wchar_t buffer[MAX_PATH] = {}; |
| if (!GetModuleFileNameW(NULL, buffer, MAX_PATH)) |
| return false; |
| |
| bool is_canary = IsCanary(buffer); |
| |
| // If getting the %LOCALAPPDATA% path or appending to it failed, then forward |
| // the call to kernel32. |
| if (!SUCCEEDED(appdata_result) || |
| !g_path_append_func(local_appdata_path, is_canary ? |
| kCanaryAppDataDirName : kAppDataDirName) || |
| !g_path_append_func(local_appdata_path, kUserDataDirName)) { |
| return false; |
| } |
| |
| LPCWSTR file_name = g_path_find_filename_func(file_path); |
| |
| bool in_userdata_dir = !!g_path_is_prefix_func(local_appdata_path, file_path); |
| bool is_settings_file = wcscmp(file_name, kPreferencesFilename) == 0 || |
| wcscmp(file_name, kLocalStateFilename) == 0; |
| |
| // Check if we are trying to access the Preferences in the UserData dir. If |
| // so, then redirect the call to bypass kernel32. |
| return in_userdata_dir && is_settings_file; |
| } |