mimmutable() for OpenBSD
OpenBSD founder Theo de Raadt first proposed a new system call, called mimmutable(), at the beginning of September. After numerous revisions, the system call looks to be merged as:
int mimmutable(void *addr, size_t len);
A call to mimmutable() will render the mapping of the len bytes of memory starting at addr immutable, meaning that the kernel will not allow any changes to either the memory protections or the mapping in that range. As a result, system calls like mmap() or mprotect() that would affect that range will, instead, fail.
At first glance, mimmutable() looks similar in spirit to OpenBSD's pledge(), which restricts the system calls that the calling process may use. But, while pledge() calls appear in numerous programs in the OpenBSD repository, mimmutable() calls will be rare indeed. Most developers lack a detailed understanding of the memory layout of their programs and are not well placed to render portions of their address space immutable, but the kernel and the linker are a different story.
The details of how mimmutable() will be used are described in detail in this email from De Raadt. In simplified form, it starts when the kernel loads a new executable image; once the text, stack, and data areas have been mapped, they will be made immutable before the program even starts running. For static binaries, the C runtime will do a bit of fixup and then use mimmutable() to make most of the rest of the mapped address space immutable as well. For dynamically linked binaries, the shared-library linker (ld.so) performs a similar set of tasks, mapping each library into the address space, then making most of those mappings immutable.
All of this will happen automatically, without any awareness on the part of the program being loaded. The end result will be a process that cannot make changes to almost all of its mapped address space (though it can always create new mappings in parts of the address space that have not yet been mapped). There is one little exception:
So this static executable is completely immutable, except for the OPENBSD_MUTABLE region. This annotation is used in one place now, deep inside libc's malloc(3) code, where a piece of code flips a data structure between readonly and read-write as a security measure. That does not become immutable.
Making this whole scheme work requires changes beyond just the OpenBSD kernel; the compiler toolchain, in particular, needed enhancements to mark the sections that must remain mutable when the program is loaded. There were evidently some programs that needed tweaks to work properly in this environment; since OpenBSD manages the kernel and user space together, it is able to make the sort of changes that Linux, out of fear of causing user-space regressions, normally cannot.
Even so, implementing mimmutable() involves a fair amount of fiddly work; one would assume that the OpenBSD developers expect to see a corresponding benefit. One obvious place is with executable memory. OpenBSD has long gone out of its way to prevent memory from being simultaneously writable and executable, but the protection that comes from this restriction goes away if an attacker is somehow able to load malicious code into a writable region, then change the permissions afterward. Nailing down the protections for a process's data areas will make that kind of attack impossible.
Beyond that, though, OpenBSD uses a couple of memory protections that are not present in Linux. One of those marks executable memory that is empowered to call into the kernel; on OpenBSD systems, only the C library is given that capability. That will prevent hostile code loaded elsewhere from making direct system calls; protecting the rest of a process with mimmutable() will prevent the changing of protections to allow system calls from elsewhere (such changes would be done with msyscall() on OpenBSD).
OpenBSD also has a special marker for memory regions that are intended to hold stacks. Whenever a process enters the kernel, its stack pointer is checked to see whether it is, indeed, pointing into a stack region; if not, the process is killed. This check thwarts "stack pivot" attacks, where an attacker redirects the stack pointer into a region of memory that is more conducive to the attack being performed. Once again, mimmutable() will prevent an attacker from turning ordinary data regions into stack-capable regions.
It is possible that a system call like mimmutable() could be used to improve security on Linux systems, but it would be a harder project. Linux kernel developers lack the ability to modify user-space programs in lockstep, so it is harder to make this kind of change without breaking somebody's code somewhere. For example, adding a "direct system calls allowed" protection bit could easily break a lot of programs under Linux that, for whatever reason, are not using the C-library wrappers and are calling directly into the kernel.
Similar roadblocks apply for restrictions on stack pointers. The kernel does have a "grows down" bit that identifies stack regions — but only those that can grow. Multithreaded programs often create threads with fixed-length stacks that will lack this bit. As a result, any user-space program that creates stacks for threads would need modification to set such a bit explicitly, and kernel developers cannot make such modifications happen. So stack-pointer checks are not likely to come to Linux anytime soon.
Still, there may be value in a system call that makes memory mappings
immutable. Getting such a thing into Linux would require a developer
interested in implementing it, a demonstration that user-space code would
make use of it, and some sort of convincing story describing attacks that
would be thwarted by it. There would probably also be a need to get
changes into the toolchains to support this feature.
It's a high bar, as is normally the case for new
system calls, but perhaps somebody might eventually be inspired to try to
get a patch over it.
| Index entries for this article | |
|---|---|
| Security | OpenBSD |
