Skip to content

Commit 4cd84ed

Browse files
committed
Return stack support
1 parent 487e6a9 commit 4cd84ed

6 files changed

+344
-2
lines changed

cmake/config-ix.cmake

+11-1
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ set(ALL_TSAN_SUPPORTED_ARCH ${X86_64} ${MIPS64} ${ARM64} ${PPC64})
207207
set(ALL_UBSAN_SUPPORTED_ARCH ${X86} ${X86_64} ${ARM32} ${ARM64}
208208
${MIPS32} ${MIPS64} ${PPC64} ${S390X})
209209
set(ALL_SAFESTACK_SUPPORTED_ARCH ${X86} ${X86_64} ${ARM64} ${MIPS32} ${MIPS64})
210+
set(ALL_RETURNSTACK_SUPPORTED_ARCH ${X86_64} ${ARM64})
210211
set(ALL_CFI_SUPPORTED_ARCH ${X86} ${X86_64} ${ARM32} ${ARM64} ${MIPS64})
211212
set(ALL_ESAN_SUPPORTED_ARCH ${X86_64} ${MIPS64})
212213
set(ALL_SCUDO_SUPPORTED_ARCH ${X86} ${X86_64} ${ARM32} ${ARM64} ${MIPS32} ${MIPS64})
@@ -451,6 +452,8 @@ else()
451452
filter_available_targets(UBSAN_SUPPORTED_ARCH ${ALL_UBSAN_SUPPORTED_ARCH})
452453
filter_available_targets(SAFESTACK_SUPPORTED_ARCH
453454
${ALL_SAFESTACK_SUPPORTED_ARCH})
455+
filter_available_targets(RETURNSTACK_SUPPORTED_ARCH
456+
${ALL_RETURNSTACK_SUPPORTED_ARCH})
454457
filter_available_targets(CFI_SUPPORTED_ARCH ${ALL_CFI_SUPPORTED_ARCH})
455458
filter_available_targets(ESAN_SUPPORTED_ARCH ${ALL_ESAN_SUPPORTED_ARCH})
456459
filter_available_targets(SCUDO_SUPPORTED_ARCH ${ALL_SCUDO_SUPPORTED_ARCH})
@@ -484,7 +487,7 @@ else()
484487
set(OS_NAME "${CMAKE_SYSTEM_NAME}")
485488
endif()
486489

487-
set(ALL_SANITIZERS asan;dfsan;msan;hwasan;tsan;safestack;cfi;esan;scudo;ubsan_minimal)
490+
set(ALL_SANITIZERS asan;dfsan;msan;hwasan;tsan;safestack;returnstack;cfi;esan;scudo;ubsan_minimal)
488491
set(COMPILER_RT_SANITIZERS_TO_BUILD all CACHE STRING
489492
"sanitizers to build if supported on the target (all;${ALL_SANITIZERS})")
490493
list_replace(COMPILER_RT_SANITIZERS_TO_BUILD all "${ALL_SANITIZERS}")
@@ -581,6 +584,13 @@ else()
581584
set(COMPILER_RT_HAS_SAFESTACK FALSE)
582585
endif()
583586

587+
if (COMPILER_RT_HAS_SANITIZER_COMMON AND RETURNSTACK_SUPPORTED_ARCH AND
588+
OS_NAME MATCHES "Linux")
589+
set(COMPILER_RT_HAS_RETURNSTACK TRUE)
590+
else()
591+
set(COMPILER_RT_HAS_RETURNSTACK FALSE)
592+
endif()
593+
584594
if (COMPILER_RT_HAS_SANITIZER_COMMON AND CFI_SUPPORTED_ARCH)
585595
set(COMPILER_RT_HAS_CFI TRUE)
586596
else()

