// -*- 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 #include #include #include #if HAVE_SYS_SYSCALL_H && !__APPLE__ #include #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(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(CheckAddress), pagesize), "sanity check for readable addr"); RAW_CHECK(!CheckAddress(reinterpret_cast(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(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