[HWASan] Intercept setjmp/longjmp on x86_64.

Reviewed By: xiangzhangllvm

Differential Revision: https://reviews.llvm.org/D109790
This commit is contained in:
Matt Morehouse 2021-09-17 07:08:57 -07:00
parent 6fb01a9470
commit 750d5fc65c
7 changed files with 125 additions and 17 deletions

View File

@ -15,7 +15,8 @@ set(HWASAN_RTL_SOURCES
hwasan_memintrinsics.cpp
hwasan_poisoning.cpp
hwasan_report.cpp
hwasan_setjmp.S
hwasan_setjmp_aarch64.S
hwasan_setjmp_x86_64.S
hwasan_tag_mismatch_aarch64.S
hwasan_thread.cpp
hwasan_thread_list.cpp

View File

@ -187,13 +187,17 @@ void HwasanTagMismatch(uptr addr, uptr access_info, uptr *registers_frame,
RunFreeHooks(ptr); \
} while (false)
#if HWASAN_WITH_INTERCEPTORS && defined(__aarch64__)
#if HWASAN_WITH_INTERCEPTORS
// 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];
// interception code is platform specific too.
# if defined(__aarch64__)
constexpr size_t kHwRegisterBufSize = 22;
# elif defined(__x86_64__)
constexpr size_t kHwRegisterBufSize = 8;
# endif
typedef unsigned long long __hw_register_buf[kHwRegisterBufSize];
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
@ -210,7 +214,7 @@ struct __hw_jmp_buf_struct {
typedef struct __hw_jmp_buf_struct __hw_jmp_buf[1];
typedef struct __hw_jmp_buf_struct __hw_sigjmp_buf[1];
constexpr unsigned kHwJmpBufMagic = 0x248ACE77;
#endif // HWASAN_WITH_INTERCEPTORS && __aarch64__
#endif // HWASAN_WITH_INTERCEPTORS
#define ENSURE_HWASAN_INITED() \
do { \

View File

@ -49,9 +49,7 @@ INTERCEPTOR(int, pthread_create, void *th, void *attr, void *(*callback)(void*),
DEFINE_REAL(int, vfork)
DECLARE_EXTERN_INTERCEPTOR_AND_WRAPPER(int, vfork)
#endif // HWASAN_WITH_INTERCEPTORS
#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);
@ -67,8 +65,14 @@ extern "C" int __sigjmp_save(__hw_sigjmp_buf env, int savemask) {
static void __attribute__((always_inline))
InternalLongjmp(__hw_register_buf env, int retval) {
# if defined(__aarch64__)
constexpr size_t kSpIndex = 13;
# elif defined(__x86_64__)
constexpr size_t kSpIndex = 6;
# endif
// Clear all memory tags on the stack between here and where we're going.
unsigned long long stack_pointer = env[13];
unsigned long long stack_pointer = env[kSpIndex];
// 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);
@ -79,6 +83,7 @@ InternalLongjmp(__hw_register_buf env, int retval) {
// 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.
# if defined(__aarch64__)
register long int retval_tmp asm("x1") = retval;
register void *env_address asm("x0") = &env[0];
asm volatile("ldp x19, x20, [%0, #0<<3];"
@ -101,6 +106,26 @@ InternalLongjmp(__hw_register_buf env, int retval) {
"br x30;"
: "+r"(env_address)
: "r"(retval_tmp));
# elif defined(__x86_64__)
register long int retval_tmp asm("%rsi") = retval;
register void *env_address asm("%rdi") = &env[0];
asm volatile(
// Restore registers.
"mov (0*8)(%0),%%rbx;"
"mov (1*8)(%0),%%rbp;"
"mov (2*8)(%0),%%r12;"
"mov (3*8)(%0),%%r13;"
"mov (4*8)(%0),%%r14;"
"mov (5*8)(%0),%%r15;"
"mov (6*8)(%0),%%rsp;"
"mov (7*8)(%0),%%rdx;"
// Return 1 if retval is 0.
"mov $1,%%rax;"
"test %1,%1;"
"cmovnz %1,%%rax;"
"jmp *%%rdx;" ::"r"(env_address),
"r"(retval_tmp));
# endif
}
INTERCEPTOR(void, siglongjmp, __hw_sigjmp_buf env, int val) {
@ -139,7 +164,7 @@ INTERCEPTOR(void, longjmp, __hw_jmp_buf env, int val) {
#undef SIG_BLOCK
#undef SIG_SETMASK
#endif // HWASAN_WITH_INTERCEPTORS && __aarch64__
# endif // HWASAN_WITH_INTERCEPTORS
namespace __hwasan {
@ -158,11 +183,9 @@ void InitializeInterceptors() {
#if HWASAN_WITH_INTERCEPTORS
#if defined(__linux__)
# if defined(__aarch64__)
INTERCEPT_FUNCTION(__libc_longjmp);
INTERCEPT_FUNCTION(longjmp);
INTERCEPT_FUNCTION(siglongjmp);
# endif // __aarch64__
INTERCEPT_FUNCTION(vfork);
#endif // __linux__
INTERCEPT_FUNCTION(pthread_create);

View File

@ -1,4 +1,4 @@
//===-- hwasan_setjmp.S --------------------------------------------------------===//
//===-- hwasan_setjmp_aarch64.S -------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@ -29,7 +29,7 @@
// Hence we have to write this function in assembly.
.section .text
.file "hwasan_setjmp.S"
.file "hwasan_setjmp_aarch64.S"
.global __interceptor_setjmp
ASM_TYPE_FUNCTION(__interceptor_setjmp)

View File

@ -0,0 +1,81 @@
//===-- hwasan_setjmp_x86_64.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
//
//===----------------------------------------------------------------------===//
//
// setjmp interceptor for x86_64.
//
//===----------------------------------------------------------------------===//
#include "sanitizer_common/sanitizer_asm.h"
#if HWASAN_WITH_INTERCEPTORS && defined(__x86_64__)
#include "sanitizer_common/sanitizer_platform.h"
// We want to save the context of the calling function.
// That requires
// 1) No modification of the return address 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.
//
// TODO: Handle Intel CET.
.section .text
.file "hwasan_setjmp_x86_64.S"
.global __interceptor_setjmp
ASM_TYPE_FUNCTION(__interceptor_setjmp)
__interceptor_setjmp:
CFI_STARTPROC
xorl %esi, %esi
jmp __interceptor_sigsetjmp
CFI_ENDPROC
ASM_SIZE(__interceptor_setjmp)
.global __interceptor_sigsetjmp
ASM_TYPE_FUNCTION(__interceptor_sigsetjmp)
__interceptor_sigsetjmp:
CFI_STARTPROC
// Save callee save registers.
mov %rbx, (0*8)(%rdi)
mov %rbp, (1*8)(%rdi)
mov %r12, (2*8)(%rdi)
mov %r13, (3*8)(%rdi)
mov %r14, (4*8)(%rdi)
mov %r15, (5*8)(%rdi)
// Save SP as it was in caller's frame.
lea 8(%rsp), %rdx
mov %rdx, (6*8)(%rdi)
// Save return address.
mov (%rsp), %rax
mov %rax, (7*8)(%rdi)
jmp __sigjmp_save
CFI_ENDPROC
ASM_SIZE(__interceptor_sigsetjmp)
.macro WEAK_ALIAS first second
.globl \second
.equ \second\(), \first
.weak \second
.endm
WEAK_ALIAS __interceptor_sigsetjmp, __sigsetjmp
WEAK_ALIAS __interceptor_setjmp, _setjmp
#endif
// We do not need executable stack.
NO_EXEC_STACK_DIRECTIVE

View File

@ -19,7 +19,7 @@
#define CHECK_TYPE_SIZE_FITS(TYPE) \
COMPILER_CHECK(sizeof(__hw_##TYPE) <= sizeof(TYPE))
#if HWASAN_WITH_INTERCEPTORS && defined(__aarch64__)
#if HWASAN_WITH_INTERCEPTORS
CHECK_TYPE_SIZE_FITS(jmp_buf);
CHECK_TYPE_SIZE_FITS(sigjmp_buf);
#endif

View File

@ -1,8 +1,7 @@
// RUN: %clang_hwasan -g %s -o %t
// RUN: not %run %t 0 2>&1 | FileCheck %s
// RUN: not %run %t -33 2>&1 | FileCheck %s
// Only implemented for interceptor ABI on AArch64.
// REQUIRES: aarch64-target-arch
// REQUIRES: pointer-tagging
#include <assert.h>
#include <setjmp.h>