diff --git a/llvm/lib/Support/Unix/Memory.inc b/llvm/lib/Support/Unix/Memory.inc index bac208a7d543ca..f894e24ea2919c 100644 --- a/llvm/lib/Support/Unix/Memory.inc +++ b/llvm/lib/Support/Unix/Memory.inc @@ -18,6 +18,10 @@ #include "llvm/Support/Process.h" #include "llvm/Support/Valgrind.h" +#ifdef __linux__ +#include "MemoryLinux.h" +#endif + #ifdef HAVE_SYS_MMAN_H #include #endif @@ -177,6 +181,55 @@ std::error_code Memory::protectMappedMemory(const MemoryBlock &M, alignAddr((const uint8_t *)M.Address + M.AllocatedSize, PageSize); bool InvalidateCache = (Flags & MF_EXEC); + bool SkipMprotect = false; + +#if defined(__linux__) + // Check for cases where the EXEC protection flag changes and a possible + // strict W^X policy cannot be bypassed via mprotect() alone, e.g. under + // PaX's MPROTECT or SELinux's deny_execmem. + // + // To support such systems, we need to create a fresh mapping with the + // target protection flags. + if ((M.Flags ^ Flags) & MF_EXEC && execProtChangeNeedsNewMapping()) { + class FDWrapper { + public: + FDWrapper(int fd) : fd(fd) {} + ~FDWrapper() { ::close(fd); } + operator int() const { return fd; } + private: + int fd; + } fd(memfd_create("llvm", MFD_CLOEXEC)); + + if (fd < 0) + return errnoAsErrorCode(); + + const char *data = reinterpret_cast(Start); + uintptr_t len = End - Start; + uintptr_t left = len; + + while (left) { + ssize_t cnt = ::write(fd, data, left); + if (cnt < 0) { + if (errno == EINTR) + continue; + + return errnoAsErrorCode(); + } + left -= cnt; + data += cnt; + } + + void *addr = ::mmap(reinterpret_cast(Start), len, Protect, + MAP_PRIVATE | MAP_FIXED, fd, 0); + if (addr == MAP_FAILED) + return errnoAsErrorCode(); + + // We created a new mapping with the final protection bits, therefore + // don't need to call mprotect() with the very same flags again -- unless + // we have to toggle PROT_READ for ARM. + SkipMprotect = true; + } +#endif #if defined(__arm__) || defined(__aarch64__) // Certain ARM implementations treat icache clear instruction as a memory @@ -190,13 +243,16 @@ std::error_code Memory::protectMappedMemory(const MemoryBlock &M, Memory::InvalidateInstructionCache(M.Address, M.AllocatedSize); InvalidateCache = false; + SkipMprotect = false; } #endif - int Result = ::mprotect((void *)Start, End - Start, Protect); + if (!SkipMprotect) { + int Result = ::mprotect((void *)Start, End - Start, Protect); - if (Result != 0) - return errnoAsErrorCode(); + if (Result != 0) + return errnoAsErrorCode(); + } if (InvalidateCache) Memory::InvalidateInstructionCache(M.Address, M.AllocatedSize); diff --git a/llvm/lib/Support/Unix/MemoryLinux.h b/llvm/lib/Support/Unix/MemoryLinux.h new file mode 100644 index 00000000000000..846b67dae45273 --- /dev/null +++ b/llvm/lib/Support/Unix/MemoryLinux.h @@ -0,0 +1,105 @@ +//===- Unix/MemoryLinux.h - Linux specific Helper Fuctions ------*- 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 +// +//===----------------------------------------------------------------------===// +// +// This file defines Linux specific helper functions for memory management. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_SUPPORT_UNIX_MEMORYLINUX_H +#define LLVM_LIB_SUPPORT_UNIX_MEMORYLINUX_H + +#ifndef __linux__ +#error Linux only support header! +#endif + +#include "llvm/Support/Process.h" + +#include +#include +#include +#include + +#include +#include + +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 1U +#endif + +namespace llvm { +namespace sys { +namespace { + +static inline bool isPermissionError(int err) { + // PaX uses EPERM, SELinux uses EACCES + return err == EPERM || err == EACCES; +} + +// FIXME: Make this either more low-level C'ish or C++'ish +static inline bool execProtChangeNeedsNewMapping() { + static int status = -1; + + if (status != -1) + return status; + + // Try to get the status from /proc/self/status, looking for PaX flags. + FILE *f = fopen("/proc/self/status", "re"); + if (f) { + char *buf = NULL; + size_t len; + + while (getline(&buf, &len, f) != -1) { + if (strncmp(buf, "PaX:", 4)) + continue; + + // Look for 'm', indicating PaX MPROTECT is disabled. + status = !strchr(buf + 4, 'm'); + break; + } + + fclose(f); + free(buf); + + if (status != -1) + return status; + } + + // Create a temporary writable mapping and try to make it executable. If + // this fails, test 'errno' to ensure it failed because we were not allowed + // to create such a mapping and not because of some transient error. + size_t size = Process::getPageSizeEstimate(); + void *addr = ::mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (addr == MAP_FAILED) { + // Must be low on memory or have too many mappings already, not much we can + // do here. + status = 0; + } else { + if (::mprotect(addr, size, PROT_READ | PROT_EXEC) < 0) + status = isPermissionError(errno); + else + status = 0; + ::munmap(addr, size); + } + + return status; +} + +static inline int memfd_create(const char *name, int flags) { +#ifdef SYS_memfd_create + return syscall(SYS_memfd_create, name, flags); +#else + return -1; +#endif +} + +} // anonymous namespace +} // namespace sys +} // namespace llvm + +#endif diff --git a/llvm/unittests/Support/MemoryTest.cpp b/llvm/unittests/Support/MemoryTest.cpp index 9daa6d0ff9e4dd..dbee1b62b25d12 100644 --- a/llvm/unittests/Support/MemoryTest.cpp +++ b/llvm/unittests/Support/MemoryTest.cpp @@ -11,6 +11,10 @@ #include "gtest/gtest.h" #include +#if defined(__linux__) +#include "../lib/Support/Unix/MemoryLinux.h" +#endif + #if defined(__NetBSD__) // clang-format off #include @@ -40,6 +44,8 @@ bool IsMPROTECT() { err(EXIT_FAILURE, "sysctl"); return !!(paxflags & CTL_PROC_PAXFLAGS_MPROTECT); +#elif defined(__linux__) + return execProtChangeNeedsNewMapping(); #elif (defined(__APPLE__) && defined(__aarch64__)) || defined(__OpenBSD__) return true; #else