Commit 43cfc627 authored by Clemens Backes's avatar Clemens Backes Committed by V8 LUCI CQ

[wasm] Fix memory protection tests for tier up

If background threads are tiering up, they could temporarily make code
writable (if using the mprotect based approach). This would make our
death tests fail (i.e. not crash).
This CL fixes that by repeatedly writing in that case. Eventually, the
code should be protected again, and then we would crash. Failure to
crash would manifest as a timeout of the tests.

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

Bug: v8:11974
Change-Id: Ibe34af499da9b964ad260d58e9b4e390007898e9
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/+/3151959
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#76770}
parent a207c15b
...@@ -69,7 +69,7 @@ class MemoryProtectionTest : public TestWithNativeContext { ...@@ -69,7 +69,7 @@ class MemoryProtectionTest : public TestWithNativeContext {
WasmCode* code() const { return code_; } WasmCode* code() const { return code_; }
bool code_is_protected() { bool code_is_protected() {
return V8_HAS_PTHREAD_JIT_WRITE_PROTECT || has_pku() || has_mprotect(); return V8_HAS_PTHREAD_JIT_WRITE_PROTECT || uses_pku() || uses_mprotect();
} }
void MakeCodeWritable() { void MakeCodeWritable() {
...@@ -78,17 +78,39 @@ class MemoryProtectionTest : public TestWithNativeContext { ...@@ -78,17 +78,39 @@ class MemoryProtectionTest : public TestWithNativeContext {
void WriteToCode() { code_->instructions()[0] = 0; } void WriteToCode() { code_->instructions()[0] = 0; }
private: void AssertCodeEventuallyProtected() {
bool has_pku() { if (!code_is_protected()) {
// Without protection, writing to code should always work.
WriteToCode();
return;
}
// Tier-up might be running and unprotecting the code region temporarily (if
// using mprotect). In that case, repeatedly write to the code region to
// make us eventually crash.
ASSERT_DEATH_IF_SUPPORTED(
do {
WriteToCode();
base::OS::Sleep(base::TimeDelta::FromMilliseconds(10));
} while (uses_mprotect()),
"");
}
bool uses_mprotect() {
// M1 always uses MAP_JIT.
if (V8_HAS_PTHREAD_JIT_WRITE_PROTECT) return false;
return mode_ == kMprotect ||
(mode_ == kPkuWithMprotectFallback && !uses_pku());
}
bool uses_pku() {
// M1 always uses MAP_JIT.
if (V8_HAS_PTHREAD_JIT_WRITE_PROTECT) return false;
bool param_has_pku = mode_ == kPku || mode_ == kPkuWithMprotectFallback; bool param_has_pku = mode_ == kPku || mode_ == kPkuWithMprotectFallback;
return param_has_pku && return param_has_pku &&
GetWasmCodeManager()->HasMemoryProtectionKeySupport(); GetWasmCodeManager()->HasMemoryProtectionKeySupport();
} }
bool has_mprotect() { private:
return mode_ == kMprotect || mode_ == kPkuWithMprotectFallback;
}
std::shared_ptr<NativeModule> CompileNativeModule() { std::shared_ptr<NativeModule> CompileNativeModule() {
// Define the bytes for a module with a single empty function. // Define the bytes for a module with a single empty function.
static const byte module_bytes[] = { static const byte module_bytes[] = {
...@@ -130,13 +152,6 @@ class ParameterizedMemoryProtectionTest ...@@ -130,13 +152,6 @@ class ParameterizedMemoryProtectionTest
void SetUp() override { Initialize(GetParam()); } void SetUp() override { Initialize(GetParam()); }
}; };
#define ASSERT_DEATH_IF_PROTECTED(code) \
if (code_is_protected()) { \
ASSERT_DEATH_IF_SUPPORTED(code, ""); \
} else { \
code; \
}
std::string PrintMemoryProtectionTestParam( std::string PrintMemoryProtectionTestParam(
::testing::TestParamInfo<MemoryProtectionMode> info) { ::testing::TestParamInfo<MemoryProtectionMode> info) {
return MemoryProtectionModeToString(info.param); return MemoryProtectionModeToString(info.param);
...@@ -149,7 +164,7 @@ INSTANTIATE_TEST_SUITE_P(MemoryProtection, ParameterizedMemoryProtectionTest, ...@@ -149,7 +164,7 @@ INSTANTIATE_TEST_SUITE_P(MemoryProtection, ParameterizedMemoryProtectionTest,
TEST_P(ParameterizedMemoryProtectionTest, CodeNotWritableAfterCompilation) { TEST_P(ParameterizedMemoryProtectionTest, CodeNotWritableAfterCompilation) {
CompileModule(); CompileModule();
ASSERT_DEATH_IF_PROTECTED(WriteToCode()); AssertCodeEventuallyProtected();
} }
TEST_P(ParameterizedMemoryProtectionTest, CodeWritableWithinScope) { TEST_P(ParameterizedMemoryProtectionTest, CodeWritableWithinScope) {
...@@ -166,7 +181,7 @@ TEST_P(ParameterizedMemoryProtectionTest, CodeNotWritableAfterScope) { ...@@ -166,7 +181,7 @@ TEST_P(ParameterizedMemoryProtectionTest, CodeNotWritableAfterScope) {
MakeCodeWritable(); MakeCodeWritable();
WriteToCode(); WriteToCode();
} }
ASSERT_DEATH_IF_PROTECTED(WriteToCode()); AssertCodeEventuallyProtected();
} }
#if V8_OS_POSIX && !V8_OS_FUCHSIA #if V8_OS_POSIX && !V8_OS_FUCHSIA
...@@ -242,13 +257,19 @@ class ParameterizedMemoryProtectionTestWithSignalHandling ...@@ -242,13 +257,19 @@ class ParameterizedMemoryProtectionTestWithSignalHandling
if (write_in_signal_handler) { if (write_in_signal_handler) {
signal_handler_scope.SetAddressToWriteToOnSignal(code_start_ptr); signal_handler_scope.SetAddressToWriteToOnSignal(code_start_ptr);
} }
// This will make us crash if code is protected and
// {write_in_signal_handler} is set. bool need_repeated_kills = uses_mprotect() && write_in_signal_handler;
{ do {
base::Optional<CodeSpaceWriteScope> write_scope; base::Optional<CodeSpaceWriteScope> write_scope;
if (open_write_scope) write_scope.emplace(native_module()); if (open_write_scope) write_scope.emplace(native_module());
// The signal handler will crash eventually if {write_in_signal_handler}
// is {true}. It might "accidentally" succeed though if tier-up is running
// in the background and using mprotect to unprotect the code for the
// whole process. In that case we repeatedly send the signal until we
// crash.
pthread_kill(pthread_self(), SIGPROF); pthread_kill(pthread_self(), SIGPROF);
} base::OS::Sleep(base::TimeDelta::FromMilliseconds(10));
} while (need_repeated_kills);
// If we write and code is protected, we never reach here. // If we write and code is protected, we never reach here.
CHECK(!write_in_signal_handler || !code_is_protected()); CHECK(!write_in_signal_handler || !code_is_protected());
...@@ -282,8 +303,8 @@ INSTANTIATE_TEST_SUITE_P( ...@@ -282,8 +303,8 @@ INSTANTIATE_TEST_SUITE_P(
TEST_P(ParameterizedMemoryProtectionTestWithSignalHandling, TestSignalHandler) { TEST_P(ParameterizedMemoryProtectionTestWithSignalHandling, TestSignalHandler) {
const bool write_in_signal_handler = std::get<1>(GetParam()); const bool write_in_signal_handler = std::get<1>(GetParam());
if (write_in_signal_handler) { if (write_in_signal_handler && code_is_protected()) {
ASSERT_DEATH_IF_PROTECTED(TestSignalHandler()); ASSERT_DEATH_IF_SUPPORTED(TestSignalHandler(), "");
} else { } else {
TestSignalHandler(); TestSignalHandler();
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment