[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