Mandatory W^X in userland
In November 2000, one month after its first release, PaX introduced PAX_MPROTECT, forbidding the following operations:
- changing the executable status of memory pages that were not originally created as executable,
- making read-only executable pages writable again,
- creating executable pages from anonymous memory,
- making read-only-after-relocations (RELRO) data pages writable again.
Amusingly, in April 2003, Theo de Raadt argued that this was breaking POSIX.
In May 2016, Theo de Raadt disallowed W^X violations by default in userland.
In February 2017, Microsoft added Arbitrary Code Guard
(ACG)
to Windows, preventing an unsigned writeable page from ever becoming
executable, and the other way around, a bit à la PAX_MPROTECT
.
On OpenBSD, contrary to PAX_MPROTECT
, it’s possible to do things like this:
#include <sys/mman.h>
int main() {
void (*fp) (void);
char* a = malloc(128);
mprotect(a, 128, PROT_WRITE)
a[0] = '\xcc';
mprotect(a, 128, PROT_READ | PROT_EXEC)
fp = (void*)a;
fp();
}
resulting in a SIGTRAP
, proving that an attacker just has to write a small
ROP chain calling mprotect
and jumping to their shellcode: W^X
isn’t
enought to prevent the introduction of new arbitrary code by an attacker.
It’s interesting to note that NetBSD, from which OpenBSD was forked, has a working
implementation of PAX_MPROTECT
since
2006.