[compiler-rt] 67474c6 - [compiler-rt] libhwasan interceptor ABI intercept longjmp/setjmp
David Tellenbach via llvm-commits
llvm-commits at lists.llvm.org
Wed Oct 30 07:09:09 PDT 2019
Author: David Tellenbach
Date: 2019-10-30T14:04:40Z
New Revision: 67474c60d344a061d6c3affb70e7c8a13c15f830
URL: https://github.com/llvm/llvm-project/commit/67474c60d344a061d6c3affb70e7c8a13c15f830
DIFF: https://github.com/llvm/llvm-project/commit/67474c60d344a061d6c3affb70e7c8a13c15f830.diff
LOG: [compiler-rt] libhwasan interceptor ABI intercept longjmp/setjmp
Summary:
The hwasan interceptor ABI doesn't have interceptors for longjmp and setjmp.
This patch introduces them.
We require the size of the jmp_buf on the platform to be at least as large as
the jmp_buf in our implementation. To enforce this we compile
hwasan_type_test.cpp that ensures a compile time failure if this is not true.
Tested on both GCC and clang using an AArch64 virtual machine.
Reviewers: eugenis, kcc, pcc, Sanatizers
Reviewed By: eugenis, Sanatizers
Tags: #sanatizers, #llvm
Differential Revision: https://reviews.llvm.org/D69045
Patch By: Matthew Malcomson <matthew.malcomson at arm.com>
Added:
compiler-rt/lib/hwasan/hwasan_setjmp.S
compiler-rt/lib/hwasan/hwasan_type_test.cpp
compiler-rt/test/hwasan/TestCases/longjmp-setjmp-interception.c
Modified:
compiler-rt/lib/hwasan/CMakeLists.txt
compiler-rt/lib/hwasan/hwasan.h
compiler-rt/lib/hwasan/hwasan_interceptors.cpp
Removed:
################################################################################
diff --git a/compiler-rt/lib/hwasan/CMakeLists.txt b/compiler-rt/lib/hwasan/CMakeLists.txt
index c976f994651b..6563c867127c 100644
--- a/compiler-rt/lib/hwasan/CMakeLists.txt
+++ b/compiler-rt/lib/hwasan/CMakeLists.txt
@@ -15,6 +15,8 @@ set(HWASAN_RTL_SOURCES
hwasan_tag_mismatch_aarch64.S
hwasan_thread.cpp
hwasan_thread_list.cpp
+ hwasan_type_test.cpp
+ hwasan_setjmp.S
)
set(HWASAN_RTL_CXX_SOURCES
diff --git a/compiler-rt/lib/hwasan/hwasan.h b/compiler-rt/lib/hwasan/hwasan.h
index 9e0ced93b55d..64cdcf30f5c7 100644
--- a/compiler-rt/lib/hwasan/hwasan.h
+++ b/compiler-rt/lib/hwasan/hwasan.h
@@ -172,4 +172,24 @@ void AndroidTestTlsSlot();
RunFreeHooks(ptr); \
} while (false)
+#if HWASAN_WITH_INTERCEPTORS && defined(__aarch64__)
+// For both bionic and glibc __sigset_t is an unsigned long.
+typedef unsigned long __hw_sigset_t;
+// Setjmp and longjmp implementations are platform specific, and hence the
+// interception code is platform specific too. As yet we've only implemented
+// the interception for AArch64.
+typedef unsigned long long __hw_register_buf[22];
+struct __hw_jmp_buf_struct {
+ // NOTE: The machine-dependent definition of `__sigsetjmp'
+ // assume that a `__hw_jmp_buf' begins with a `__hw_register_buf' and that
+ // `__mask_was_saved' follows it. Do not move these members or add others
+ // before it.
+ __hw_register_buf __jmpbuf; // Calling environment.
+ int __mask_was_saved; // Saved the signal mask?
+ __hw_sigset_t __saved_mask; // Saved signal mask.
+};
+typedef struct __hw_jmp_buf_struct __hw_jmp_buf[1];
+typedef struct __hw_jmp_buf_struct __hw_sigjmp_buf[1];
+#endif // HWASAN_WITH_INTERCEPTORS && __aarch64__
+
#endif // HWASAN_H
diff --git a/compiler-rt/lib/hwasan/hwasan_interceptors.cpp b/compiler-rt/lib/hwasan/hwasan_interceptors.cpp
index 95e2e865717d..4f9bd3469eb1 100644
--- a/compiler-rt/lib/hwasan/hwasan_interceptors.cpp
+++ b/compiler-rt/lib/hwasan/hwasan_interceptors.cpp
@@ -220,6 +220,80 @@ DEFINE_REAL(int, vfork)
DECLARE_EXTERN_INTERCEPTOR_AND_WRAPPER(int, vfork)
#endif
+#if HWASAN_WITH_INTERCEPTORS && defined(__aarch64__)
+// Get and/or change the set of blocked signals.
+extern "C" int sigprocmask(int __how, const __hw_sigset_t *__restrict __set,
+ __hw_sigset_t *__restrict __oset);
+#define SIG_BLOCK 0
+#define SIG_SETMASK 2
+extern "C" int __sigjmp_save(__hw_sigjmp_buf env, int savemask) {
+ env[0].__mask_was_saved =
+ (savemask && sigprocmask(SIG_BLOCK, (__hw_sigset_t *)0,
+ &env[0].__saved_mask) == 0);
+ return 0;
+}
+
+static void __attribute__((always_inline))
+InternalLongjmp(__hw_register_buf env, int retval) {
+ // Clear all memory tags on the stack between here and where we're going.
+ unsigned long long stack_pointer = env[13];
+ // The stack pointer should never be tagged, so we don't need to clear the
+ // tag for this function call.
+ __hwasan_handle_longjmp((void *)stack_pointer);
+
+ // Run code for handling a longjmp.
+ // Need to use a register that isn't going to be loaded from the environment
+ // buffer -- hence why we need to specify the register to use.
+ // Must implement this ourselves, since we don't know the order of registers
+ // in
diff erent libc implementations and many implementations mangle the
+ // stack pointer so we can't use it without knowing the demangling scheme.
+ register long int retval_tmp asm("x1") = retval;
+ register void *env_address asm("x0") = &env[0];
+ asm volatile("ldp x19, x20, [%0, #0<<3];"
+ "ldp x21, x22, [%0, #2<<3];"
+ "ldp x23, x24, [%0, #4<<3];"
+ "ldp x25, x26, [%0, #6<<3];"
+ "ldp x27, x28, [%0, #8<<3];"
+ "ldp x29, x30, [%0, #10<<3];"
+ "ldp d8, d9, [%0, #14<<3];"
+ "ldp d10, d11, [%0, #16<<3];"
+ "ldp d12, d13, [%0, #18<<3];"
+ "ldp d14, d15, [%0, #20<<3];"
+ "ldr x5, [%0, #13<<3];"
+ "mov sp, x5;"
+ // Return the value requested to return through arguments.
+ // This should be in x1 given what we requested above.
+ "cmp %1, #0;"
+ "mov x0, #1;"
+ "csel x0, %1, x0, ne;"
+ "br x30;"
+ : "+r"(env_address)
+ : "r"(retval_tmp));
+}
+
+INTERCEPTOR(void, siglongjmp, __hw_sigjmp_buf env, int val) {
+ if (env[0].__mask_was_saved)
+ // Restore the saved signal mask.
+ (void)sigprocmask(SIG_SETMASK, &env[0].__saved_mask,
+ (__hw_sigset_t *)0);
+ InternalLongjmp(env[0].__jmpbuf, val);
+}
+
+// Required since glibc libpthread calls __libc_longjmp on pthread_exit, and
+// _setjmp on start_thread. Hence we have to intercept the longjmp on
+// pthread_exit so the __hw_jmp_buf order matches.
+INTERCEPTOR(void, __libc_longjmp, __hw_jmp_buf env, int val) {
+ InternalLongjmp(env[0].__jmpbuf, val);
+}
+
+INTERCEPTOR(void, longjmp, __hw_jmp_buf env, int val) {
+ InternalLongjmp(env[0].__jmpbuf, val);
+}
+#undef SIG_BLOCK
+#undef SIG_SETMASK
+
+#endif // HWASAN_WITH_INTERCEPTORS && __aarch64__
+
static void BeforeFork() {
StackDepotLockAll();
}
diff --git a/compiler-rt/lib/hwasan/hwasan_setjmp.S b/compiler-rt/lib/hwasan/hwasan_setjmp.S
new file mode 100644
index 000000000000..0c1354331940
--- /dev/null
+++ b/compiler-rt/lib/hwasan/hwasan_setjmp.S
@@ -0,0 +1,100 @@
+//===-- hwasan_setjmp.S --------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of HWAddressSanitizer.
+//
+// HWAddressSanitizer runtime.
+//===----------------------------------------------------------------------===//
+
+#include "sanitizer_common/sanitizer_asm.h"
+
+#if HWASAN_WITH_INTERCEPTORS && defined(__aarch64__)
+#include "sanitizer_common/sanitizer_platform.h"
+
+// We want to save the context of the calling function.
+// That requires
+// 1) No modification of the link register by this function.
+// 2) No modification of the stack pointer by this function.
+// 3) (no modification of any other saved register, but that's not really going
+// to occur, and hence isn't as much of a worry).
+//
+// There's essentially no way to ensure that the compiler will not modify the
+// stack pointer when compiling a C function.
+// Hence we have to write this function in assembly.
+
+.section .text
+.file "hwasan_setjmp.S"
+
+.global __interceptor_setjmp
+ASM_TYPE_FUNCTION(__interceptor_setjmp)
+__interceptor_setjmp:
+ CFI_STARTPROC
+ mov x1, #0
+ b __interceptor_sigsetjmp
+ CFI_ENDPROC
+ASM_SIZE(__interceptor_setjmp)
+
+#if SANITIZER_ANDROID
+// Bionic also defines a function `setjmp` that calls `sigsetjmp` saving the
+// current signal.
+.global __interceptor_setjmp_bionic
+ASM_TYPE_FUNCTION(__interceptor_setjmp_bionic)
+__interceptor_setjmp_bionic:
+ CFI_STARTPROC
+ mov x1, #1
+ b __interceptor_sigsetjmp
+ CFI_ENDPROC
+ASM_SIZE(__interceptor_setjmp_bionic)
+#endif
+
+.global __interceptor_sigsetjmp
+ASM_TYPE_FUNCTION(__interceptor_sigsetjmp)
+__interceptor_sigsetjmp:
+ CFI_STARTPROC
+ stp x19, x20, [x0, #0<<3]
+ stp x21, x22, [x0, #2<<3]
+ stp x23, x24, [x0, #4<<3]
+ stp x25, x26, [x0, #6<<3]
+ stp x27, x28, [x0, #8<<3]
+ stp x29, x30, [x0, #10<<3]
+ stp d8, d9, [x0, #14<<3]
+ stp d10, d11, [x0, #16<<3]
+ stp d12, d13, [x0, #18<<3]
+ stp d14, d15, [x0, #20<<3]
+ mov x2, sp
+ str x2, [x0, #13<<3]
+ // We always have the second argument to __sigjmp_save (savemask) set, since
+ // the _setjmp function above has set it for us as `false`.
+ // This function is defined in hwasan_interceptors.cc
+ b __sigjmp_save
+ CFI_ENDPROC
+ASM_SIZE(__interceptor_sigsetjmp)
+
+
+.macro ALIAS first second
+ .globl \second
+ .equ \second\(), \first
+.endm
+
+#if SANITIZER_ANDROID
+ALIAS __interceptor_sigsetjmp, sigsetjmp
+.weak sigsetjmp
+
+ALIAS __interceptor_setjmp_bionic, setjmp
+.weak setjmp
+#else
+ALIAS __interceptor_sigsetjmp, __sigsetjmp
+.weak __sigsetjmp
+#endif
+
+ALIAS __interceptor_setjmp, _setjmp
+.weak _setjmp
+#endif
+
+// We do not need executable stack.
+NO_EXEC_STACK_DIRECTIVE
diff --git a/compiler-rt/lib/hwasan/hwasan_type_test.cpp b/compiler-rt/lib/hwasan/hwasan_type_test.cpp
new file mode 100644
index 000000000000..8cff495bae15
--- /dev/null
+++ b/compiler-rt/lib/hwasan/hwasan_type_test.cpp
@@ -0,0 +1,25 @@
+//===-- hwasan_type_test.cpp ------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of HWAddressSanitizer.
+//
+// Compile-time tests of the internal type definitions.
+//===----------------------------------------------------------------------===//
+
+#include "interception/interception.h"
+#include "sanitizer_common/sanitizer_platform_limits_posix.h"
+#include "hwasan.h"
+#include <setjmp.h>
+
+#define CHECK_TYPE_SIZE_FITS(TYPE) \
+ COMPILER_CHECK(sizeof(__hw_##TYPE) <= sizeof(TYPE))
+
+#if HWASAN_WITH_INTERCEPTORS && defined(__aarch64__)
+CHECK_TYPE_SIZE_FITS(jmp_buf);
+CHECK_TYPE_SIZE_FITS(sigjmp_buf);
+#endif
diff --git a/compiler-rt/test/hwasan/TestCases/longjmp-setjmp-interception.c b/compiler-rt/test/hwasan/TestCases/longjmp-setjmp-interception.c
new file mode 100644
index 000000000000..68c1aaca0c61
--- /dev/null
+++ b/compiler-rt/test/hwasan/TestCases/longjmp-setjmp-interception.c
@@ -0,0 +1,39 @@
+// RUN: %clang_hwasan -g %s -o %t && not %run %t 2>&1 | FileCheck %s
+// Only implemented for interceptor ABI on AArch64.
+// REQUIRES: aarch64-target-arch
+
+#include <setjmp.h>
+#include <stdio.h>
+
+/* Testing longjmp/setjmp should test that accesses to scopes jmp'd over are
+ caught. */
+int __attribute__((noinline))
+uses_longjmp(int **other_array, int num, jmp_buf env) {
+ int internal_array[100] = {0};
+ *other_array = &internal_array[0];
+ if (num % 2)
+ longjmp(env, num);
+ else
+ return num % 8;
+}
+
+int __attribute__((noinline)) uses_setjmp(int num) {
+ int big_array[100];
+ int *other_array = NULL;
+ sigjmp_buf cur_env;
+ int temp = 0;
+ if ((temp = sigsetjmp(cur_env, 1)) != 0) {
+ // We're testing that our longjmp interceptor untagged the previous stack.
+ // Hence the tag in memory should be zero.
+ if (other_array != NULL)
+ return other_array[0];
+ // CHECK: READ of size 4 at{{.*}}tags: {{..}}/00
+ return 100;
+ } else
+ return uses_longjmp(&other_array, num, cur_env);
+}
+
+int __attribute__((noinline)) main() {
+ uses_setjmp(1);
+ return 0;
+}
More information about the llvm-commits
mailing list