root/chrome_elf/create_file/chrome_create_file.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. PopulateShellFunctions
  2. CreateFileWImpl
  3. CreateFileWRedirect
  4. GetRedirectCount
  5. CreateFileNTDLL
  6. ShouldBypass

// 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;
}

/* [<][>][^][v][top][bottom][index][help] */