// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/crash/core/app/crashpad.h"

#include <pthread.h>
#include <sys/prctl.h>

#include <limits>

#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/linux_util.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/posix/global_descriptors.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "build/branding_buildflags.h"
#include "build/chromeos_buildflags.h"
#include "components/crash/core/app/crash_reporter_client.h"
#include "components/crash/core/app/crash_switches.h"
#include "content/public/common/content_descriptors.h"
#include "sandbox/linux/services/namespace_sandbox.h"
#include "third_party/crashpad/crashpad/client/crashpad_client.h"
#include "third_party/crashpad/crashpad/client/crashpad_info.h"

#if BUILDFLAG(IS_CHROMEOS_DEVICE)
#include <sys/types.h>
#include <unistd.h>

#include <utility>

#include "base/at_exit.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "third_party/cros_system_api/constants/crash_reporter.h"
#endif

namespace crash_reporter {

namespace {

// TODO(jperaza): This is the first chance handler type used by Breakpad and v8.
// The Crashpad FirstChanceHandler type explicitly declares the third parameter
// to be a ucontext_t* instead of a void*. Using a reinterpret cast to convert
// one function type to another causes calling the handler to fail with SIGILL
// when CFI is enabled. Use a helper with Crashpad's FirstChanceHandler type
// to delegate to the real first chance handler instead of re-interpreting the
// handler itself. This can be removed if the two function types converge.
bool (*g_first_chance_handler)(int, siginfo_t*, void*);

bool FirstChanceHandlerHelper(int signo,
                              siginfo_t* siginfo,
                              ucontext_t* context) {
  return g_first_chance_handler(signo, siginfo, context);
}

#if BUILDFLAG(IS_CHROMEOS_DEVICE)
// Returns /run/crash_reporter/crashpad_ready/<pid>, the file we touch to
// tell crash_reporter that crashpad is ready and it doesn't need to use
// early-crash mode.
base::FilePath GetCrashpadReadyFilename() {
  pid_t pid = getpid();
  base::FilePath path(crash_reporter::kCrashpadReadyDirectory);
  return path.Append(base::NumberToString(pid));
}

// Inform crash_reporter than crashpad is ready to handle crashes by
// touching /run/crash_reporter/crashpad_ready/<pid>.
//
// Before this point, crash_reporter will attempt to handle Chrome crashes
// sent to it by the kernel core_pattern. This gives us a chance to catch
// crashes that happen before crashpad initializes. See code around
// UserCollector::handling_early_chrome_crash_ for details. Once the
// /run/crash_reporter/crashpad_ready/<pid> file exists, however,
// crash_reporter's UserCollector will assume crashpad will deal with the
// crash and can early-return.
//
// We only need this for the browser process; the cores are too large for
// other processes so early crash doesn't attempt to handle them.
void InformCrashReporterThatCrashpadIsReady() {
  base::FilePath path = GetCrashpadReadyFilename();
  // Note: Using a base::File with FLAG_CREATE instead of WriteFile() to
  // avoid symbolic link shenanigans.
  base::File file(path, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
  if (!file.IsValid()) {
    LOG(ERROR) << "Could not create " << path << ": "
               << base::File::ErrorToString(file.error_details());
  }

  // Remove file when the program exits. This isn't perfect; if the program
  // crashes, and if the next browser process comes up with the same PID
  // (without an intervening reboot), and that browser process crashes before
  // crashpad initializes, then the crash service won't get the crash report.
  // This is unlikely enough that I think we can risk it, especially since
  // losing a single crash report is not a huge deal.
  base::AtExitManager::RegisterTask(base::BindOnce(&DeleteCrashpadIsReadyFile));
}
#endif  // BUILDFLAG(IS_CHROMEOS_DEVICE)

}  // namespace

void SetFirstChanceExceptionHandler(bool (*handler)(int, siginfo_t*, void*)) {
  DCHECK(!g_first_chance_handler);
  g_first_chance_handler = handler;
  crashpad::CrashpadClient::SetFirstChanceExceptionHandler(
      FirstChanceHandlerHelper);
}

bool GetHandlerSocket(int* fd, pid_t* pid) {
  return crashpad::CrashpadClient::GetHandlerSocket(fd, pid);
}

#if BUILDFLAG(IS_CHROMEOS_DEVICE)
void DeleteCrashpadIsReadyFile() {
  // Attempt delete but do not log errors if the delete fails. The file might
  // not exist if this function is called twice, or if Chrome did a fork-
  // without-exec and this function is not in the same process that called
  // InformCrashReporterThatCrashpadIsReady().
  base::DeleteFile(GetCrashpadReadyFilename());
}
#endif  // BUILDFLAG(IS_CHROMEOS_DEVICE)

namespace internal {

bool PlatformCrashpadInitialization(
    bool initial_client,
    bool browser_process,
    bool embedded_handler,
    const std::string& user_data_dir,
    const base::FilePath& exe_path,
    const std::vector<std::string>& initial_arguments,
    const std::vector<base::FilePath>& attachments,
    base::FilePath* database_path) {
  DCHECK_EQ(initial_client, browser_process);
  DCHECK(initial_arguments.empty());

  // Not used on Linux.
  DCHECK(!embedded_handler);
  DCHECK(exe_path.empty());

  crashpad::CrashpadClient client;
#if BUILDFLAG(IS_CHROMEOS)
  std::string crash_loop_before =
      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
          switches::kCrashLoopBefore);
  if (!crash_loop_before.empty()) {
    uint64_t crash_loop_before_time;
    if (!base::StringToUint64(crash_loop_before, &crash_loop_before_time)) {
      LOG(ERROR) << "Couldn't parse --crash-loop-before=" << crash_loop_before;
      DCHECK(false);
      crash_loop_before_time = std::numeric_limits<uint64_t>::max();
    }
    client.SetCrashLoopBefore(crash_loop_before_time);
  }
#endif

