aboutsummaryrefslogtreecommitdiffstats
path: root/vespalib/src/tests/util/memory_trap/memory_trap_test.cpp
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@yahooinc.com>2023-01-11 10:31:06 +0000
committerTor Brede Vekterli <vekterli@yahooinc.com>2023-01-11 12:29:54 +0000
commitde82bfc3fe492c406a69799f11530fe3b9b97d57 (patch)
tree2772efbdb8060648d6acd277b597134b86981194 /vespalib/src/tests/util/memory_trap/memory_trap_test.cpp
parent1402b8c5ac7cf608bcbdabb46873d6a3308ab69d (diff)
Add utility functionality for trapping memory accesses
At its core this is a way to wrap a range of memory in a way where all non-zero writes are eventually detected and, if supported by the underlying OS and hardware, instantly detects both reads and writes to as much as possible of the underlying memory range. Implemented using `mprotect` of page-aligned regions under the hood.
Diffstat (limited to 'vespalib/src/tests/util/memory_trap/memory_trap_test.cpp')
-rw-r--r--vespalib/src/tests/util/memory_trap/memory_trap_test.cpp61
1 files changed, 61 insertions, 0 deletions
diff --git a/vespalib/src/tests/util/memory_trap/memory_trap_test.cpp b/vespalib/src/tests/util/memory_trap/memory_trap_test.cpp
new file mode 100644
index 00000000000..ee26231c546
--- /dev/null
+++ b/vespalib/src/tests/util/memory_trap/memory_trap_test.cpp
@@ -0,0 +1,61 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/util/memory_trap.h>
+#include <vespa/vespalib/gtest/gtest.h>
+#include <cstdlib>
+
+using namespace vespalib;
+using namespace ::testing;
+
+template <typename T>
+void do_not_optimize_away(T&& t) noexcept {
+ asm volatile("" : : "m"(t) : "memory"); // Clobber the value to avoid losing it to compiler optimizations
+}
+
+struct MemoryTrapTest : Test {
+ static void SetUpTestSuite() {
+ // Don't overwrite env var if already set; we'll assume it's done for a good reason.
+ setenv("VESPA_USE_MPROTECT_TRAP", "yes", 0);
+ }
+};
+
+TEST_F(MemoryTrapTest, untouched_memory_traps_do_not_trigger) {
+ InlineMemoryTrap<2> stack_trap;
+ HeapMemoryTrap heap_trap(4);
+ // No touching == no crashing. Good times.
+}
+
+TEST_F(MemoryTrapTest, write_to_stack_trap_eventually_discovered) {
+ // We don't explicitly test death messages since the way the process dies depends on
+ // whether mprotect is enabled, whether ASAN instrumentation is enabled etc.
+ ASSERT_DEATH({
+ InlineMemoryTrap<2> stack_trap;
+ // This may trigger immediately or on destruction. Either way it eventually kills the process.
+ stack_trap.trapper().buffer()[0] = 0x01;
+ },"");
+}
+
+TEST_F(MemoryTrapTest, write_to_heap_trap_eventually_discovered) {
+ ASSERT_DEATH({
+ HeapMemoryTrap heap_trap(4);
+ // This may trigger immediately or on destruction. Either way it eventually kills the process.
+ heap_trap.trapper().buffer()[heap_trap.trapper().size() - 1] = 0x01;
+ },"");
+}
+
+TEST_F(MemoryTrapTest, read_from_hw_backed_trap_crashes_process) {
+ if (!MemoryRangeTrapper::hw_trapping_enabled()) {
+ return;
+ }
+ ASSERT_DEATH({
+ HeapMemoryTrap heap_trap(4); // Entire buffer should always be covered
+ // Clobber trap just in case the compiler is clever enough to look into the trap implementation
+ // and see that we memset everything to zero and `dummy` can thus be constant-promoted to 0
+ // (probably won't dare to do this anyway due to opaque mprotect() that touches buffer pointer).
+ do_not_optimize_away(heap_trap);
+ char dummy = heap_trap.trapper().buffer()[0];
+ do_not_optimize_away(dummy); // never reached
+ },"");
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()