#if defined(_LIBUNWIND_TARGET_LINUX) && \
(defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_S390X))
+#include <sys/syscall.h>
+#include <sys/uio.h>
+#include <unistd.h>
#define _LIBUNWIND_CHECK_LINUX_SIGRETURN 1
#endif
//
// [1] https://github.com/torvalds/linux/blob/master/arch/arm64/kernel/vdso/sigreturn.S
const pint_t pc = static_cast<pint_t>(this->getReg(UNW_REG_IP));
+ // The PC might contain an invalid address if the unwind info is bad, so
+ // directly accessing it could cause a segfault. Use process_vm_readv to read
+ // the memory safely instead. process_vm_readv was added in Linux 3.2, and
+ // AArch64 supported was added in Linux 3.7, so the syscall is guaranteed to
+ // be present. Unfortunately, there are Linux AArch64 environments where the
+ // libc wrapper for the syscall might not be present (e.g. Android 5), so call
+ // the syscall directly instead.
+ uint32_t instructions[2];
+ struct iovec local_iov = {&instructions, sizeof instructions};
+ struct iovec remote_iov = {reinterpret_cast<void *>(pc), sizeof instructions};
+ long bytesRead =
+ syscall(SYS_process_vm_readv, getpid(), &local_iov, 1, &remote_iov, 1, 0);
// Look for instructions: mov x8, #0x8b; svc #0x0
- if (_addressSpace.get32(pc) == 0xd2801168 &&
- _addressSpace.get32(pc + 4) == 0xd4000001) {
- _info = {};
- _info.start_ip = pc;
- _info.end_ip = pc + 4;
- _isSigReturn = true;
- return true;
- }
- return false;
+ if (bytesRead != sizeof instructions || instructions[0] != 0xd2801168 ||
+ instructions[1] != 0xd4000001)
+ return false;
+
+ _info = {};
+ _info.start_ip = pc;
+ _info.end_ip = pc + 4;
+ _isSigReturn = true;
+ return true;
}
template <typename A, typename R>
--- /dev/null
+// -*- 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
+//
+//===----------------------------------------------------------------------===//
+
+// Ensure that libunwind doesn't crash on invalid info; the Linux aarch64
+// sigreturn frame check would previously attempt to access invalid memory in
+// this scenario.
+// REQUIRES: linux && (target={{aarch64-.+}} || target={{x86_64-.+}})
+
+// GCC doesn't support __attribute__((naked)) on AArch64.
+// UNSUPPORTED: gcc
+
+// Inline assembly is incompatible with MSAN.
+// UNSUPPORTED: msan
+
+#undef NDEBUG
+#include <assert.h>
+#include <libunwind.h>
+#include <stdio.h>
+
+__attribute__((naked)) void bad_unwind_info() {
+#if defined(__aarch64__)
+ __asm__("// not using 0 because unwinder was already resilient to that\n"
+ "mov x8, #4\n"
+ "stp x30, x8, [sp, #-16]!\n"
+ ".cfi_def_cfa_offset 16\n"
+ "// purposely use incorrect offset for x30\n"
+ ".cfi_offset x30, -8\n"
+ "bl stepper\n"
+ "ldr x30, [sp], #16\n"
+ ".cfi_def_cfa_offset 0\n"
+ ".cfi_restore x30\n"
+ "ret\n");
+#elif defined(__x86_64__)
+ __asm__("pushq %rbx\n"
+ ".cfi_def_cfa_offset 16\n"
+ "movq 8(%rsp), %rbx\n"
+ "# purposely corrupt return value on stack\n"
+ "movq $4, 8(%rsp)\n"
+ "callq stepper\n"
+ "movq %rbx, 8(%rsp)\n"
+ "popq %rbx\n"
+ ".cfi_def_cfa_offset 8\n"
+ "ret\n");
+#else
+#error This test is only supported on aarch64 or x86-64
+#endif
+}
+
+extern "C" void stepper() {
+ unw_cursor_t cursor;
+ unw_context_t uc;
+ unw_getcontext(&uc);
+ unw_init_local(&cursor, &uc);
+ // stepping to bad_unwind_info should succeed
+ assert(unw_step(&cursor) > 0);
+ // stepping past bad_unwind_info should fail but not crash
+ assert(unw_step(&cursor) <= 0);
+}
+
+int main() { bad_unwind_info(); }