Add a new RunWithFaultHandler utility
This avoids the need for a global `afterspeculation` label.
Use to rewrite the Meltdown example.
diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt
index 79a10a7..0ce95d9 100644
--- a/demos/CMakeLists.txt
+++ b/demos/CMakeLists.txt
@@ -50,6 +50,10 @@
utils.cc
)
+if(UNIX)
+ target_sources(safeside PRIVATE faults.cc)
+endif()
+
# Configure the assembler. Set ASM_EXT (extension for assembly files) and
# ASM_PLATFORM (target CPU), which we'll use to add the right assembly
# implementation.
diff --git a/demos/faults.cc b/demos/faults.cc
new file mode 100644
index 0000000..78b621f
--- /dev/null
+++ b/demos/faults.cc
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under both the 3-Clause BSD License and the GPLv2, found in the
+ * LICENSE and LICENSE.GPL-2.0 files, respectively, in the root directory.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
+ */
+
+#include "faults.h"
+
+#include <setjmp.h>
+
+#include <cstring>
+
+namespace {
+
+// The fault handler that should be called from the signal handler, and the
+// sigjmp context that should be jumped to after that.
+//
+// Declaring these thread-local might be a bit aspirational -- they definitely
+// *shouldn't* be shared across threads, but the current implementation doesn't
+// necessarily guarantee it's safe to use RunWithFaultHandler on two threads
+// without external synchronization.
+thread_local PosixFaultHandler fault_handler;
+thread_local sigjmp_buf signal_handler_jmpbuf;
+
+void SignalHandler(int signal, siginfo_t *info, void *ucontext) {
+ fault_handler(signal, info, ucontext);
+ siglongjmp(signal_handler_jmpbuf, 1);
+}
+
+} // namespace
+
+bool RunWithFaultHandler(std::function<void()> inner,
+ PosixFaultHandler handler) {
+ struct sigaction sa = {};
+ sa.sa_sigaction = SignalHandler;
+
+ struct sigaction oldsa;
+ // This sets the signal handler for the entire process.
+ sigaction(SIGSEGV, &sa, &oldsa);
+
+ fault_handler = handler;
+ bool handled_fault = true;
+
+ // Use sigsetjmp/siglongjmp to save and restore signal mask. Otherwise we
+ // will jump out of the signal handler and leave the currently-being-handled
+ // signal blocked. The result of a SIGSEGV being raised while blocked is
+ // "undefined"[1], but in practice leads to killing the process.
+ //
+ // [1] https://www.man7.org/linux/man-pages/man2/sigprocmask.2.html#NOTES
+ if (sigsetjmp(signal_handler_jmpbuf, 1) == 0) {
+ inner();
+ handled_fault = false;
+ }
+
+ sigaction(SIGSEGV, &oldsa, nullptr);
+ fault_handler = {};
+
+ return handled_fault;
+}
diff --git a/demos/faults.h b/demos/faults.h
new file mode 100644
index 0000000..c7bcfdd
--- /dev/null
+++ b/demos/faults.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under both the 3-Clause BSD License and the GPLv2, found in the
+ * LICENSE and LICENSE.GPL-2.0 files, respectively, in the root directory.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
+ */
+
+#ifndef DEMOS_FAULTS_H_
+#define DEMOS_FAULTS_H_
+
+#include <signal.h>
+
+#include <functional>
+
+// See "The siginfo_t argument to a SA_SIGINFO handler"
+// at https://www.man7.org/linux/man-pages/man2/sigaction.2.html
+using PosixFaultHandler = std::function<void(int, siginfo_t*, void*)>;
+
+// Run `inner` with a signal handler installed to catch failure signals,
+// currently defined as `SIGSEGV`. If such a signal is raised, the signal
+// handler calls `handler` and then returns from RunWithFaultHandler.
+//
+// Returns true iff a signal was handled.
+bool RunWithFaultHandler(std::function<void()> inner,
+ PosixFaultHandler handler);
+
+// Convenience adapter for callers that don't need signal details.
+inline bool RunWithFaultHandler(std::function<void()> inner,
+ std::function<void()> handler) {
+ return RunWithFaultHandler(inner,
+ [handler](int, siginfo_t*, void*) { handler(); });
+}
+
+#endif // DEMOS_FAULTS_H_
diff --git a/demos/meltdown.cc b/demos/meltdown.cc
index e9832f1..9b357b2 100644
--- a/demos/meltdown.cc
+++ b/demos/meltdown.cc
@@ -22,8 +22,6 @@
#include <fstream>
#include <iostream>
-#include <signal.h>
-
#include "cache_sidechannel.h"
#include "instr.h"
#include "faults.h"
@@ -55,14 +53,19 @@
// value of the in-bounds access is usually different from the secret value
// we want to leak via out-of-bounds speculative access.
size_t safe_offset = run % strlen(public_data);
- ForceRead(oracle.data() + static_cast<size_t>(data[safe_offset]));
- // Access attempt to the kernel memory. This does not succeed
- // architecturally and kernel sends SIGSEGV instead.
- ForceRead(oracle.data() + static_cast<size_t>(data[offset]));
+ bool handled_fault = RunWithFaultHandler([&]() {
+ ForceRead(oracle.data() + static_cast<size_t>(data[safe_offset]));
- // SIGSEGV signal handler moves the instruction pointer to this label.
- asm volatile("afterspeculation:");
+ // Access attempt to the kernel memory. This does not succeed
+ // architecturally and kernel sends SIGSEGV instead.
+ ForceRead(oracle.data() + static_cast<size_t>(data[offset]));
+ }, [](){});
+
+ if (!handled_fault) {
+ std::cerr << "Read didn't yield expected fault" << std::endl;
+ exit(EXIT_FAILURE);
+ }
std::pair<bool, char> result =
sidechannel.RecomputeScores(data[safe_offset]);
@@ -92,7 +95,6 @@
in >> std::dec >> private_length;
in.close();
- OnSignalMoveRipToAfterspeculation(SIGSEGV);
std::cout << "Leaking the string: ";
std::cout.flush();
const size_t private_offset =