MAP_STACK and mimmutable
For a memory region to be used as a stack, the
MAP_STACK
flag has to be passed to mmap
; otherwise, upon syscalls and pagefaults, if
the stack-pointer register doesn’t point to such a page, the program gets
killed.
Although this feature has been introduced in 2018 in OpenBSD by Theo de Raadt,
based on discussions with Ted Unangst and work done by Stefan Sperling, it was
already partially
present in Windows as a function named nt!PsValidateUserStack
,
documented in Ivan Fratic’s ropguard
project, and removed in
2012
likely because some of the
bypasses
were both obvious and generic:
- Write a stub that calls
mmap
with theMAP_STACK
flag, write your payload there, and jump on it, like done in 2023 by Thalium to pwn a TrustZone applet. - Write a stub to read the rest of your rop-chain onto the stack and pivot to it.
- Before each syscall, make point
esp
somewhere to the stack, and restore its value afterwards, as done in by @qwertyoruiop in yalu102 in 2017.
But checking the stack pointer on every pagefault is a bit more interesting,
unfortunately, someone told me about an OpenBSD-specific bypass, left as an
exercise to the reader: 647335652c535610ff7496e2b6624440
.
The commit implementing this mitigation, by Theo de Raadt mentions the following:
Observe that MAP_STACK can only be set/cleared by mmap(), which zeroes the contents of the region – there is no mprotect() equivalent operation, so there is no MAP_STACK-adding gadget.
I guess this means to say that an attacker has no way to directly map a
ROP-chain as MAP_STACK
. Except that this can be done by calling mmap
directly, and copying the ropchain there before jumping on it, which fits in a
couple of instructions.
The MAP_STACK
marking is also present in Linux
since the 13 August 2008,
but was added for different
reasons.
It’s not really used for anything super-interesting, beside pretty-printing via /proc
.
This mitigation adds a bit of complexity, but no overhead nor hinders debuggability, and the check-stack-pointer-on-pagefault trick might annoy an attacker for a couple of hours. It’s definitely not worth breaking the ABI for this.
In September 2022, De Raadt added
a way to lock memory mappings via the new mimmutable
syscall:
This identifies all current mapped memory in a region, and tags the mappings. Such mappings can never be unmapped. No new mmap can be done on top of the mappings. And the permissions cannot be changed. Other than that, the underlying storage memory works fine, it is just the mapping that is locked.
Apparently, De Raadt is “aware of an method used at least once (not on OpenBSD)
which managed to mprotect a region of libc, and then place things there, for
later execution.”, which not only sounds dumb (why would anyone mprotect
the
libc mapping‽), but jumping to mmap
isn’t exactly a new technique. Moreover,
nobody, in any real-life exploit, has ever relied on changing some library mappings to do
anything. The only sighting of this technique was in a zer0pts CTF 2021
challenge writeup,
but it could trivially have used another technique instead.
In 2023, in his CanSecWest talk, he elaborated on this, saying that it was likely an iPhone exploit, likely the talk from Charlie Miller and Vincenzo Iozzo, at BlackHat 2009, about bypassing code signature,
Interestingly, he also mentioned there that mimmutable
will be used in the
future, for security-related features, building on top of the invariants it
provides.
This is at best a partial copy of
PAX_MPROTECT
, implemented
21 years after its publication, without giving credit,
and after having argued in April
2003 that it was
violating “POSIX by breaking mprotect”.
A documented bypass
is to simply call dlopen
, since it’ll by default mapped as mutable, because
they can be dlclose
’d and unmapped.
But in all fairness, he never called mimmutable
a security improvement.