sled/3party/gperftools/src/check_address-inl.h
2024-03-17 10:11:06 +08:00

196 lines
6.4 KiB
C

// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*-
// Copyright (c) 2023, gperftools Contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// This is internal implementation details of
// stacktrace_generic_fp-inl.h module. We only split this into
// separate header to enable unit test coverage.
// This is only used on OS-es with mmap support.
#include <fcntl.h>
#include <signal.h>
#include <sys/mman.h>
#include <unistd.h>
#if HAVE_SYS_SYSCALL_H && !__APPLE__
#include <sys/syscall.h>
#endif
namespace {
#if defined(__linux__) && !defined(FORCE_PIPES)
#define CHECK_ADDRESS_USES_SIGPROCMASK
// Linux kernel ABI for sigprocmask requires us to pass exact sizeof
// for kernel's sigset_t. Which is 64-bit for most arches, with only
// notable exception of mips.
#if defined(__mips__)
static constexpr int kKernelSigSetSize = 16;
#else
static constexpr int kKernelSigSetSize = 8;
#endif
// For Linux we have two strategies. One is calling sigprocmask with
// bogus HOW argument and 'new' sigset arg our address. Kernel ends up
// reading new sigset before interpreting how. So then we either get
// EFAULT when addr is unreadable, or we get EINVAL for readable addr,
// but bogus HOW argument.
//
// We 'steal' this idea from abseil. But nothing guarantees this exact
// behavior of Linux. So to be future-compatible (some our binaries
// will run tens of years from the time they're compiled), we also
// have second more robust method.
bool CheckAccessSingleSyscall(uintptr_t addr, int pagesize) {
addr &= ~uintptr_t{15};
if (addr == 0) {
return false;
}
int rv = syscall(SYS_rt_sigprocmask, ~0, addr, uintptr_t{0}, kKernelSigSetSize);
RAW_CHECK(rv < 0, "sigprocmask(~0, addr, ...)");
return (errno != EFAULT);
}
// This is second strategy. Idea is more or less same as before, but
// we use SIG_BLOCK for HOW argument. Then if this succeeds (with side
// effect of blocking random set of signals), we simply restore
// previous signal mask.
bool CheckAccessTwoSyscalls(uintptr_t addr, int pagesize) {
addr &= ~uintptr_t{15};
if (addr == 0) {
return false;
}
uintptr_t old[(kKernelSigSetSize + sizeof(uintptr_t) - 1) / sizeof(uintptr_t)];
int rv = syscall(SYS_rt_sigprocmask, SIG_BLOCK, addr, old, kKernelSigSetSize);
if (rv == 0) {
syscall(SYS_rt_sigprocmask, SIG_SETMASK, old, nullptr, kKernelSigSetSize);
return true;
}
return false;
}
bool CheckAddressFirstCall(uintptr_t addr, int pagesize);
bool (* volatile CheckAddress)(uintptr_t addr, int pagesize) = CheckAddressFirstCall;
// And we choose between strategies by checking at runtime if
// single-syscall approach actually works and switch to a proper
// version.
bool CheckAddressFirstCall(uintptr_t addr, int pagesize) {
void* unreadable = mmap(0, pagesize, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
RAW_CHECK(unreadable != MAP_FAILED, "mmap of unreadable");
if (!CheckAccessSingleSyscall(reinterpret_cast<uintptr_t>(unreadable), pagesize)) {
CheckAddress = CheckAccessSingleSyscall;
} else {
CheckAddress = CheckAccessTwoSyscalls;
}
// Sanity check that our unreadable address is unreadable and that
// our readable address (our own fn pointer variable) is readable.
RAW_CHECK(CheckAddress(reinterpret_cast<uintptr_t>(CheckAddress),
pagesize),
"sanity check for readable addr");
RAW_CHECK(!CheckAddress(reinterpret_cast<uintptr_t>(unreadable),
pagesize),
"sanity check for unreadable addr");
(void)munmap(unreadable, pagesize);
return CheckAddress(addr, pagesize);
};
#else
#if HAVE_SYS_SYSCALL_H && !__APPLE__
static int raw_read(int fd, void* buf, size_t count) {
return syscall(SYS_read, fd, buf, count);
}
static int raw_write(int fd, void* buf, size_t count) {
return syscall(SYS_write, fd, buf, count);
}
#else
#define raw_read read
#define raw_write write
#endif
bool CheckAddress(uintptr_t addr, int pagesize) {
static tcmalloc::TrivialOnce once;
static int fds[2];
once.RunOnce([] () {
RAW_CHECK(pipe(fds) == 0, "pipe(fds)");
auto add_flag = [] (int fd, int get, int set, int the_flag) {
int flags = fcntl(fd, get, 0);
RAW_CHECK(flags >= 0, "fcntl get");
flags |= the_flag;
RAW_CHECK(fcntl(fd, set, flags) == 0, "fcntl set");
};
for (int i = 0; i < 2; i++) {
add_flag(fds[i], F_GETFD, F_SETFD, FD_CLOEXEC);
add_flag(fds[i], F_GETFL, F_SETFL, O_NONBLOCK);
}
});
do {
int rv = raw_write(fds[1], reinterpret_cast<void*>(addr), 1);
RAW_CHECK(rv != 0, "raw_write(...) == 0");
if (rv > 0) {
return true;
}
if (errno == EFAULT) {
return false;
}
RAW_CHECK(errno == EAGAIN, "write errno must be EAGAIN");
char drainbuf[256];
do {
rv = raw_read(fds[0], drainbuf, sizeof(drainbuf));
if (rv < 0 && errno != EINTR) {
RAW_CHECK(errno == EAGAIN, "read errno must be EAGAIN");
break;
}
// read succeeded or we got EINTR
} while (true);
} while (true);
return false;
}
#endif
} // namespace