// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// 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 "util/posix/signals.h"

#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>

#include <limits>

#include "base/compiler_specific.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "test/errors.h"
#include "test/multiprocess.h"
#include "test/scoped_temp_dir.h"
#include "util/posix/scoped_mmap.h"

namespace crashpad {
namespace test {
namespace {

constexpr int kUnexpectedExitStatus = 3;

// Keep synchronized with CauseSignal().
bool CanCauseSignal(int sig) {
  return sig == SIGABRT ||
         sig == SIGALRM ||
         sig == SIGBUS ||
#if !defined(ARCH_CPU_ARM64)
         sig == SIGFPE ||
#endif  // !defined(ARCH_CPU_ARM64)
#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARMEL)
         sig == SIGILL ||
#endif  // defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARMEL
         sig == SIGPIPE ||
         sig == SIGSEGV ||
#if defined(OS_MACOSX)
         sig == SIGSYS ||
#endif  // OS_MACOSX
#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64)
         sig == SIGTRAP ||
#endif  // defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64)
         false;
}

// Keep synchronized with CanCauseSignal().
void CauseSignal(int sig) {
  switch (sig) {
    case SIGABRT: {
      abort();
      break;
    }

    case SIGALRM: {
      struct itimerval itimer = {};
      itimer.it_value.tv_usec = 1E3;  // 1 millisecond
      if (setitimer(ITIMER_REAL, &itimer, nullptr) != 0) {
        PLOG(ERROR) << "setitimer";
        _exit(kUnexpectedExitStatus);
      }

      while (true) {
        sleep(std::numeric_limits<unsigned int>::max());
      }
    }

    case SIGBUS: {
      ScopedMmap mapped_file;
      {
        base::ScopedFD fd;
        {
          ScopedTempDir temp_dir;
          fd.reset(open(temp_dir.path().Append("empty").value().c_str(),
                        O_RDWR | O_CREAT | O_EXCL | O_NOCTTY | O_CLOEXEC,
                        0644));
          if (fd.get() < 0) {
            PLOG(ERROR) << "open";
          }
        }
        if (fd.get() < 0) {
          _exit(kUnexpectedExitStatus);
        }

        if (!mapped_file.ResetMmap(nullptr,
                                   getpagesize(),
                                   PROT_READ | PROT_WRITE,
                                   MAP_PRIVATE,
                                   fd.get(),
                                   0)) {
          _exit(kUnexpectedExitStatus);
        }
      }

      *mapped_file.addr_as<char*>() = 0;

      _exit(kUnexpectedExitStatus);
      break;
    }

#if !defined(ARCH_CPU_ARM64)
    // ARM64 has hardware integer division instructions that don’t generate a
    // trap for divide-by-zero, so this doesn’t produce SIGFPE.
    case SIGFPE: {
      // Optimization makes this tricky, so get zero from a system call likely
      // to succeed, and try to do something with the result.
      struct stat stat_buf;
      int zero = stat("/", &stat_buf);
      if (zero == -1) {
        // It’s important to check |== -1| and not |!= 0|. An optimizer is free
        // to discard an |== 0| branch entirely, because division by zero is
        // undefined behavior.
        PLOG(ERROR) << "stat";
        _exit(kUnexpectedExitStatus);
      }

      int quotient = 2 / zero;
      fstat(quotient, &stat_buf);
      break;
    }
#endif  // ARCH_CPU_ARM64

#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARMEL)
    case SIGILL: {
      // __builtin_trap() causes SIGTRAP on arm64 on Android.
      __builtin_trap();
      break;
    }
#endif  // defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARMEL)

    case SIGPIPE: {
      int pipe_fds[2];
      if (pipe(pipe_fds) != 0) {
        PLOG(ERROR) << "pipe";
        _exit(kUnexpectedExitStatus);
      }

      if (close(pipe_fds[0]) != 0) {
        PLOG(ERROR) << "close";
        _exit(kUnexpectedExitStatus);
      }

      char c = 0;
      ssize_t rv = write(pipe_fds[1], &c, sizeof(c));
      if (rv < 0) {
        PLOG(ERROR) << "write";
        _exit(kUnexpectedExitStatus);
      } else if (rv != sizeof(c)) {
        LOG(ERROR) << "write";
        _exit(kUnexpectedExitStatus);
      }
      break;
    }

    case SIGSEGV: {
      volatile int* i = nullptr;
      *i = 0;
      break;
    }

#if defined(OS_MACOSX)
    case SIGSYS: {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
      int rv = syscall(4095);
#pragma clang diagnostic pop
      if (rv != 0) {
        PLOG(ERROR) << "syscall";
        _exit(kUnexpectedExitStatus);
      }
      break;
    }
#endif  // OS_MACOSX

#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64)
    case SIGTRAP: {
#if defined(ARCH_CPU_X86_FAMILY)
      asm("int3");
#elif defined(ARCH_CPU_ARM64)
      // bkpt #0 should work for 32-bit ARCH_CPU_ARMEL, but according to
      // https://crrev.com/f53167270c44, it only causes SIGTRAP on Linux under a
      // 64-bit kernel. For a pure 32-bit armv7 system, it generates SIGBUS.
      asm("brk #0");
#endif
      break;
    }
#endif  // defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64)

    default: {
      LOG(ERROR) << "unexpected signal " << sig;
      _exit(kUnexpectedExitStatus);
      break;
    }
  }
}

class SignalsTest : public Multiprocess {
 public:
  enum class SignalSource {
    kCause,
    kRaise,
  };
  enum class TestType {
    kDefaultHandler,
    kHandlerExits,
    kHandlerReraisesToDefault,
    kHandlerReraisesToPrevious,
  };
  static constexpr int kExitingHandlerExitStatus = 2;

