[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