[llvm] [llvm][Support][Memory] Add memfd based fallback for strict W^X Linux systems (PR #98538)

via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 31 13:52:19 PDT 2024


https://github.com/minipli-oss updated https://github.com/llvm/llvm-project/pull/98538

>From 3a22789bd1dc4cdffccfc008aea93fc8a56245d4 Mon Sep 17 00:00:00 2001
From: Mathias Krause <minipli at grsecurity.net>
Date: Wed, 3 Jul 2024 22:01:37 +0200
Subject: [PATCH 01/13] [Support][Memory] Add memfd based fallback for strict
 W^X Linux systems

PaX's MPROTECT feature as well as SELinux's deny_execmem policy prevent
a transition from a once writable memory mapping to an executable one.
This obviously breaks JIT code generation.

As both of these are Linux specific and the Linux kernel gained memfd
support almost 10 years ago in v3.17, making use of it to implement a
fallback seems like a viable option to get JIT code generation fixed
for such systems.

Implement a detour through a memfd for systems that are detected to deny
the W<->X transition of memory mappings. For PaX this can be easily
achieved by evaluating the per-process PaX flags, if access to
/proc/self/status is available. For others, and as a fallback for PaX, a
runtime test is done once.

This enables such systems to make use of JIT code generation without
completely abandoning their W^X policy.

Signed-off-by: Mathias Krause <minipli at grsecurity.net>
---
 llvm/lib/Support/Unix/Memory.inc      |  62 ++++++++++++++-
 llvm/lib/Support/Unix/MemoryLinux.h   | 105 ++++++++++++++++++++++++++
 llvm/unittests/Support/MemoryTest.cpp |   6 ++
 3 files changed, 170 insertions(+), 3 deletions(-)
 create mode 100644 llvm/lib/Support/Unix/MemoryLinux.h

diff --git a/llvm/lib/Support/Unix/Memory.inc b/llvm/lib/Support/Unix/Memory.inc
index bac208a7d543c..f894e24ea2919 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 <sys/mman.h>
 #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<char *>(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<void *>(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 0000000000000..846b67dae4527
--- /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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/mman.h>
+#include <sys/syscall.h>
+
+#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 9daa6d0ff9e4d..dbee1b62b25d1 100644
--- a/llvm/unittests/Support/MemoryTest.cpp
+++ b/llvm/unittests/Support/MemoryTest.cpp
@@ -11,6 +11,10 @@
 #include "gtest/gtest.h"
 #include <cstdlib>
 
+#if defined(__linux__)
+#include "../lib/Support/Unix/MemoryLinux.h"
+#endif
+
 #if defined(__NetBSD__)
 // clang-format off
 #include <sys/param.h>
@@ -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

>From b4fd3785bca2e48829c82125788b78cbc2927767 Mon Sep 17 00:00:00 2001
From: Mathias Krause <minipli at grsecurity.net>
Date: Wed, 24 Jul 2024 09:52:32 +0200
Subject: [PATCH 02/13] Minor fixes

- set errno if SYS_memfd_create is unknown so error handling actually
  works in this case
- try making use of MFD_EXEC as demanded by recent kernels
---
 llvm/lib/Support/Unix/Memory.inc    | 18 ++++++++++++++++--
 llvm/lib/Support/Unix/MemoryLinux.h |  6 +++++-
 2 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Support/Unix/Memory.inc b/llvm/lib/Support/Unix/Memory.inc
index f894e24ea2919..b3d8f78797541 100644
--- a/llvm/lib/Support/Unix/Memory.inc
+++ b/llvm/lib/Support/Unix/Memory.inc
@@ -191,14 +191,28 @@ std::error_code Memory::protectMappedMemory(const MemoryBlock &M,
   // To support such systems, we need to create a fresh mapping with the
   // target protection flags.
   if ((M.Flags ^ Flags) & MF_EXEC && execProtChangeNeedsNewMapping()) {
+    static unsigned int flags = MFD_CLOEXEC | MFD_EXEC;
     class FDWrapper {
     public:
       FDWrapper(int fd) : fd(fd) {}
-      ~FDWrapper() { ::close(fd); }
+      ~FDWrapper() { close(); }
+      FDWrapper& operator=(int nfd) { close(); fd = nfd; return *this; }
       operator int() const { return fd; }
     private:
+      void close() { if (fd >= 0) ::close(fd); fd = -1; }
       int fd;
-    } fd(memfd_create("llvm", MFD_CLOEXEC));
+    } fd(memfd_create("llvm", flags));
+
+    // The initial version of memfd_create() created executable fds by default.
+    // Since Linux v6.3 the default behaviour depends on a sysctl setting
+    // 'vm.memfd_noexec', requiring users to explicitly request the executable
+    // status by passing MFD_EXEC in the flags. This flag will be rejected by
+    // earlier kernels with EINVAL. Simply retry without the flag in this case,
+    // as mappings will be executable by default on these kernels.
+    if (fd < 0 && errno == EINVAL && (flags & MFD_EXEC)) {
+      flags &= ~MFD_EXEC;
+      fd = memfd_create("llvm", flags);
+    }
 
     if (fd < 0)
       return errnoAsErrorCode();
diff --git a/llvm/lib/Support/Unix/MemoryLinux.h b/llvm/lib/Support/Unix/MemoryLinux.h
index 846b67dae4527..9a60882f6c47a 100644
--- a/llvm/lib/Support/Unix/MemoryLinux.h
+++ b/llvm/lib/Support/Unix/MemoryLinux.h
@@ -28,7 +28,10 @@
 #include <sys/syscall.h>
 
 #ifndef MFD_CLOEXEC
-#define MFD_CLOEXEC 1U
+#define MFD_CLOEXEC 0x0001U
+#endif
+#ifndef MFD_EXEC
+#define MFD_EXEC 0x0010U
 #endif
 
 namespace llvm {
@@ -94,6 +97,7 @@ static inline int memfd_create(const char *name, int flags) {
 #ifdef SYS_memfd_create
   return syscall(SYS_memfd_create, name, flags);
 #else
+  errno = ENOSYS;
   return -1;
 #endif
 }

>From 8942359145b9afe011c2e63d9fa219e1b0eb4c56 Mon Sep 17 00:00:00 2001
From: Mathias Krause <minipli at grsecurity.net>
Date: Wed, 24 Jul 2024 11:37:34 +0200
Subject: [PATCH 03/13] Add explicit test for w->x transition

---
 llvm/unittests/Support/MemoryTest.cpp | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/llvm/unittests/Support/MemoryTest.cpp b/llvm/unittests/Support/MemoryTest.cpp
index dbee1b62b25d1..9bbdb8491a08d 100644
--- a/llvm/unittests/Support/MemoryTest.cpp
+++ b/llvm/unittests/Support/MemoryTest.cpp
@@ -306,6 +306,32 @@ TEST_P(MappedMemoryTest, EnabledWrite) {
   EXPECT_FALSE(Memory::releaseMappedMemory(M2));
 }
 
+TEST_P(MappedMemoryTest, MakeExec) {
+  // This test applies only to readable and writeable combinations
+  if (Flags && !((Flags & Memory::MF_READ) && (Flags & Memory::MF_WRITE)))
+    GTEST_SKIP();
+  CHECK_UNSUPPORTED();
+
+  std::error_code EC;
+  MemoryBlock M = Memory::allocateMappedMemory(sizeof(int), nullptr, Flags, EC);
+  EXPECT_EQ(std::error_code(), EC);
+
+  EXPECT_NE((void*)nullptr, M.base());
+  EXPECT_LE(sizeof(int), M.allocatedSize());
+
+  int *x = (int*)M.base();
+  *x = 0xcc;
+
+  EXPECT_EQ(0xcc, *x);
+
+  Flags ^= Memory::MF_WRITE;
+  Flags |= Memory::MF_EXEC;
+
+  EXPECT_EQ(std::error_code(), Memory::protectMappedMemory(M, Flags));
+  EXPECT_EQ(0xcc, *x);
+  EXPECT_FALSE(Memory::releaseMappedMemory(M));
+}
+
 TEST_P(MappedMemoryTest, SuccessiveNear) {
   CHECK_UNSUPPORTED();
   std::error_code EC;

>From 741da1971245e9f8ae289e670fef1eae5ec96627 Mon Sep 17 00:00:00 2001
From: Mathias Krause <minipli at grsecurity.net>
Date: Wed, 24 Jul 2024 13:37:24 +0200
Subject: [PATCH 04/13] clang-format fixes

---
 llvm/lib/Support/Unix/Memory.inc      | 13 +++++++++++--
 llvm/unittests/Support/MemoryTest.cpp |  4 ++--
 2 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Support/Unix/Memory.inc b/llvm/lib/Support/Unix/Memory.inc
index b3d8f78797541..2f622e27f6da0 100644
--- a/llvm/lib/Support/Unix/Memory.inc
+++ b/llvm/lib/Support/Unix/Memory.inc
@@ -196,10 +196,19 @@ std::error_code Memory::protectMappedMemory(const MemoryBlock &M,
     public:
       FDWrapper(int fd) : fd(fd) {}
       ~FDWrapper() { close(); }
-      FDWrapper& operator=(int nfd) { close(); fd = nfd; return *this; }
+      FDWrapper &operator=(int nfd) {
+        close();
+        fd = nfd;
+        return *this;
+      }
       operator int() const { return fd; }
+
     private:
-      void close() { if (fd >= 0) ::close(fd); fd = -1; }
+      void close() {
+        if (fd >= 0)
+          ::close(fd);
+        fd = -1;
+      }
       int fd;
     } fd(memfd_create("llvm", flags));
 
diff --git a/llvm/unittests/Support/MemoryTest.cpp b/llvm/unittests/Support/MemoryTest.cpp
index 9bbdb8491a08d..028a5fda97ef1 100644
--- a/llvm/unittests/Support/MemoryTest.cpp
+++ b/llvm/unittests/Support/MemoryTest.cpp
@@ -316,10 +316,10 @@ TEST_P(MappedMemoryTest, MakeExec) {
   MemoryBlock M = Memory::allocateMappedMemory(sizeof(int), nullptr, Flags, EC);
   EXPECT_EQ(std::error_code(), EC);
 
-  EXPECT_NE((void*)nullptr, M.base());
+  EXPECT_NE((void *)nullptr, M.base());
   EXPECT_LE(sizeof(int), M.allocatedSize());
 
-  int *x = (int*)M.base();
+  int *x = (int *)M.base();
   *x = 0xcc;
 
   EXPECT_EQ(0xcc, *x);

>From aa45cd3329cb7186eaf5adbfe7d99d71aa88f039 Mon Sep 17 00:00:00 2001
From: Mathias Krause <minipli at grsecurity.net>
Date: Thu, 25 Jul 2024 16:05:51 +0200
Subject: [PATCH 05/13] Use a MemoryBuffer and StringRef to parse
 /proc/self/status

---
 llvm/lib/Support/Unix/MemoryLinux.h | 31 ++++++-----------------------
 1 file changed, 6 insertions(+), 25 deletions(-)

diff --git a/llvm/lib/Support/Unix/MemoryLinux.h b/llvm/lib/Support/Unix/MemoryLinux.h
index 9a60882f6c47a..029529f39977a 100644
--- a/llvm/lib/Support/Unix/MemoryLinux.h
+++ b/llvm/lib/Support/Unix/MemoryLinux.h
@@ -17,13 +17,9 @@
 #error Linux only support header!
 #endif
 
+#include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Process.h"
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
 #include <sys/mman.h>
 #include <sys/syscall.h>
 
@@ -43,7 +39,6 @@ static inline bool isPermissionError(int err) {
   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;
 
@@ -51,25 +46,11 @@ static inline bool execProtChangeNeedsNewMapping() {
     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;
+  if (auto file = MemoryBuffer::getFileAsStream("/proc/self/status")) {
+    auto pax_flags = (*file)->getBuffer().rsplit("PaX:").second.trim();
+    if (!pax_flags.empty())
+      // 'M' indicates MPROTECT is enabled
+      return (status = pax_flags.find('M') != StringRef::npos);
   }
 
   // Create a temporary writable mapping and try to make it executable.  If

>From b6a67c22b85d7fb4e701923b59d99e02ca3110ff Mon Sep 17 00:00:00 2001
From: Mathias Krause <minipli at grsecurity.net>
Date: Thu, 25 Jul 2024 16:50:15 +0200
Subject: [PATCH 06/13] Drop llvm/lib/Support/Unix/MemoryLinux.h

Remove the "private" header file and make execProtectionChangeNeedsNewMapping()
a protected static member function of llvm::sys::Memory, allowing it to be
accessed by the unit tests as well.
---
 llvm/include/llvm/Support/Memory.h    | 17 +++++
 llvm/lib/Support/Unix/Memory.inc      | 73 ++++++++++++++++++++--
 llvm/lib/Support/Unix/MemoryLinux.h   | 90 ---------------------------
 llvm/lib/Support/Windows/Memory.inc   |  2 +
 llvm/unittests/Support/MemoryTest.cpp | 12 ++--
 5 files changed, 92 insertions(+), 102 deletions(-)
 delete mode 100644 llvm/lib/Support/Unix/MemoryLinux.h

diff --git a/llvm/include/llvm/Support/Memory.h b/llvm/include/llvm/Support/Memory.h
index d7d60371d315f..c101941733ee8 100644
--- a/llvm/include/llvm/Support/Memory.h
+++ b/llvm/include/llvm/Support/Memory.h
@@ -131,6 +131,23 @@ namespace sys {
     /// that has been emitted it must invalidate the instruction cache on some
     /// platforms.
     static void InvalidateInstructionCache(const void *Addr, size_t Len);
+
+  protected:
+    /// This method tells if a change of the executable status of a memory
+    /// mapping needs to re-create the mapping instead of only having its
+    /// internal protection flags modified.
+    ///
+    /// It's needed for systems enforcing a strict W^X policy like PaX's
+    /// MPROTECT or SELinux's deny_execmem.
+    ///
+    /// It's an internal helper for protectMappedMemory() and should have no
+    /// further use beside unit tests.
+    ///
+    /// \r true if a mapping needs to be recreated for a change to its
+    /// executable protection status
+    ///
+    /// Determine memory executable protection change procedure.
+    static bool execProtectionChangeNeedsNewMapping();
   };
 
   /// Owning version of MemoryBlock.
diff --git a/llvm/lib/Support/Unix/Memory.inc b/llvm/lib/Support/Unix/Memory.inc
index 2f622e27f6da0..bf52a6127c4ad 100644
--- a/llvm/lib/Support/Unix/Memory.inc
+++ b/llvm/lib/Support/Unix/Memory.inc
@@ -15,17 +15,18 @@
 #include "llvm/Support/Alignment.h"
 #include "llvm/Support/DataTypes.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Process.h"
 #include "llvm/Support/Valgrind.h"
 
-#ifdef __linux__
-#include "MemoryLinux.h"
-#endif
-
 #ifdef HAVE_SYS_MMAN_H
 #include <sys/mman.h>
 #endif
 
+#ifdef __linux__
+#include <sys/syscall.h>
+#endif
+
 #ifdef __APPLE__
 #include <mach/mach.h>
 #endif
@@ -71,6 +72,24 @@ static int getPosixProtectionFlags(unsigned Flags) {
   return PROT_NONE;
 }
 
+#if defined(__linux__)
+#ifndef MFD_CLOEXEC
+#define MFD_CLOEXEC 0x0001U
+#endif
+#ifndef MFD_EXEC
+#define MFD_EXEC 0x0010U
+#endif
+
+static inline int memfd_create(const char *name, int flags) {
+#ifdef SYS_memfd_create
+  return syscall(SYS_memfd_create, name, flags);
+#else
+  errno = ENOSYS;
+  return -1;
+#endif
+}
+#endif
+
 namespace llvm {
 namespace sys {
 
@@ -190,7 +209,7 @@ std::error_code Memory::protectMappedMemory(const MemoryBlock &M,
   //
   // To support such systems, we need to create a fresh mapping with the
   // target protection flags.
-  if ((M.Flags ^ Flags) & MF_EXEC && execProtChangeNeedsNewMapping()) {
+  if ((M.Flags ^ Flags) & MF_EXEC && execProtectionChangeNeedsNewMapping()) {
     static unsigned int flags = MFD_CLOEXEC | MFD_EXEC;
     class FDWrapper {
     public:
@@ -330,5 +349,49 @@ void Memory::InvalidateInstructionCache(const void *Addr, size_t Len) {
   ValgrindDiscardTranslations(Addr, Len);
 }
 
+static inline bool isPermissionError(int err) {
+  // PaX uses EPERM, SELinux uses EACCES
+  return err == EPERM || err == EACCES;
+}
+
+bool Memory::execProtectionChangeNeedsNewMapping() {
+#if defined(__linux__)
+  static int status = -1;
+
+  if (status != -1)
+    return status;
+
+  // Try to get the status from /proc/self/status, looking for PaX flags.
+  if (auto file = MemoryBuffer::getFileAsStream("/proc/self/status")) {
+    auto pax_flags = (*file)->getBuffer().rsplit("PaX:").second.trim();
+    if (!pax_flags.empty())
+      // 'M' indicates MPROTECT is enabled
+      return (status = pax_flags.find('M') != StringRef::npos);
+  }
+
+  // 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;
+#else
+  return false;
+#endif
+}
+
 } // namespace sys
 } // namespace llvm
diff --git a/llvm/lib/Support/Unix/MemoryLinux.h b/llvm/lib/Support/Unix/MemoryLinux.h
deleted file mode 100644
index 029529f39977a..0000000000000
--- a/llvm/lib/Support/Unix/MemoryLinux.h
+++ /dev/null
@@ -1,90 +0,0 @@
-//===- 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/MemoryBuffer.h"
-#include "llvm/Support/Process.h"
-
-#include <sys/mman.h>
-#include <sys/syscall.h>
-
-#ifndef MFD_CLOEXEC
-#define MFD_CLOEXEC 0x0001U
-#endif
-#ifndef MFD_EXEC
-#define MFD_EXEC 0x0010U
-#endif
-
-namespace llvm {
-namespace sys {
-namespace {
-
-static inline bool isPermissionError(int err) {
-  // PaX uses EPERM, SELinux uses EACCES
-  return err == EPERM || err == EACCES;
-}
-
-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.
-  if (auto file = MemoryBuffer::getFileAsStream("/proc/self/status")) {
-    auto pax_flags = (*file)->getBuffer().rsplit("PaX:").second.trim();
-    if (!pax_flags.empty())
-      // 'M' indicates MPROTECT is enabled
-      return (status = pax_flags.find('M') != StringRef::npos);
-  }
-
-  // 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
-  errno = ENOSYS;
-  return -1;
-#endif
-}
-
-} // anonymous namespace
-} // namespace sys
-} // namespace llvm
-
-#endif
diff --git a/llvm/lib/Support/Windows/Memory.inc b/llvm/lib/Support/Windows/Memory.inc
index 93a32111ee3f6..a05e20003ed5a 100644
--- a/llvm/lib/Support/Windows/Memory.inc
+++ b/llvm/lib/Support/Windows/Memory.inc
@@ -189,5 +189,7 @@ void Memory::InvalidateInstructionCache(const void *Addr, size_t Len) {
   FlushInstructionCache(GetCurrentProcess(), Addr, Len);
 }
 
+bool Memory::execProtectionChangeNeedsNewMapping() { return false; }
+
 } // namespace sys
 } // namespace llvm
diff --git a/llvm/unittests/Support/MemoryTest.cpp b/llvm/unittests/Support/MemoryTest.cpp
index 028a5fda97ef1..9dad3c48718e6 100644
--- a/llvm/unittests/Support/MemoryTest.cpp
+++ b/llvm/unittests/Support/MemoryTest.cpp
@@ -11,10 +11,6 @@
 #include "gtest/gtest.h"
 #include <cstdlib>
 
-#if defined(__linux__)
-#include "../lib/Support/Unix/MemoryLinux.h"
-#endif
-
 #if defined(__NetBSD__)
 // clang-format off
 #include <sys/param.h>
@@ -30,6 +26,10 @@ using namespace sys;
 
 namespace {
 
+struct checkMPROTECT : private llvm::sys::Memory {
+  static bool check() { return execProtectionChangeNeedsNewMapping(); }
+};
+
 bool IsMPROTECT() {
 #if defined(__NetBSD__)
   int mib[3];
@@ -44,12 +44,10 @@ 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
-  return false;
+  return checkMPROTECT::check();
 #endif
 }
 

>From 3c94191bbe2e227f6f7b87bca4e6b91a489fb1f4 Mon Sep 17 00:00:00 2001
From: Mathias Krause <minipli at grsecurity.net>
Date: Thu, 25 Jul 2024 16:57:52 +0200
Subject: [PATCH 07/13] Handle the (theoretical) case where "PaX:" isn't the
 last line in /proc/PID/status

---
 llvm/lib/Support/Unix/Memory.inc | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Support/Unix/Memory.inc b/llvm/lib/Support/Unix/Memory.inc
index bf52a6127c4ad..cd7788b13c819 100644
--- a/llvm/lib/Support/Unix/Memory.inc
+++ b/llvm/lib/Support/Unix/Memory.inc
@@ -363,7 +363,8 @@ bool Memory::execProtectionChangeNeedsNewMapping() {
 
   // Try to get the status from /proc/self/status, looking for PaX flags.
   if (auto file = MemoryBuffer::getFileAsStream("/proc/self/status")) {
-    auto pax_flags = (*file)->getBuffer().rsplit("PaX:").second.trim();
+    auto pax_flags =
+        (*file)->getBuffer().rsplit("PaX:").second.split('\n').first.trim();
     if (!pax_flags.empty())
       // 'M' indicates MPROTECT is enabled
       return (status = pax_flags.find('M') != StringRef::npos);

>From 005cd2b3bc7329ad8ebcb72f0695f1fe7f585724 Mon Sep 17 00:00:00 2001
From: Mathias Krause <minipli at grsecurity.net>
Date: Thu, 25 Jul 2024 17:45:02 +0200
Subject: [PATCH 08/13] Give test a more sensible name

---
 llvm/unittests/Support/MemoryTest.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/unittests/Support/MemoryTest.cpp b/llvm/unittests/Support/MemoryTest.cpp
index 9dad3c48718e6..59bb542b77c20 100644
--- a/llvm/unittests/Support/MemoryTest.cpp
+++ b/llvm/unittests/Support/MemoryTest.cpp
@@ -304,7 +304,7 @@ TEST_P(MappedMemoryTest, EnabledWrite) {
   EXPECT_FALSE(Memory::releaseMappedMemory(M2));
 }
 
-TEST_P(MappedMemoryTest, MakeExec) {
+TEST_P(MappedMemoryTest, MakeExecutable) {
   // This test applies only to readable and writeable combinations
   if (Flags && !((Flags & Memory::MF_READ) && (Flags & Memory::MF_WRITE)))
     GTEST_SKIP();

>From fc3e57711e9dd79d36e6827aac2231c3dc922213 Mon Sep 17 00:00:00 2001
From: Mathias Krause <minipli at grsecurity.net>
Date: Thu, 25 Jul 2024 22:01:32 +0200
Subject: [PATCH 09/13] [llvm][Support] Support lightweight raw_fd_ostreams

For very simple fd streams we don't need to know the current position
nor the file's type. Support skipping these syscalls.

Signed-off-by: Mathias Krause <minipli at grsecurity.net>
---
 llvm/include/llvm/Support/raw_ostream.h |  1 +
 llvm/lib/Support/raw_ostream.cpp        | 10 +++++++---
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/llvm/include/llvm/Support/raw_ostream.h b/llvm/include/llvm/Support/raw_ostream.h
index df9ee2e5a7858..5a7305fbc7a23 100644
--- a/llvm/include/llvm/Support/raw_ostream.h
+++ b/llvm/include/llvm/Support/raw_ostream.h
@@ -526,6 +526,7 @@ class raw_fd_ostream : public raw_pwrite_stream {
   /// this closes the file when the stream is destroyed. If FD is for stdout or
   /// stderr, it will not be closed.
   raw_fd_ostream(int fd, bool shouldClose, bool unbuffered = false,
+                 bool simpleStream = false,
                  OStreamKind K = OStreamKind::OK_OStream);
 
   ~raw_fd_ostream() override;
diff --git a/llvm/lib/Support/raw_ostream.cpp b/llvm/lib/Support/raw_ostream.cpp
index 2ce54faa9857e..bcb2700554d3e 100644
--- a/llvm/lib/Support/raw_ostream.cpp
+++ b/llvm/lib/Support/raw_ostream.cpp
@@ -617,7 +617,7 @@ raw_fd_ostream::raw_fd_ostream(StringRef Filename, std::error_code &EC,
 /// FD is the file descriptor that this writes to.  If ShouldClose is true, this
 /// closes the file when the stream is destroyed.
 raw_fd_ostream::raw_fd_ostream(int fd, bool shouldClose, bool unbuffered,
-                               OStreamKind K)
+                               bool simpleStream, OStreamKind K)
     : raw_pwrite_stream(unbuffered, K), FD(fd), ShouldClose(shouldClose) {
   if (FD < 0 ) {
     ShouldClose = false;
@@ -635,6 +635,10 @@ raw_fd_ostream::raw_fd_ostream(int fd, bool shouldClose, bool unbuffered,
   if (FD <= STDERR_FILENO)
     ShouldClose = false;
 
+  // Don't do unnecessary work for simple streams, like memfds
+  if (simpleStream)
+    return;
+
 #ifdef _WIN32
   // Check if this is a console device. This is not equivalent to isatty.
   IsWindowsConsole =
@@ -926,7 +930,7 @@ raw_fd_stream::raw_fd_stream(StringRef Filename, std::error_code &EC)
     : raw_fd_ostream(getFD(Filename, EC, sys::fs::CD_CreateAlways,
                            sys::fs::FA_Write | sys::fs::FA_Read,
                            sys::fs::OF_None),
-                     true, false, OStreamKind::OK_FDStream) {
+                     true, false, false, OStreamKind::OK_FDStream) {
   if (EC)
     return;
 
@@ -935,7 +939,7 @@ raw_fd_stream::raw_fd_stream(StringRef Filename, std::error_code &EC)
 }
 
 raw_fd_stream::raw_fd_stream(int fd, bool shouldClose)
-    : raw_fd_ostream(fd, shouldClose, false, OStreamKind::OK_FDStream) {}
+    : raw_fd_ostream(fd, shouldClose, false, false, OStreamKind::OK_FDStream) {}
 
 ssize_t raw_fd_stream::read(char *Ptr, size_t Size) {
   assert(get_fd() >= 0 && "File already closed.");

>From c9937f9f29de8b04ad77a560b4c2a52bbdec5273 Mon Sep 17 00:00:00 2001
From: Mathias Krause <minipli at grsecurity.net>
Date: Thu, 25 Jul 2024 22:06:22 +0200
Subject: [PATCH 10/13] Drop one-off FDWrapper class and make use of
 raw_fd_ostream, now that it supports a lightweight mode

Signed-off-by: Mathias Krause <minipli at grsecurity.net>
---
 llvm/lib/Support/Unix/Memory.inc | 45 ++++++--------------------------
 1 file changed, 8 insertions(+), 37 deletions(-)

diff --git a/llvm/lib/Support/Unix/Memory.inc b/llvm/lib/Support/Unix/Memory.inc
index cd7788b13c819..f51b30a36df72 100644
--- a/llvm/lib/Support/Unix/Memory.inc
+++ b/llvm/lib/Support/Unix/Memory.inc
@@ -210,58 +210,29 @@ std::error_code Memory::protectMappedMemory(const MemoryBlock &M,
   // To support such systems, we need to create a fresh mapping with the
   // target protection flags.
   if ((M.Flags ^ Flags) & MF_EXEC && execProtectionChangeNeedsNewMapping()) {
-    static unsigned int flags = MFD_CLOEXEC | MFD_EXEC;
-    class FDWrapper {
-    public:
-      FDWrapper(int fd) : fd(fd) {}
-      ~FDWrapper() { close(); }
-      FDWrapper &operator=(int nfd) {
-        close();
-        fd = nfd;
-        return *this;
-      }
-      operator int() const { return fd; }
-
-    private:
-      void close() {
-        if (fd >= 0)
-          ::close(fd);
-        fd = -1;
-      }
-      int fd;
-    } fd(memfd_create("llvm", flags));
-
     // The initial version of memfd_create() created executable fds by default.
     // Since Linux v6.3 the default behaviour depends on a sysctl setting
     // 'vm.memfd_noexec', requiring users to explicitly request the executable
     // status by passing MFD_EXEC in the flags. This flag will be rejected by
     // earlier kernels with EINVAL. Simply retry without the flag in this case,
     // as mappings will be executable by default on these kernels.
+    static unsigned int flags = MFD_CLOEXEC | MFD_EXEC;
+    int fd = memfd_create("llvm", flags);
     if (fd < 0 && errno == EINVAL && (flags & MFD_EXEC)) {
       flags &= ~MFD_EXEC;
       fd = memfd_create("llvm", flags);
     }
-
     if (fd < 0)
       return errnoAsErrorCode();
 
-    const char *data = reinterpret_cast<char *>(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;
+    raw_fd_ostream fdos(fd, /*shouldClose=*/true, /*unbuffered=*/true,
+                        /*simpleStream=*/true);
 
-        return errnoAsErrorCode();
-      }
-      left -= cnt;
-      data += cnt;
-    }
+    fdos.write(reinterpret_cast<char *>(Start), End - Start);
+    if (auto ec = fdos.error())
+      return ec;
 
-    void *addr = ::mmap(reinterpret_cast<void *>(Start), len, Protect,
+    void *addr = ::mmap(reinterpret_cast<void *>(Start), End - Start, Protect,
                         MAP_PRIVATE | MAP_FIXED, fd, 0);
     if (addr == MAP_FAILED)
       return errnoAsErrorCode();

>From de9ac6d9c933515cc0eea79cca3c0e3c5e76017c Mon Sep 17 00:00:00 2001
From: Mathias Krause <minipli at grsecurity.net>
Date: Wed, 31 Jul 2024 22:38:50 +0200
Subject: [PATCH 11/13] Change memfd_create()'s 'flags' argument type to
 'unsigned int'

Signed-off-by: Mathias Krause <minipli at grsecurity.net>
---
 llvm/lib/Support/Unix/Memory.inc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Support/Unix/Memory.inc b/llvm/lib/Support/Unix/Memory.inc
index f51b30a36df72..09c3636fd8cf5 100644
--- a/llvm/lib/Support/Unix/Memory.inc
+++ b/llvm/lib/Support/Unix/Memory.inc
@@ -80,7 +80,7 @@ static int getPosixProtectionFlags(unsigned Flags) {
 #define MFD_EXEC 0x0010U
 #endif
 
-static inline int memfd_create(const char *name, int flags) {
+static inline int memfd_create(const char *name, unsigned int flags) {
 #ifdef SYS_memfd_create
   return syscall(SYS_memfd_create, name, flags);
 #else

>From aeb2d4a94b23a621d5c695a41f8113ddeb43cac0 Mon Sep 17 00:00:00 2001
From: Mathias Krause <minipli at grsecurity.net>
Date: Wed, 31 Jul 2024 22:39:54 +0200
Subject: [PATCH 12/13] Provide documentation for fd-based raw_fd_ostream()
 contructor

Signed-off-by: Mathias Krause <minipli at grsecurity.net>
---
 llvm/include/llvm/Support/raw_ostream.h | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/llvm/include/llvm/Support/raw_ostream.h b/llvm/include/llvm/Support/raw_ostream.h
index 5a7305fbc7a23..2d89b17721d63 100644
--- a/llvm/include/llvm/Support/raw_ostream.h
+++ b/llvm/include/llvm/Support/raw_ostream.h
@@ -522,9 +522,18 @@ class raw_fd_ostream : public raw_pwrite_stream {
                  sys::fs::CreationDisposition Disp, sys::fs::FileAccess Access,
                  sys::fs::OpenFlags Flags);
 
-  /// FD is the file descriptor that this writes to.  If ShouldClose is true,
-  /// this closes the file when the stream is destroyed. If FD is for stdout or
-  /// stderr, it will not be closed.
+  /// Create a stream based of a given file descriptor.
+  ///
+  /// \param fd is the file descriptor that this writes to.
+  /// \param shouldClose If true, this closes the file when the stream is
+  ///                    destroyed. If \p fd is for stdout or stderr, it will
+  ///                    not be closed.
+  /// \param unbuffered If true, writes will be passed on as-is to write_impl(),
+  ///                   bypassing any buffering.
+  /// \param simpleStream If true, the passed file descriptor isn't queried for
+  ///                     its current location nor its file type, disabling
+  ///                     seek support.  It's intended for very basic file
+  ///                     descriptor streams not needing such functionality.
   raw_fd_ostream(int fd, bool shouldClose, bool unbuffered = false,
                  bool simpleStream = false,
                  OStreamKind K = OStreamKind::OK_OStream);

>From 5101d863b39f50490ebafd8b717a89ede90ce0b8 Mon Sep 17 00:00:00 2001
From: Mathias Krause <minipli at grsecurity.net>
Date: Wed, 31 Jul 2024 22:47:01 +0200
Subject: [PATCH 13/13] Only define memfd_create() stub if needed

If MFD_CLOEXEC is defined, assume the libc function is so as well.

Signed-off-by: Mathias Krause <minipli at grsecurity.net>
---
 llvm/lib/Support/Unix/Memory.inc | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Support/Unix/Memory.inc b/llvm/lib/Support/Unix/Memory.inc
index 09c3636fd8cf5..0d05454134877 100644
--- a/llvm/lib/Support/Unix/Memory.inc
+++ b/llvm/lib/Support/Unix/Memory.inc
@@ -75,11 +75,8 @@ static int getPosixProtectionFlags(unsigned Flags) {
 #if defined(__linux__)
 #ifndef MFD_CLOEXEC
 #define MFD_CLOEXEC 0x0001U
-#endif
-#ifndef MFD_EXEC
-#define MFD_EXEC 0x0010U
-#endif
 
+// Assume if MFD_CLOEXEC is defined, so is the libc function
 static inline int memfd_create(const char *name, unsigned int flags) {
 #ifdef SYS_memfd_create
   return syscall(SYS_memfd_create, name, flags);
@@ -88,6 +85,10 @@ static inline int memfd_create(const char *name, unsigned int flags) {
   return -1;
 #endif
 }
+#endif // MFD_CLOEXEC
+#ifndef MFD_EXEC
+#define MFD_EXEC 0x0010U
+#endif
 #endif
 
 namespace llvm {



More information about the llvm-commits mailing list