[compiler-rt] libhwasan interceptor ABI intercept longjmp/setjmp
authorDavid Tellenbach <david.tellenbach@arm.com>
Wed, 30 Oct 2019 14:04:40 +0000 (14:04 +0000)
committerDavid Tellenbach <david.tellenbach@arm.com>
Wed, 30 Oct 2019 14:04:40 +0000 (14:04 +0000)
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@arm.com>

compiler-rt/lib/hwasan/CMakeLists.txt
compiler-rt/lib/hwasan/hwasan.h
compiler-rt/lib/hwasan/hwasan_interceptors.cpp
compiler-rt/lib/hwasan/hwasan_setjmp.S [new file with mode: 0644]
compiler-rt/lib/hwasan/hwasan_type_test.cpp [new file with mode: 0644]
compiler-rt/test/hwasan/TestCases/longjmp-setjmp-interception.c [new file with mode: 0644]

index c976f99..6563c86 100644 (file)
@@ -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
index 9e0ced9..64cdcf3 100644 (file)
@@ -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
index 95e2e86..4f9bd34 100644 (file)
@@ -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 different 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 (file)
index 0000000..0c13543
--- /dev/null
@@ -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 (file)
index 0000000..8cff495
--- /dev/null
@@ -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 (file)
index 0000000..68c1aac
--- /dev/null
@@ -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;
+}