// Copyright (c) 2006-2010 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.

// For information about interceptions as a whole see
// http://dev.chromium.org/developers/design-documents/sandbox .

#include "sandbox/win/src/interception_agent.h"

#include <windows.h>

#include <stddef.h>

#include "sandbox/win/src/eat_resolver.h"
#include "sandbox/win/src/interception_internal.h"
#include "sandbox/win/src/interceptors.h"
#include "sandbox/win/src/sandbox_nt_util.h"
#include "sandbox/win/src/sidestep_resolver.h"

namespace {

// Returns true if target lies between base and base + range.
bool IsWithinRange(const void* base, size_t range, const void* target) {
  const char* end = reinterpret_cast<const char*>(base) + range;
  return reinterpret_cast<const char*>(target) < end;
}

}  // namespace

namespace sandbox {

// This is the list of all imported symbols from ntdll.dll.
SANDBOX_INTERCEPT NtExports g_nt;

// The list of intercepted functions back-pointers.
SANDBOX_INTERCEPT OriginalFunctions g_originals;

// Memory buffer mapped from the parent, with the list of interceptions.
SANDBOX_INTERCEPT SharedMemory* g_interceptions = nullptr;

InterceptionAgent* InterceptionAgent::GetInterceptionAgent() {
  static InterceptionAgent* s_singleton = nullptr;
  if (!s_singleton) {
    if (!g_interceptions)
      return nullptr;

    size_t array_bytes = g_interceptions->num_intercepted_dlls * sizeof(void*);
    s_singleton = reinterpret_cast<InterceptionAgent*>(
        new (NT_ALLOC) char[array_bytes + sizeof(InterceptionAgent)]);

    bool success = s_singleton->Init(g_interceptions);
    if (!success) {
      operator delete(s_singleton, NT_ALLOC);
      s_singleton = nullptr;
    }
  }
  return s_singleton;
}

bool InterceptionAgent::Init(SharedMemory* shared_memory) {
  interceptions_ = shared_memory;
  for (int i = 0; i < shared_memory->num_intercepted_dlls; i++)
    dlls_[i] = nullptr;
  return true;
}

bool InterceptionAgent::DllMatch(const UNICODE_STRING* full_path,
                                 const UNICODE_STRING* name,
                                 const DllPatchInfo* dll_info) {
  UNICODE_STRING current_name;
  current_name.Length =
      static_cast<USHORT>(g_nt.wcslen(dll_info->dll_name) * sizeof(wchar_t));
  current_name.MaximumLength = current_name.Length;
  current_name.Buffer = const_cast<wchar_t*>(dll_info->dll_name);

  BOOLEAN case_insensitive = TRUE;
  if (full_path &&
      !g_nt.RtlCompareUnicodeString(&current_name, full_path, case_insensitive))
    return true;

  if (name &&
      !g_nt.RtlCompareUnicodeString(&current_name, name, case_insensitive))
    return true;

  return false;
}

bool InterceptionAgent::OnDllLoad(const UNICODE_STRING* full_path,
                                  const UNICODE_STRING* name,
                                  void* base_address) {
  DllPatchInfo* dll_info = interceptions_->dll_list;
  int i = 0;
  for (; i < interceptions_->num_intercepted_dlls; i++) {
    if (DllMatch(full_path, name, dll_info))
      break;

    dll_info = reinterpret_cast<DllPatchInfo*>(
        reinterpret_cast<char*>(dll_info) + dll_info->record_bytes);
  }

  // Return now if the dll is not in our list of interest.
  if (i == interceptions_->num_intercepted_dlls)
    return true;

  // The dll must be unloaded.
  if (dll_info->unload_module)
    return false;

  // Purify causes this condition to trigger.
  if (dlls_[i])
    return true;

  size_t buffer_bytes = offsetof(DllInterceptionData, thunks) +
                        dll_info->num_functions * sizeof(ThunkData);
  dlls_[i] = reinterpret_cast<DllInterceptionData*>(
      new (NT_PAGE, base_address) char[buffer_bytes]);

  DCHECK_NT(dlls_[i]);
  if (!dlls_[i])
    return true;

  dlls_[i]->data_bytes = buffer_bytes;
  dlls_[i]->num_thunks = 0;
  dlls_[i]->base = base_address;
  dlls_[i]->used_bytes = offsetof(DllInterceptionData, thunks);

  VERIFY(PatchDll(dll_info, dlls_[i]));

  ULONG old_protect;
  SIZE_T real_size = buffer_bytes;
  void* to_protect = dlls_[i];
  VERIFY_SUCCESS(g_nt.ProtectVirtualMemory(NtCurrentProcess, &to_protect,
                                           &real_size, PAGE_EXECUTE_READ,
                                           &old_protect));
  return true;
}

void InterceptionAgent::OnDllUnload(void* base_address) {
  for (int i = 0; i < interceptions_->num_intercepted_dlls; i++) {
    if (dlls_[i] && dlls_[i]->base == base_address) {
      operator delete(dlls_[i], NT_PAGE);
      dlls_[i] = nullptr;
      break;
    }
  }
}

// TODO(rvargas): We have to deal with prebinded dlls. I see two options: change
// the timestamp of the patched dll, or modify the info on the prebinded dll.
// the first approach messes matching of debug symbols, the second one is more
// complicated.
bool InterceptionAgent::PatchDll(const DllPatchInfo* dll_info,
                                 DllInterceptionData* thunks) {
  DCHECK_NT(thunks);
  DCHECK_NT(dll_info);

  const FunctionInfo* function = reinterpret_cast<const FunctionInfo*>(
      reinterpret_cast<const char*>(dll_info) + dll_info->offset_to_functions);

  for (int i = 0; i < dll_info->num_functions; i++) {
    if (!IsWithinRange(dll_info, dll_info->record_bytes, function->function)) {
      NOTREACHED_NT();
      return false;
    }

    ResolverThunk* resolver = GetResolver(function->type);
    if (!resolver)
      return false;

    const char* interceptor =
        function->function + g_nt.strlen(function->function) + 1;

    if (!IsWithinRange(function, function->record_bytes, interceptor) ||
        !IsWithinRange(dll_info, dll_info->record_bytes, interceptor)) {
      NOTREACHED_NT();
      return false;
    }

    NTSTATUS ret = resolver->Setup(
        thunks->base, interceptions_->interceptor_base, function->function,
        interceptor, function->interceptor_address, &thunks->thunks[i],
        sizeof(ThunkData), nullptr);
    if (!NT_SUCCESS(ret)) {
      NOTREACHED_NT();
      return false;
    }

    DCHECK_NT(!g_originals[function->id] ||
              g_originals[function->id] == &thunks->thunks[i]);
    g_originals[function->id] = &thunks->thunks[i];

    thunks->num_thunks++;
    thunks->used_bytes += sizeof(ThunkData);

    function = reinterpret_cast<const FunctionInfo*>(
        reinterpret_cast<const char*>(function) + function->record_bytes);
  }

  return true;
}

// This method is called from within the loader lock
ResolverThunk* InterceptionAgent::GetResolver(InterceptionType type) {
  static EatResolverThunk* eat_resolver = nullptr;
  static SidestepResolverThunk* sidestep_resolver = nullptr;
  static SmartSidestepResolverThunk* smart_sidestep_resolver = nullptr;

  if (!eat_resolver)
    eat_resolver = new (NT_ALLOC) EatResolverThunk;

#if !defined(_WIN64)
  // Sidestep is not supported for x64.
  if (!sidestep_resolver)
    sidestep_resolver = new (NT_ALLOC) SidestepResolverThunk;

  if (!smart_sidestep_resolver)
    smart_sidestep_resolver = new (NT_ALLOC) SmartSidestepResolverThunk;
#endif

  switch (type) {
    case INTERCEPTION_EAT:
      return eat_resolver;
    case INTERCEPTION_SIDESTEP:
      return sidestep_resolver;
    case INTERCEPTION_SMART_SIDESTEP:
      return smart_sidestep_resolver;
    default:
      NOTREACHED_NT();
  }

  return nullptr;
}

}  // namespace sandbox