lib/returnstack/CMakeLists.txt

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
add_compiler_rt_component(returnstack)
2+
3+
set(RETURNSTACK_SOURCES returnstack.cc)
4+
5+
include_directories(..)
6+
7+
set(RETURNSTACK_CFLAGS ${SANITIZER_COMMON_CFLAGS})
8+
9+
if (COMPILER_RT_DEFAULT_TARGET_ARCH STREQUAL "aarch64")
10+
list(APPEND RETURNSTACK_CFLAGS "-ffixed-x28")
11+
elseif (COMPILER_RT_DEFAULT_TARGET_ARCH STREQUAL "x86_64")
12+
list(APPEND RETURNSTACK_CFLAGS "-ffixed-r15")
13+
endif ()
14+
15+
foreach(arch ${RETURNSTACK_SUPPORTED_ARCH})
16+
add_compiler_rt_runtime(clang_rt.returnstack
17+
STATIC
18+
ARCHS ${arch}
19+
SOURCES ${RETURNSTACK_SOURCES}
20+
$<TARGET_OBJECTS:RTInterception.${arch}>
21+
CFLAGS ${RETURNSTACK_CFLAGS}
22+
PARENT_TARGET returnstack)
23+
endforeach()
24+

lib/returnstack/returnstack.cc

+217
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
//===-- returnstack.cc ----------------------------------------------------===//
2+
//
3+
// This file is distributed under the University of Illinois Open Source
4+
// License. See LICENSE.TXT for details.
5+
//
6+
// Author: Philipp Zieris <[email protected]>
7+
//
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// This file implements the runtime support for the return stack protection
11+
// mechanism. The runtime manages allocation/deallocation of return stacks
12+
// for the main thread, as well as all pthreads that are created/destroyed
13+
// during program execution.
14+
//
15+
//===----------------------------------------------------------------------===//
16+
17+
#include <pthread.h>
18+
#include <stdbool.h>
19+
#include <stdio.h>
20+
#include <stdlib.h>
21+
#include <string.h>
22+
#include <sys/mman.h>
23+
#include <sys/random.h>
24+
#include <unistd.h>
25+
26+
#include "interception/interception.h"
27+
28+
using namespace __sanitizer;
29+
30+
#if defined(__aarch64__)
31+
#include "rsp_routines_aarch64.inc"
32+
#elif defined(__x86_64__)
33+
#include "rsp_routines_x86_64.inc"
34+
#else
35+
#error "Return stack runtime support not available for this architecture."
36+
#endif
37+
38+
/// Page size obtained at runtime.
39+
static unsigned pageSize;
40+
41+
/// Base address of the return stack region (not a secret information).
42+
static uptr ReturnStackRegionBase;
43+
44+
/// The size of the return stack region is dependant on the architecture's user
45+
/// space size. The maximum return stack region size is 16 TB, which results in
46+
/// 32 entropy bits for randomizing return stacks.
47+
///
48+
/// Architecture User space Return stack region Entropy
49+
/// ARM64 256 TB 16 TB 32 bits
50+
/// x86-64 128 TB 16 TB 32 bits
51+
#if defined(__aarch64__)
52+
const unsigned long kReturnStackRegionSize = 0x100000000000UL;
53+
#elif defined(__x86_64__)
54+
const unsigned long kReturnStackRegionSize = 0x100000000000UL;
55+
#endif
56+
57+
/// Number of return stack pages.
58+
const unsigned kReturnStackPages = 8;
59+
60+
/// Number of guard pages.
61+
const unsigned kReturnStackGuardPages = 1;
62+
63+
/// Marker placed on the return stack between metadata and return addresses.
64+
#if SANITIZER_WORDSIZE == 64
65+
const unsigned long kReturnStackMarker = 0xffffffffffffffff;
66+
#else
67+
const unsigned long kReturnStackMarker = 0xffffffff;
68+
#endif
69+
70+
typedef struct thread_start {
71+
void *(*start_routine)(void *);
72+
void *arg;
73+
} thread_start_t;
74+
75+
static void NORETURN terminate(char const *message) {
76+
fprintf(stderr, "Return stack runtime error: %s.\n", message);
77+
exit(EXIT_FAILURE);
78+
}
79+
80+
static inline void return_stack_region_create() {
81+
uptr addr;
82+
83+
// Allocate the return stack region.
84+
addr = (uptr)mmap(0, kReturnStackRegionSize, PROT_NONE,
85+
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
86+
if (addr == (uptr)-1)
87+
terminate("Failed to allocate the return stack region");
88+
89+
ReturnStackRegionBase = addr;
90+
}
91+
92+
static inline bool contains_return_stack(uptr addr, size_t size) {
93+
int fd[2];
94+
uptr end;
95+
96+
// Open pipe for writing.
97+
if (pipe(fd) == -1)
98+
terminate("Failed to open pipe");
99+
100+
// Try to read from each page within the range.
101+
end = addr + size;
102+
while (addr < end) {
103+
if (write(fd[1], (void *)addr, 1) != 1)
104+
return false;
105+
addr += pageSize;
106+
}
107+
108+
return true;
109+
}
110+
111+
static void return_stack_create() {
112+
size_t guardsize, stacksize;
113+
uptr addr;
114+
ssize_t len = sizeof(uptr);
115+
116+
// Calculate guard and stack sizes.
117+
guardsize = kReturnStackGuardPages * pageSize;
118+
stacksize = kReturnStackPages * pageSize;
119+
120+
// Randomly choose a new return stack and set its access permissions.
121+
while (1) {
122+
if (getrandom(&addr, len, 0) < len)
123+
terminate("Failed to get random offset");
124+
addr %= kReturnStackRegionSize - stacksize - guardsize;
125+
addr &= ~((uptr)pageSize - 1);
126+
addr += ReturnStackRegionBase;
127+
if (!contains_return_stack(addr, stacksize + 2 * guardsize)) {
128+
if (mprotect((void *)(addr + guardsize), stacksize, PROT_READ | PROT_WRITE) == -1)
129+
terminate("Failed to set access permissions for return stack");
130+
break;
131+
}
132+
}
133+
134+
// Write base address of return stack to the RSP register.
135+
asm_write_rsp((void *)(addr + guardsize));
136+
addr = 0;
137+
138+
// Push stack size and marker.
139+
asm_push_rsp(stacksize);
140+
asm_push_rsp(kReturnStackMarker);
141+
}
142+
143+
static void return_stack_destroy(void *arg) {
144+
size_t stacksize;
145+
uptr addr;
146+
147+
// Unwind return stack until the marker is hit.
148+
asm_unwind_rsp(kReturnStackMarker);
149+
150+
// Read stack size and base address.
151+
asm_pop_rsp((size_t)stacksize);
152+
asm_read_rsp((uptr)addr);
153+
154+
// Remove access permissions from return stack.
155+
if (mprotect((void *)addr, stacksize, PROT_NONE) == -1)
156+
terminate("Failed to remove access permissions from return stack.");
157+
}
158+
159+
static void *thread_start(void *ts) {
160+
161+
void *(*start_routine)(void *) = ((thread_start_t *)ts)->start_routine;
162+
void *arg = ((thread_start_t *)ts)->arg;
163+
164+
memset(ts, 0, sizeof(thread_start_t));
165+
free(ts);
166+
167+
// Create return stack for the new thread.
168+
return_stack_create();
169+
170+
// Push our clean-up handler on the thread-cancellation stack.
171+
pthread_cleanup_push(return_stack_destroy, NULL);
172+
173+
// Call the actual start routine.
174+
start_routine(arg);
175+
176+
// Pop our clean-up handler.
177+
pthread_cleanup_pop(NULL);
178+
179+
return NULL;
180+
}
181+
182+
INTERCEPTOR(int, pthread_create, pthread_t *thread,
183+
const pthread_attr_t *attr,
184+
void *(*start_routine)(void*), void *arg) {
185+
186+
// This memory is freed by thread_start.
187+
thread_start_t *ts = (thread_start_t *)malloc(sizeof(thread_start_t));
188+
if (ts == NULL)
189+
terminate("Malloc failure");
190+
memset(ts, 0, sizeof(thread_start_t));
191+
ts->start_routine = start_routine;
192+
ts->arg = arg;
193+
194+
return REAL(pthread_create)(thread, attr, thread_start, ts);
195+
}
196+
197+
extern "C"
198+
__attribute__((visibility("default"))) void __return_stack_init() {
199+
200+
// Get the page size.
201+
if ((pageSize = sysconf(_SC_PAGESIZE)) == (unsigned)-1)
202+
terminate("Failed to retrieve page size");
203+
204+
// Create the return stack region and allocate a return stack for the main
205+
// thread.
206+
return_stack_region_create();
207+
return_stack_create();
208+
209+
// Initialize the pthread interceptor for thread allocation.
210+
INTERCEPT_FUNCTION(pthread_create);
211+
}
212+
213+
extern "C" {
214+
__attribute__((section(".preinit_array"), used))
215+
void (*__returnstack_preinit)(void) = __return_stack_init;
216+
}
217+
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//===-- rsp_routines_aarch64.inc ------------------------------------------===//
2+
//
3+
// This file is distributed under the University of Illinois Open Source
4+
// License. See LICENSE.TXT for details.
5+
//
6+
// Author: Philipp Zieris <[email protected]>
7+
//
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// This file provides macros for return stack manipulation on ARM64.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#define asm_pop_rsp(value) \
15+
asm volatile ("ldr %0, [x28, #-8]!" \
16+
: "=r" (value) \
17+
: /* No inputs */ \
18+
: "x28")
19+
20+
#define asm_push_rsp(value) \
21+
asm volatile ("str %0, [x28], #8" \
22+
: /* No outputs */ \
23+
: "r" (value) \
24+
: "x28")
25+
26+
#define asm_read_rsp(rsp) \
27+
asm volatile ("mov %0, x28" \
28+
: "=r" (rsp))
29+
30+
#define asm_unwind_rsp(marker) \
31+
asm volatile ("rs_unwind:" \
32+
"ldr x0, [x28, #-8]!;" \
33+
"cmp x0, %0;" \
34+
"bne rs_unwind;" \
35+
: /* No outputs */ \
36+
: "r" (marker) \
37+
: "cc", "x0", "x28")
38+
39+
#define asm_write_rsp(rsp) \
40+
asm volatile ("mov x28, %0" \
41+
: /* No outputs */ \
42+
: "r" (rsp) \
43+
: "x28");
44+
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//===-- rsp_routines_x86_64.inc -------------------------------------------===//
2+
//
3+
// This file is distributed under the University of Illinois Open Source
4+
// License. See LICENSE.TXT for details.
5+
//
6+
// Author: Philipp Zieris <[email protected]>
7+
//
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// This file provides macros for return stack manipulation on x86-64.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#define asm_pop_rsp(value) \
15+
asm volatile ("lea -0x8(%%r15), %%r15;" \
16+
"mov (%%r15), %0;" \
17+
: "=r" (value) \
18+
: /* No inputs */ \
19+
: "r15")
20+
21+
#define asm_push_rsp(value) \
22+
asm volatile ("mov %0, (%%r15);" \
23+
"lea 0x8(%%r15), %%r15;" \
24+
: /* No outputs */ \
25+
: "r" (value) \
26+
: "r15")
27+
28+
#define asm_read_rsp(rsp) \
29+
asm volatile ("movq %%r15, %0" \
30+
: "=r" (rsp))
31+
32+
#define asm_unwind_rsp(marker) \
33+
asm volatile ("rs_unwind:" \
34+
"lea -0x8(%%r15), %%r15;" \
35+
"cmp (%%r15), %0;" \
36+
"jne rs_unwind;" \
37+
: /* No outputs */ \
38+
: "r" (marker) \
39+
: "cc", "r15")
40+
41+
#define asm_write_rsp(rsp) \
42+
asm volatile ("mov %0, %%r15" \
43+
: /* No outputs */ \
44+
: "r" (rsp) \
45+
: "r15")
46+

test/CMakeLists.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ if(COMPILER_RT_CAN_EXECUTE_TESTS)
6868

6969
foreach(sanitizer ${COMPILER_RT_SANITIZERS_TO_BUILD})
7070
# cfi testing is gated on ubsan
71-
if(NOT ${sanitizer} STREQUAL cfi)
71+
# no testing for return stacks yet
72+
if(NOT ${sanitizer} STREQUAL cfi AND NOT ${sanitizer} STREQUAL returnstack)
7273
compiler_rt_test_runtime(${sanitizer})
7374
endif()
7475
endforeach()

0 commit comments

Comments
 (0)