[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
Fri Aug 9 01:40:21 PDT 2024


================
@@ -251,5 +320,50 @@ 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.split('\n').first.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);
----------------
minipli-oss wrote:

Implementing a _freestanding_ check was actually relatively easy. ...well, kind of. I was able to implement a C version in basically no time but I struggled to make that comply with LLVM's set of C++ helpers around file i/o. In fact, I failed to find something that would suit my needs for opening a file as a stream for reading and writing. That's why I went for `std::fstream` instead. If that's a no-go, please, PLEASE, point me to some abstraction that's better suited. (Honestly, I even find the hops one has to go through just to make use of `MemoryBuffer::getFileAsStream()` ugly but beauty lies in the eye of the beholder, they say....). Just for the record, that would be the C pendant:

```C
#include <sys/stat.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

int is_selinux_enabled(void) __attribute__((weak));

#ifndef SEL_INO_MASK
#define SEL_INO_MASK 0x00ffffff
#endif
#ifndef SEL_CLASS_INO_OFFSET
#define SEL_CLASS_INO_OFFSET 0x04000000
#endif
#ifndef SEL_VEC_MAX
#define SEL_VEC_MAX 32
#endif
#ifndef SELINUX_MAGIC
#define SELINUX_MAGIC 0xf97cff8c
#endif

#define SEL_MNT "/sys/fs/selinux"

int selinux_can_execmem(void) {
    if (is_selinux_enabled && !is_selinux_enabled())
        return 1;

    // get current context
    char ctx[1024] = {};
    FILE *f = fopen("/proc/self/attr/current", "r");
    if (!f)
        return -1;
    if (!fgets(ctx, sizeof(ctx), f)) {
        fclose(f);
        return -1;
    }
    fclose(f);

    // The inode encodes class and permission bits
    struct stat sbuf;
    if (stat(SEL_MNT "/class/process/perms/execmem", &sbuf))
        return -1;

    if (!(sbuf.st_ino & SEL_CLASS_INO_OFFSET))
        return -1;

    int clazz = (sbuf.st_ino & SEL_INO_MASK) / (SEL_VEC_MAX + 1);
    int xperm = (sbuf.st_ino & SEL_INO_MASK) % (SEL_VEC_MAX + 1) - 1;
    if (xperm < 0 || xperm > 31)
        return -1;

    unsigned int execmem = 1U << xperm;

    f = fopen(SEL_MNT "/access", "r+");
    if (!f)
        return -1;

    if (fprintf(f, "%s %s %d %x", ctx, ctx, clazz, execmem) < 0) {
        fclose(f);
        return -1;
    }

    unsigned int lo, hi;
    if (fscanf(f, "%x %x", &lo, &hi) != 2) {
        fclose(f);
        return -1;
    }
    fclose(f);

    uint64_t allowed = (uint64_t)hi << 32 | lo;
    return !!(allowed & execmem);

}
int main(void) {
    int status = selinux_can_execmem();
    printf("I am %sallowed to execmem\n",
           status == 0 ? "not " :
           status == 1 ? "" :
           "maybe ");
    return 0;
}
```

Either way, the current version avoids the AVC denial by implementing a specific test which should address your concerns.

https://github.com/llvm/llvm-project/pull/98538


More information about the llvm-commits mailing list