[wasm] Test code protection in signal handlers

Test that also signal handlers cannot write to code, even if a
{CodeSpaceWriteScope} is open when the signal is triggered.

R=jkummerow@chromium.org
CC=mpdenton@chromium.org

Bug: v8:11974
Change-Id: I1e49e4b31ba196948f7f7adfdf88675816e0a58a
Cq-Include-Trybots: luci.v8.try:v8_mac_arm64_rel_ng
Cq-Include-Trybots: luci.v8.try:v8_mac_arm64_dbg_ng
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3140607
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#76747}
This commit is contained in:
Clemens Backes 2021-09-08 15:23:14 +02:00 committed by V8 LUCI CQ
parent cd79e83f42
commit 06de35edb6

View File

@ -2,6 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "include/v8config.h"
// TODO(clemensb): Extend this to other OSes.
#if V8_OS_POSIX && !V8_OS_FUCHSIA
#include <signal.h>
#endif // V8_OS_POSIX && !V8_OS_FUCHSIA
#include "src/flags/flags.h"
#include "src/wasm/code-space-access.h"
#include "src/wasm/module-compiler.h"
@ -162,6 +169,127 @@ TEST_P(ParameterizedMemoryProtectionTest, CodeNotWritableAfterScope) {
ASSERT_DEATH_IF_PROTECTED(WriteToCode());
}
#if V8_OS_POSIX && !V8_OS_FUCHSIA
class ParameterizedMemoryProtectionTestWithSignalHandling
: public MemoryProtectionTest,
public ::testing::WithParamInterface<
std::tuple<MemoryProtectionMode, bool, bool>> {
public:
class SignalHandlerScope {
public:
SignalHandlerScope() {
CHECK_NULL(current_handler_scope_);
current_handler_scope_ = this;
struct sigaction sa;
sa.sa_sigaction = &HandleSignal;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK;
CHECK_EQ(0, sigaction(SIGPROF, &sa, &old_signal_handler_));
}
~SignalHandlerScope() {
CHECK_EQ(current_handler_scope_, this);
current_handler_scope_ = nullptr;
sigaction(SIGPROF, &old_signal_handler_, nullptr);
}
void SetAddressToWriteToOnSignal(uint8_t* address) {
CHECK_NULL(code_address_);
CHECK_NOT_NULL(address);
code_address_ = address;
}
int num_handled_signals() const { return handled_signals_; }
private:
static void HandleSignal(int signal, siginfo_t*, void*) {
if (signal == SIGPROF) {
printf("Handled SIGPROF.\n");
} else {
printf("Handled unknown signal: %d.\n", signal);
}
CHECK_NOT_NULL(current_handler_scope_);
current_handler_scope_->handled_signals_ += 1;
if (current_handler_scope_->code_address_ != nullptr) {
printf("Writing to %p.\n", current_handler_scope_->code_address_);
*current_handler_scope_->code_address_ = 0;
}
}
struct sigaction old_signal_handler_;
int handled_signals_ = 0;
uint8_t* code_address_ = nullptr;
// These are accessed from the signal handler.
static SignalHandlerScope* current_handler_scope_;
};
void SetUp() override { Initialize(std::get<0>(GetParam())); }
void TestSignalHandler() {
const bool write_in_signal_handler = std::get<1>(GetParam());
const bool open_write_scope = std::get<2>(GetParam());
CompileModule();
SignalHandlerScope signal_handler_scope;
CHECK_EQ(0, signal_handler_scope.num_handled_signals());
pthread_kill(pthread_self(), SIGPROF);
CHECK_EQ(1, signal_handler_scope.num_handled_signals());
uint8_t* code_start_ptr = &code()->instructions()[0];
uint8_t code_start = *code_start_ptr;
CHECK_NE(0, code_start);
if (write_in_signal_handler) {
signal_handler_scope.SetAddressToWriteToOnSignal(code_start_ptr);
}
// This will make us crash if code is protected and
// {write_in_signal_handler} is set.
{
base::Optional<CodeSpaceWriteScope> write_scope;
if (open_write_scope) write_scope.emplace(native_module());
pthread_kill(pthread_self(), SIGPROF);
}
// If we write and code is protected, we never reach here.
CHECK(!write_in_signal_handler || !code_is_protected());
CHECK_EQ(2, signal_handler_scope.num_handled_signals());
CHECK_EQ(write_in_signal_handler ? 0 : code_start, *code_start_ptr);
}
};
// static
ParameterizedMemoryProtectionTestWithSignalHandling::SignalHandlerScope*
ParameterizedMemoryProtectionTestWithSignalHandling::SignalHandlerScope::
current_handler_scope_ = nullptr;
std::string PrintMemoryProtectionAndSignalHandlingTestParam(
::testing::TestParamInfo<std::tuple<MemoryProtectionMode, bool, bool>>
info) {
MemoryProtectionMode protection_mode = std::get<0>(info.param);
const bool write_in_signal_handler = std::get<1>(info.param);
const bool open_write_scope = std::get<2>(info.param);
return std::string{MemoryProtectionModeToString(protection_mode)} + "_" +
(write_in_signal_handler ? "Write" : "NoWrite") + "_" +
(open_write_scope ? "WithScope" : "NoScope");
}
INSTANTIATE_TEST_SUITE_P(
MemoryProtection, ParameterizedMemoryProtectionTestWithSignalHandling,
::testing::Combine(::testing::Values(kNoProtection, kPku, kMprotect,
kPkuWithMprotectFallback),
::testing::Bool(), ::testing::Bool()),
PrintMemoryProtectionAndSignalHandlingTestParam);
TEST_P(ParameterizedMemoryProtectionTestWithSignalHandling, TestSignalHandler) {
const bool write_in_signal_handler = std::get<1>(GetParam());
if (write_in_signal_handler) {
ASSERT_DEATH_IF_PROTECTED(TestSignalHandler());
} else {
TestSignalHandler();
}
}
#endif // V8_OS_POSIX && !V8_OS_FUCHSIA
} // namespace wasm
} // namespace internal
} // namespace v8