[llvm] [llvm][Support][Memory] Add memfd based fallback for strict W^X Linux systems (PR #98538)
Jannik Glückert via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 31 10:40:53 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);
----------------
Jannik2099 wrote:
This will trigger an AVC denial, which might kill the process depending on host configuration, but at the very least will cause log spam and confusion (does this executable require WX because of the jit, or because it was compromised?).
Ideally you would check for the selinux `execmem` permission beforehand, here's an example:
```cpp
#include <functional>
#include <iostream>
#include <selinux/selinux.h>
namespace {
int noop_log(int, const char *, ...) { return 0; };
bool selinux_can_execmem() {
if (is_selinux_enabled() != 1) {
return true;
}
// this is required as libselinux installs printf() as a default log callback, or because consumers may
// install their own
const auto previous_callback = selinux_get_callback(SELINUX_CB_LOG);
selinux_set_callback(SELINUX_CB_LOG, {.func_log = &noop_log});
struct ScopeGuard {
std::function<void()> func;
~ScopeGuard() { func(); }
};
const ScopeGuard scope_guard{
[previous_callback]() { selinux_set_callback(SELINUX_CB_LOG, previous_callback); }};
char *user_context_raw{};
if (getcon_raw(&user_context_raw) < 0) {
std::cerr << "error in getting my selinux context\n";
return true;
}
if (selinux_check_access(user_context_raw, user_context_raw, "process", "execmem", nullptr) == 0) {
return false;
}
return true;
}
} // namespace
int main() {
if (selinux_can_execmem()) {
std::cout << "I am allowed to execmem\n";
} else {
std::cout << "I am not allowed to execmem\n";
}
}
```
Note that this will require linking with `-lselinux`. There is no freestanding way to check for selinux perms afaik.
https://github.com/llvm/llvm-project/pull/98538
More information about the llvm-commits
mailing list