  SignalsTest(TestType test_type, SignalSource signal_source, int sig)
      : Multiprocess(),
        sig_(sig),
        test_type_(test_type),
        signal_source_(signal_source) {}
  ~SignalsTest() {}

 private:
  static void SignalHandler_Exit(int sig, siginfo_t* siginfo, void* context) {
    _exit(kExitingHandlerExitStatus);
  }

  static void SignalHandler_ReraiseToDefault(int sig,
                                             siginfo_t* siginfo,
                                             void* context) {
    Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);
  }

  static void SignalHandler_ReraiseToPrevious(int sig,
                                              siginfo_t* siginfo,
                                              void* context) {
    Signals::RestoreHandlerAndReraiseSignalOnReturn(
        siginfo, old_actions_.ActionForSignal(sig));
  }

  // Multiprocess:
  void MultiprocessParent() override {}

  void MultiprocessChild() override {
    bool (*install_handlers)(Signals::Handler, int, Signals::OldActions*);
    if (Signals::IsCrashSignal(sig_)) {
      install_handlers = Signals::InstallCrashHandlers;
    } else if (Signals::IsTerminateSignal(sig_)) {
      install_handlers = Signals::InstallTerminateHandlers;
    } else {
      _exit(kUnexpectedExitStatus);
    }

    switch (test_type_) {
      case TestType::kDefaultHandler: {
        // Don’t rely on the default handler being active. Something may have
        // changed it (particularly on Android).
        struct sigaction action;
        sigemptyset(&action.sa_mask);
        action.sa_flags = 0;
        action.sa_handler = SIG_DFL;
        ASSERT_EQ(sigaction(sig_, &action, nullptr), 0)
            << ErrnoMessage("sigaction");
        break;
      }

      case TestType::kHandlerExits: {
        ASSERT_TRUE(install_handlers(SignalHandler_Exit, 0, nullptr));
        break;
      }

      case TestType::kHandlerReraisesToDefault: {
        ASSERT_TRUE(
            install_handlers(SignalHandler_ReraiseToDefault, 0, nullptr));
        break;
      }

      case TestType::kHandlerReraisesToPrevious: {
        ASSERT_TRUE(install_handlers(SignalHandler_Exit, 0, nullptr));
        ASSERT_TRUE(install_handlers(
            SignalHandler_ReraiseToPrevious, 0, &old_actions_));
        break;
      }
    }

    switch (signal_source_) {
      case SignalSource::kCause:
        CauseSignal(sig_);
        break;
      case SignalSource::kRaise:
        raise(sig_);
        break;
    }

    _exit(kUnexpectedExitStatus);
  }

  int sig_;
  TestType test_type_;
  SignalSource signal_source_;
  static Signals::OldActions old_actions_;

  DISALLOW_COPY_AND_ASSIGN(SignalsTest);
};

Signals::OldActions SignalsTest::old_actions_;

bool ShouldTestSignal(int sig) {
  return Signals::IsCrashSignal(sig) || Signals::IsTerminateSignal(sig);
}

TEST(Signals, WillSignalReraiseAutonomously) {
  const struct {
    int sig;
    int code;
    bool result;
  } kTestData[] = {
      {SIGBUS, BUS_ADRALN, true},
      {SIGFPE, FPE_FLTDIV, true},
      {SIGILL, ILL_ILLOPC, true},
      {SIGSEGV, SEGV_MAPERR, true},
      {SIGBUS, 0, false},
      {SIGFPE, -1, false},
      {SIGILL, SI_USER, false},
      {SIGSEGV, SI_QUEUE, false},
      {SIGTRAP, TRAP_BRKPT, false},
      {SIGHUP, SEGV_MAPERR, false},
      {SIGINT, SI_USER, false},
  };
  for (size_t index = 0; index < arraysize(kTestData); ++index) {
    const auto test_data = kTestData[index];
    SCOPED_TRACE(base::StringPrintf(
        "index %zu, sig %d, code %d", index, test_data.sig, test_data.code));
    siginfo_t siginfo = {};
    siginfo.si_signo = test_data.sig;
    siginfo.si_code = test_data.code;
    EXPECT_EQ(Signals::WillSignalReraiseAutonomously(&siginfo),
              test_data.result);
  }
}

TEST(Signals, Cause_DefaultHandler) {
  for (int sig = 1; sig < NSIG; ++sig) {
    SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));

