Skip to content

[libunwind] Replace process_vm_readv with SYS_rt_sigprocmask #74791

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jan 5, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 50 additions & 27 deletions libunwind/src/UnwindCursor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#define __UNWINDCURSOR_HPP__

#include "cet_unwind.h"
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
Expand Down Expand Up @@ -990,6 +992,7 @@ class UnwindCursor : public AbstractUnwindCursor{
R dummy;
return stepThroughSigReturn(dummy);
}
bool isReadableAddr(const pint_t addr) const;
#if defined(_LIBUNWIND_TARGET_AARCH64)
bool setInfoForSigReturn(Registers_arm64 &);
int stepThroughSigReturn(Registers_arm64 &);
Expand Down Expand Up @@ -2700,20 +2703,12 @@ bool UnwindCursor<A, R>::setInfoForSigReturn(Registers_arm64 &) {
// [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);
// directly accessing it could cause a SIGSEGV.
if (!isReadableAddr(pc))
return false;
auto *instructions = reinterpret_cast<const uint32_t *>(pc);
// Look for instructions: mov x8, #0x8b; svc #0x0
if (bytesRead != sizeof instructions || instructions[0] != 0xd2801168 ||
instructions[1] != 0xd4000001)
if (instructions[0] != 0xd2801168 || instructions[1] != 0xd4000001)
return false;

_info = {};
Expand Down Expand Up @@ -2762,18 +2757,17 @@ int UnwindCursor<A, R>::stepThroughSigReturn(Registers_arm64 &) {
template <typename A, typename R>
bool UnwindCursor<A, R>::setInfoForSigReturn(Registers_riscv &) {
const pint_t pc = static_cast<pint_t>(getReg(UNW_REG_IP));
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);
// The PC might contain an invalid address if the unwind info is bad, so
// directly accessing it could cause a SIGSEGV.
if (!isReadableAddr(pc))
return false;
const auto *instructions = reinterpret_cast<const uint32_t *>(pc);
// Look for the two instructions used in the sigreturn trampoline
// __vdso_rt_sigreturn:
//
// 0x08b00893 li a7,0x8b
// 0x00000073 ecall
if (bytesRead != sizeof instructions || instructions[0] != 0x08b00893 ||
instructions[1] != 0x00000073)
if (instructions[0] != 0x08b00893 || instructions[1] != 0x00000073)
return false;

_info = {};
Expand Down Expand Up @@ -2822,13 +2816,11 @@ bool UnwindCursor<A, R>::setInfoForSigReturn(Registers_s390x &) {
// onto the stack.
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.
uint16_t inst;
struct iovec local_iov = {&inst, sizeof inst};
struct iovec remote_iov = {reinterpret_cast<void *>(pc), sizeof inst};
long bytesRead = process_vm_readv(getpid(), &local_iov, 1, &remote_iov, 1, 0);
if (bytesRead == sizeof inst && (inst == 0x0a77 || inst == 0x0aad)) {
// directly accessing it could cause a SIGSEGV.
if (!isReadableAddr(pc))
return false;
const auto inst = *reinterpret_cast<const uint16_t *>(pc);
if (inst == 0x0a77 || inst == 0x0aad) {
_info = {};
_info.start_ip = pc;
_info.end_ip = pc + 2;
Expand Down Expand Up @@ -2974,6 +2966,37 @@ bool UnwindCursor<A, R>::getFunctionName(char *buf, size_t bufLen,
buf, bufLen, offset);
}

#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN)
template <typename A, typename R>
bool UnwindCursor<A, R>::isReadableAddr(const pint_t addr) const {
// We use SYS_rt_sigprocmask, inspired by Abseil's AddressIsReadable.

const auto sigsetAddr = reinterpret_cast<sigset_t *>(addr);
// We have to check that addr is nullptr because sigprocmask allows that
// as an argument without failure.
if (!sigsetAddr)
return false;
const auto saveErrno = errno;
// We MUST use a raw syscall here, as wrappers may try to access
// sigsetAddr which may cause a SIGSEGV. A raw syscall however is
// safe. Additionally, we need to pass the kernel_sigset_size, which is
// different from libc sizeof(sigset_t). For the majority of architectures,
// it's 64 bits (_NSIG), and libc NSIG is _NSIG + 1.
const auto kernelSigsetSize = NSIG / 8;
[[maybe_unused]] const int Result = syscall(
SYS_rt_sigprocmask, /*how=*/~0, sigsetAddr, nullptr, kernelSigsetSize);
// Because our "how" is invalid, this syscall should always fail, and our
// errno should always be EINVAL or an EFAULT. This relies on the Linux
// kernel to check copy_from_user before checking if the "how" argument is
// invalid.
assert(Result == -1);
assert(errno == EFAULT || errno == EINVAL);
const auto readable = errno != EFAULT;
errno = saveErrno;
return readable;
}
#endif

#if defined(_LIBUNWIND_USE_CET)
extern "C" void *__libunwind_cet_get_registers(unw_cursor_t *cursor) {
AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor;
Expand Down