// Copyright 2017 The Crashpad Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "test/mac/exception_swallower.h"

#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

#include <string>

#include "base/apple/scoped_mach_port.h"
#include "base/check_op.h"
#include "base/strings/stringprintf.h"
#include "handler/mac/exception_handler_server.h"
#include "util/mach/bootstrap.h"
#include "util/mach/exc_server_variants.h"
#include "util/mach/exception_ports.h"
#include "util/mach/mach_extensions.h"
#include "util/misc/random_string.h"
#include "util/thread/thread.h"

namespace crashpad {
namespace test {

namespace {

constexpr char kServiceEnvironmentVariable[] =
    "CRASHPAD_EXCEPTION_SWALLOWER_SERVICE";

ExceptionSwallower* g_exception_swallower;

// Like getenv(), but fails a CHECK() if the underlying function fails. It’s not
// considered a failure for |name| to be unset in the environment. In that case,
// nullptr is returned.
const char* CheckedGetenv(const char* name) {
  errno = 0;
  const char* value;
  PCHECK((value = getenv(name)) || errno == 0) << "getenv";
  return value;
}

}  // namespace

class ExceptionSwallower::ExceptionSwallowerThread
    : public Thread,
      public UniversalMachExcServer::Interface {
 public:
  explicit ExceptionSwallowerThread(
      base::apple::ScopedMachReceiveRight receive_right)
      : Thread(),
        UniversalMachExcServer::Interface(),
        exception_handler_server_(std::move(receive_right), true),
        pid_(getpid()) {
    Start();
  }

  ExceptionSwallowerThread(const ExceptionSwallowerThread&) = delete;
  ExceptionSwallowerThread& operator=(const ExceptionSwallowerThread&) = delete;

  ~ExceptionSwallowerThread() override {}

  void Stop() { exception_handler_server_.Stop(); }

  // Returns the process ID that the thread is running in. This is used to
  // detect misuses that place the exception swallower server thread and code
  // that wants its exceptions swallowed in the same process.
  pid_t ProcessID() const { return pid_; }

 private:
  // Thread:

  void ThreadMain() override { exception_handler_server_.Run(this); }

  // UniversalMachExcServer::Interface:

  kern_return_t CatchMachException(exception_behavior_t behavior,
                                   exception_handler_t exception_port,
                                   thread_t thread,
                                   task_t task,
                                   exception_type_t exception,
                                   const mach_exception_data_type_t* code,
                                   mach_msg_type_number_t code_count,
                                   thread_state_flavor_t* flavor,
                                   ConstThreadState old_state,
                                   mach_msg_type_number_t old_state_count,
                                   thread_state_t new_state,
                                   mach_msg_type_number_t* new_state_count,
                                   const mach_msg_trailer_t* trailer,
                                   bool* destroy_complex_request) override {
    *destroy_complex_request = true;

    // Swallow.

    ExcServerCopyState(
        behavior, old_state, old_state_count, new_state, new_state_count);
    return ExcServerSuccessfulReturnValue(exception, behavior, false);
  }

  ExceptionHandlerServer exception_handler_server_;
  pid_t pid_;
};

ExceptionSwallower::ExceptionSwallower() : exception_swallower_thread_() {
  CHECK(!g_exception_swallower);
  g_exception_swallower = this;

  if (CheckedGetenv(kServiceEnvironmentVariable)) {
    // The environment variable is already set, so just proceed with the
    // existing service. This normally happens when the Google Test “threadsafe”
    // death test style is chosen, because the test child process will
    // re-execute code already run in the test parent process. See
    // https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#death-test-styles.
    return;
  }

  std::string service_name =
      base::StringPrintf("org.chromium.crashpad.test.exception_swallower.%d.%s",
                         getpid(),
                         RandomString().c_str());
  base::apple::ScopedMachReceiveRight receive_right(
      BootstrapCheckIn(service_name));
  CHECK(receive_right.is_valid());

  exception_swallower_thread_.reset(
      new ExceptionSwallowerThread(std::move(receive_right)));

  PCHECK(setenv(kServiceEnvironmentVariable, service_name.c_str(), 1) == 0)
      << "setenv";
}

ExceptionSwallower::~ExceptionSwallower() {
  PCHECK(unsetenv(kServiceEnvironmentVariable) == 0) << "unsetenv";

  exception_swallower_thread_->Stop();
  exception_swallower_thread_->Join();

  CHECK_EQ(g_exception_swallower, this);
  g_exception_swallower = nullptr;
}

// static
void ExceptionSwallower::SwallowExceptions() {
  // The exception swallower thread can’t be in this process, because the
  // EXC_CRASH or EXC_CORPSE_NOTIFY exceptions that it needs to swallow will be
  // delivered after a crash has occurred and none of its threads will be
  // scheduled to run.
  CHECK(!g_exception_swallower ||
        !g_exception_swallower->exception_swallower_thread_ ||
        g_exception_swallower->exception_swallower_thread_->ProcessID() !=
            getpid());

  const char* service_name = CheckedGetenv(kServiceEnvironmentVariable);
  CHECK(service_name);

  base::apple::ScopedMachSendRight exception_swallower_port(
      BootstrapLookUp(service_name));
  CHECK(exception_swallower_port.is_valid());

  ExceptionPorts task_exception_ports(ExceptionPorts::kTargetTypeTask,
                                      TASK_NULL);

  // The mask is similar to the one used by CrashpadClient::UseHandler(), but
  // EXC_CORPSE_NOTIFY is added. This is done for the benefit of tests that
  // crash intentionally with their own custom exception port set for EXC_CRASH.
  // In that case, depending on the actions taken by the EXC_CRASH handler, the
  // exception may be transformed by the kernel into an EXC_CORPSE_NOTIFY, which
  // would be sent to an EXC_CORPSE_NOTIFY handler, normally the system’s crash
  // reporter at the task or host level. See 10.13.0
  // xnu-4570.1.46/bsd/kern/kern_exit.c proc_prepareexit(). Swallowing
  // EXC_CORPSE_NOTIFY at the task level prevents such exceptions from reaching
  // the system’s crash reporter.
  CHECK(task_exception_ports.SetExceptionPort(
      (EXC_MASK_CRASH |
       EXC_MASK_RESOURCE |
       EXC_MASK_GUARD |
       EXC_MASK_CORPSE_NOTIFY) & ExcMaskValid(),
      exception_swallower_port.get(),
      EXCEPTION_DEFAULT,
      THREAD_STATE_NONE));
}

}  // namespace test
}  // namespace crashpad
