blob: 2db6f8d8372f96aaaf44c9a16ef8062c086ca41d [file] [log] [blame]
// 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;
}