Skip to content

Commit fc1c478

Browse files
[libunwind] Replace process_vm_readv with SYS_rt_sigprocmask (#74791)
process_vm_readv is generally considered dangerous from a syscall perspective, and is frequently blanket banned in seccomp filters such as those in Chromium and ChromiumOS. We can get the same behaviour during the invalid PC address case with the raw SYS_rt_sigprocmask syscall. Testing to ensure that process_vm_readv does not appear, I ran the output of check-unwind on an ARM64 device under strace. Previously, bad_unwind_info in particular would use process_vm_readv, but with this commit, it now no longer uses it: ``` strace test/Output/bad_unwind_info.pass.cpp.dir/t.tmp.exe \ |& grep process_vm_readv ``` The libunwind unittests were also tested on ARM64 ChromeOS (Gentoo Linux) devices.
1 parent 7c3bcc3 commit fc1c478

File tree

1 file changed

+50
-27
lines changed

1 file changed

+50
-27
lines changed

libunwind/src/UnwindCursor.hpp

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#define __UNWINDCURSOR_HPP__
1313

1414
#include "cet_unwind.h"
15+
#include <errno.h>
16+
#include <signal.h>
1517
#include <stdint.h>
1618
#include <stdio.h>
1719
#include <stdlib.h>
@@ -990,6 +992,7 @@ class UnwindCursor : public AbstractUnwindCursor{
990992
R dummy;
991993
return stepThroughSigReturn(dummy);
992994
}
995+
bool isReadableAddr(const pint_t addr) const;
993996
#if defined(_LIBUNWIND_TARGET_AARCH64)
994997
bool setInfoForSigReturn(Registers_arm64 &);
995998
int stepThroughSigReturn(Registers_arm64 &);
@@ -2700,20 +2703,12 @@ bool UnwindCursor<A, R>::setInfoForSigReturn(Registers_arm64 &) {
27002703
// [1] https://github.com/torvalds/linux/blob/master/arch/arm64/kernel/vdso/sigreturn.S
27012704
const pint_t pc = static_cast<pint_t>(this->getReg(UNW_REG_IP));
27022705
// The PC might contain an invalid address if the unwind info is bad, so
2703-
// directly accessing it could cause a segfault. Use process_vm_readv to read
2704-
// the memory safely instead. process_vm_readv was added in Linux 3.2, and
2705-
// AArch64 supported was added in Linux 3.7, so the syscall is guaranteed to
2706-
// be present. Unfortunately, there are Linux AArch64 environments where the
2707-
// libc wrapper for the syscall might not be present (e.g. Android 5), so call
2708-
// the syscall directly instead.
2709-
uint32_t instructions[2];
2710-
struct iovec local_iov = {&instructions, sizeof instructions};
2711-
struct iovec remote_iov = {reinterpret_cast<void *>(pc), sizeof instructions};
2712-
long bytesRead =
2713-
syscall(SYS_process_vm_readv, getpid(), &local_iov, 1, &remote_iov, 1, 0);
2706+
// directly accessing it could cause a SIGSEGV.
2707+
if (!isReadableAddr(pc))
2708+
return false;
2709+
auto *instructions = reinterpret_cast<const uint32_t *>(pc);
27142710
// Look for instructions: mov x8, #0x8b; svc #0x0
2715-
if (bytesRead != sizeof instructions || instructions[0] != 0xd2801168 ||
2716-
instructions[1] != 0xd4000001)
2711+
if (instructions[0] != 0xd2801168 || instructions[1] != 0xd4000001)
27172712
return false;
27182713

27192714
_info = {};
@@ -2762,18 +2757,17 @@ int UnwindCursor<A, R>::stepThroughSigReturn(Registers_arm64 &) {
27622757
template <typename A, typename R>
27632758
bool UnwindCursor<A, R>::setInfoForSigReturn(Registers_riscv &) {
27642759
const pint_t pc = static_cast<pint_t>(getReg(UNW_REG_IP));
2765-
uint32_t instructions[2];
2766-
struct iovec local_iov = {&instructions, sizeof instructions};
2767-
struct iovec remote_iov = {reinterpret_cast<void *>(pc), sizeof instructions};
2768-
long bytesRead =
2769-
syscall(SYS_process_vm_readv, getpid(), &local_iov, 1, &remote_iov, 1, 0);
2760+
// The PC might contain an invalid address if the unwind info is bad, so
2761+
// directly accessing it could cause a SIGSEGV.
2762+
if (!isReadableAddr(pc))
2763+
return false;
2764+
const auto *instructions = reinterpret_cast<const uint32_t *>(pc);
27702765
// Look for the two instructions used in the sigreturn trampoline
27712766
// __vdso_rt_sigreturn:
27722767
//
27732768
// 0x08b00893 li a7,0x8b
27742769
// 0x00000073 ecall
2775-
if (bytesRead != sizeof instructions || instructions[0] != 0x08b00893 ||
2776-
instructions[1] != 0x00000073)
2770+
if (instructions[0] != 0x08b00893 || instructions[1] != 0x00000073)
27772771
return false;
27782772

27792773
_info = {};
@@ -2822,13 +2816,11 @@ bool UnwindCursor<A, R>::setInfoForSigReturn(Registers_s390x &) {
28222816
// onto the stack.
28232817
const pint_t pc = static_cast<pint_t>(this->getReg(UNW_REG_IP));
28242818
// The PC might contain an invalid address if the unwind info is bad, so
2825-
// directly accessing it could cause a segfault. Use process_vm_readv to
2826-
// read the memory safely instead.
2827-
uint16_t inst;
2828-
struct iovec local_iov = {&inst, sizeof inst};
2829-
struct iovec remote_iov = {reinterpret_cast<void *>(pc), sizeof inst};
2830-
long bytesRead = process_vm_readv(getpid(), &local_iov, 1, &remote_iov, 1, 0);
2831-
if (bytesRead == sizeof inst && (inst == 0x0a77 || inst == 0x0aad)) {
2819+
// directly accessing it could cause a SIGSEGV.
2820+
if (!isReadableAddr(pc))
2821+
return false;
2822+
const auto inst = *reinterpret_cast<const uint16_t *>(pc);
2823+
if (inst == 0x0a77 || inst == 0x0aad) {
28322824
_info = {};
28332825
_info.start_ip = pc;
28342826
_info.end_ip = pc + 2;
@@ -2974,6 +2966,37 @@ bool UnwindCursor<A, R>::getFunctionName(char *buf, size_t bufLen,
29742966
buf, bufLen, offset);
29752967
}
29762968

2969+
#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN)
2970+
template <typename A, typename R>
2971+
bool UnwindCursor<A, R>::isReadableAddr(const pint_t addr) const {
2972+
// We use SYS_rt_sigprocmask, inspired by Abseil's AddressIsReadable.
2973+
2974+
const auto sigsetAddr = reinterpret_cast<sigset_t *>(addr);
2975+
// We have to check that addr is nullptr because sigprocmask allows that
2976+
// as an argument without failure.
2977+
if (!sigsetAddr)
2978+
return false;
2979+
const auto saveErrno = errno;
2980+
// We MUST use a raw syscall here, as wrappers may try to access
2981+
// sigsetAddr which may cause a SIGSEGV. A raw syscall however is
2982+
// safe. Additionally, we need to pass the kernel_sigset_size, which is
2983+
// different from libc sizeof(sigset_t). For the majority of architectures,
2984+
// it's 64 bits (_NSIG), and libc NSIG is _NSIG + 1.
2985+
const auto kernelSigsetSize = NSIG / 8;
2986+
[[maybe_unused]] const int Result = syscall(
2987+
SYS_rt_sigprocmask, /*how=*/~0, sigsetAddr, nullptr, kernelSigsetSize);
2988+
// Because our "how" is invalid, this syscall should always fail, and our
2989+
// errno should always be EINVAL or an EFAULT. This relies on the Linux
2990+
// kernel to check copy_from_user before checking if the "how" argument is
2991+
// invalid.
2992+
assert(Result == -1);
2993+
assert(errno == EFAULT || errno == EINVAL);
2994+
const auto readable = errno != EFAULT;
2995+
errno = saveErrno;
2996+
return readable;
2997+
}
2998+
#endif
2999+
29773000
#if defined(_LIBUNWIND_USE_CET)
29783001
extern "C" void *__libunwind_cet_get_registers(unw_cursor_t *cursor) {
29793002
AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor;

0 commit comments

Comments
 (0)