    if (!CanCauseSignal(sig)) {
      continue;
    }

    SignalsTest test(SignalsTest::TestType::kDefaultHandler,
                     SignalsTest::SignalSource::kCause,
                     sig);
    test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig);
    test.Run();
  }
}

TEST(Signals, Cause_HandlerExits) {
  for (int sig = 1; sig < NSIG; ++sig) {
    SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));

    if (!CanCauseSignal(sig)) {
      continue;
    }

    SignalsTest test(SignalsTest::TestType::kHandlerExits,
                     SignalsTest::SignalSource::kCause,
                     sig);
    test.SetExpectedChildTermination(Multiprocess::kTerminationNormal,
                                     SignalsTest::kExitingHandlerExitStatus);
    test.Run();
  }
}

TEST(Signals, Cause_HandlerReraisesToDefault) {
  for (int sig = 1; sig < NSIG; ++sig) {
    SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));

    if (!CanCauseSignal(sig)) {
      continue;
    }

    SignalsTest test(SignalsTest::TestType::kHandlerReraisesToDefault,
                     SignalsTest::SignalSource::kCause,
                     sig);
    test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig);
    test.Run();
  }
}

TEST(Signals, Cause_HandlerReraisesToPrevious) {
  for (int sig = 1; sig < NSIG; ++sig) {
    SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));

    if (!CanCauseSignal(sig)) {
      continue;
    }

    SignalsTest test(SignalsTest::TestType::kHandlerReraisesToPrevious,
                     SignalsTest::SignalSource::kCause,
                     sig);
    test.SetExpectedChildTermination(Multiprocess::kTerminationNormal,
                                     SignalsTest::kExitingHandlerExitStatus);
    test.Run();
  }
}

TEST(Signals, Raise_DefaultHandler) {
  for (int sig = 1; sig < NSIG; ++sig) {
    SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));

    if (!ShouldTestSignal(sig)) {
      continue;
    }

    SignalsTest test(SignalsTest::TestType::kDefaultHandler,
                     SignalsTest::SignalSource::kRaise,
                     sig);
    test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig);
    test.Run();
  }
}

TEST(Signals, Raise_HandlerExits) {
  for (int sig = 1; sig < NSIG; ++sig) {
    SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));

    if (!ShouldTestSignal(sig)) {
      continue;
    }

    SignalsTest test(SignalsTest::TestType::kHandlerExits,
                     SignalsTest::SignalSource::kRaise,
                     sig);
    test.SetExpectedChildTermination(Multiprocess::kTerminationNormal,
                                     SignalsTest::kExitingHandlerExitStatus);
    test.Run();
  }
}

TEST(Signals, Raise_HandlerReraisesToDefault) {
  for (int sig = 1; sig < NSIG; ++sig) {
    SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));

    if (!ShouldTestSignal(sig)) {
      continue;
    }

#if defined(OS_MACOSX)
    if (sig == SIGBUS) {
      // Signal handlers can’t distinguish between SIGBUS arising out of a
      // hardware fault and SIGBUS raised asynchronously.
      // Signals::RestoreHandlerAndReraiseSignalOnReturn() assumes that SIGBUS
      // comes from a hardware fault, but this test uses raise(), so the
      // re-raise test must be skipped.
      continue;
    }
#endif  // defined(OS_MACOSX)

    SignalsTest test(SignalsTest::TestType::kHandlerReraisesToDefault,
                     SignalsTest::SignalSource::kRaise,
                     sig);
    test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig);
    test.Run();
  }
}

TEST(Signals, Raise_HandlerReraisesToPrevious) {
  for (int sig = 1; sig < NSIG; ++sig) {
    SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));

    if (!ShouldTestSignal(sig)) {
      continue;
    }

#if defined(OS_MACOSX)
    if (sig == SIGBUS) {
      // Signal handlers can’t distinguish between SIGBUS arising out of a
      // hardware fault and SIGBUS raised asynchronously.
      // Signals::RestoreHandlerAndReraiseSignalOnReturn() assumes that SIGBUS
      // comes from a hardware fault, but this test uses raise(), so the
      // re-raise test must be skipped.
      continue;
    }