  CrashReporterClient* crash_reporter_client = GetCrashReporterClient();
  if (initial_client) {
    base::FilePath metrics_path;
    crash_reporter_client->GetCrashDumpLocation(database_path);
    crash_reporter_client->GetCrashMetricsLocation(&metrics_path);

    base::FilePath handler_path;
    if (!base::PathService::Get(base::DIR_EXE, &handler_path)) {
      return false;
    }
    handler_path = handler_path.Append("chrome_crashpad_handler");

    // When --use-cros-crash-reporter is set (below), the handler passes dumps
    // to ChromeOS's /sbin/crash_reporter which in turn passes the dump to
    // crash_sender which handles the upload.
    std::string url;
#if !BUILDFLAG(IS_CHROMEOS_DEVICE)
    url = crash_reporter_client->GetUploadUrl();
#else
    url = std::string();
#endif

    ProductInfo product_info;
    crash_reporter_client->GetProductInfo(&product_info);

    std::map<std::string, std::string> annotations;
    annotations["prod"] = product_info.product_name;
    annotations["ver"] = product_info.version;

#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
    // Empty means stable.
    const bool allow_empty_channel = true;
    if (product_info.channel == "extended") {
      // Extended stable reports as stable (empty string) with an extra bool.
      product_info.channel.clear();
      annotations["extended_stable_channel"] = "true";
    }
#else
    const bool allow_empty_channel = false;
#endif
    if (allow_empty_channel || !product_info.channel.empty()) {
      annotations["channel"] = product_info.channel;
    }

    annotations["plat"] = std::string("Linux");

#if BUILDFLAG(IS_CHROMEOS_DEVICE)
    // Chromium OS: save board and builder path for 'tast symbolize'.
    annotations["chromeos-board"] = base::SysInfo::GetLsbReleaseBoard();
    std::string builder_path;
    if (base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_BUILDER_PATH",
                                          &builder_path)) {
      annotations["chromeos-builder-path"] = builder_path;
    }

#else
    // Other Linux: save lsb-release. This isn't needed on Chromium OS,
    // where crash_reporter provides it's own values for lsb-release.
    annotations["lsb-release"] = base::GetLinuxDistro();
#endif

    std::vector<std::string> arguments;
    if (crash_reporter_client->ShouldMonitorCrashHandlerExpensively()) {
      arguments.push_back("--monitor-self");
    }

    // Set up --monitor-self-annotation even in the absence of --monitor-self
    // so that minidumps produced by Crashpad's generate_dump tool will
    // contain these annotations.
    arguments.push_back("--monitor-self-annotation=ptype=crashpad-handler");

#if BUILDFLAG(IS_CHROMEOS_DEVICE)
    arguments.push_back("--use-cros-crash-reporter");

    if (crash_reporter_client->IsRunningUnattended()) {
      arguments.push_back(base::StringPrintf("--minidump-dir-for-tests=%s",
                                             database_path->value().c_str()));
      arguments.push_back("--always-allow-feedback");
    }
#endif

    CHECK(client.StartHandler(handler_path, *database_path, metrics_path, url,
                              annotations, arguments, false, false,
                              attachments));
  } else {
    int fd = base::GlobalDescriptors::GetInstance()->Get(kCrashDumpSignal);

    pid_t pid = 0;
    if (!sandbox::NamespaceSandbox::InNewUserNamespace()) {
      std::string pid_string =
          base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
              switches::kCrashpadHandlerPid);
      bool parsed = base::StringToInt(pid_string, &pid);
      DCHECK(parsed);
    }

    // SIGSYS handling is reserved for the sandbox.
    client.SetUnhandledSignals({SIGSYS});

    client.SetHandlerSocket(crashpad::ScopedFileHandle(fd), pid);

    *database_path = base::FilePath();
  }

  // In the not-large-dumps case record enough extra memory to be able to save
  // dereferenced memory from all registers on the crashing thread. crashpad may
  // save 512-bytes per register, and the largest register set (not including
  // stack pointers) is ARM64 with 32 registers. Hence, 16 KiB.
  const uint32_t kIndirectMemoryLimit =
      crash_reporter_client->GetShouldDumpLargerDumps() ? 4 * 1024 * 1024
                                                        : 16 * 1024;
  crashpad::CrashpadInfo::GetCrashpadInfo()
      ->set_gather_indirectly_referenced_memory(crashpad::TriState::kEnabled,
                                                kIndirectMemoryLimit);

#if BUILDFLAG(IS_CHROMEOS_DEVICE)
  if (initial_client) {
    InformCrashReporterThatCrashpadIsReady();
  }
#endif

  return true;
}

}  // namespace internal
}  // namespace crash_reporter