#endif  // defined(OS_MACOSX)

    SignalsTest test(SignalsTest::TestType::kHandlerReraisesToPrevious,
                     SignalsTest::SignalSource::kRaise,
                     sig);
    test.SetExpectedChildTermination(Multiprocess::kTerminationNormal,
                                     SignalsTest::kExitingHandlerExitStatus);
    test.Run();
  }
}

TEST(Signals, IsCrashSignal) {
  // Always crash signals.
  EXPECT_TRUE(Signals::IsCrashSignal(SIGABRT));
  EXPECT_TRUE(Signals::IsCrashSignal(SIGBUS));
  EXPECT_TRUE(Signals::IsCrashSignal(SIGFPE));
  EXPECT_TRUE(Signals::IsCrashSignal(SIGILL));
  EXPECT_TRUE(Signals::IsCrashSignal(SIGQUIT));
  EXPECT_TRUE(Signals::IsCrashSignal(SIGSEGV));
  EXPECT_TRUE(Signals::IsCrashSignal(SIGSYS));
  EXPECT_TRUE(Signals::IsCrashSignal(SIGTRAP));

  // Always terminate signals.
  EXPECT_FALSE(Signals::IsCrashSignal(SIGALRM));
  EXPECT_FALSE(Signals::IsCrashSignal(SIGHUP));
  EXPECT_FALSE(Signals::IsCrashSignal(SIGINT));
  EXPECT_FALSE(Signals::IsCrashSignal(SIGPIPE));
  EXPECT_FALSE(Signals::IsCrashSignal(SIGPROF));
  EXPECT_FALSE(Signals::IsCrashSignal(SIGTERM));
  EXPECT_FALSE(Signals::IsCrashSignal(SIGUSR1));
  EXPECT_FALSE(Signals::IsCrashSignal(SIGUSR2));
  EXPECT_FALSE(Signals::IsCrashSignal(SIGVTALRM));

  // Never crash or terminate signals.
  EXPECT_FALSE(Signals::IsCrashSignal(SIGCHLD));
  EXPECT_FALSE(Signals::IsCrashSignal(SIGCONT));
  EXPECT_FALSE(Signals::IsCrashSignal(SIGTSTP));
  EXPECT_FALSE(Signals::IsCrashSignal(SIGTTIN));
  EXPECT_FALSE(Signals::IsCrashSignal(SIGTTOU));
  EXPECT_FALSE(Signals::IsCrashSignal(SIGURG));
  EXPECT_FALSE(Signals::IsCrashSignal(SIGWINCH));
}

TEST(Signals, IsTerminateSignal) {
  // Always terminate signals.
  EXPECT_TRUE(Signals::IsTerminateSignal(SIGALRM));
  EXPECT_TRUE(Signals::IsTerminateSignal(SIGHUP));
  EXPECT_TRUE(Signals::IsTerminateSignal(SIGINT));
  EXPECT_TRUE(Signals::IsTerminateSignal(SIGPIPE));
  EXPECT_TRUE(Signals::IsTerminateSignal(SIGPROF));
  EXPECT_TRUE(Signals::IsTerminateSignal(SIGTERM));
  EXPECT_TRUE(Signals::IsTerminateSignal(SIGUSR1));
  EXPECT_TRUE(Signals::IsTerminateSignal(SIGUSR2));
  EXPECT_TRUE(Signals::IsTerminateSignal(SIGVTALRM));

  // Always crash signals.
  EXPECT_FALSE(Signals::IsTerminateSignal(SIGABRT));
  EXPECT_FALSE(Signals::IsTerminateSignal(SIGBUS));
  EXPECT_FALSE(Signals::IsTerminateSignal(SIGFPE));
  EXPECT_FALSE(Signals::IsTerminateSignal(SIGILL));
  EXPECT_FALSE(Signals::IsTerminateSignal(SIGQUIT));
  EXPECT_FALSE(Signals::IsTerminateSignal(SIGSEGV));
  EXPECT_FALSE(Signals::IsTerminateSignal(SIGSYS));
  EXPECT_FALSE(Signals::IsTerminateSignal(SIGTRAP));

  // Never crash or terminate signals.
  EXPECT_FALSE(Signals::IsTerminateSignal(SIGCHLD));
  EXPECT_FALSE(Signals::IsTerminateSignal(SIGCONT));
  EXPECT_FALSE(Signals::IsTerminateSignal(SIGTSTP));
  EXPECT_FALSE(Signals::IsTerminateSignal(SIGTTIN));
  EXPECT_FALSE(Signals::IsTerminateSignal(SIGTTOU));
  EXPECT_FALSE(Signals::IsTerminateSignal(SIGURG));
  EXPECT_FALSE(Signals::IsTerminateSignal(SIGWINCH));
}

}  // namespace
}  // namespace test
}  // namespace crashpad