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 |
Posted Dec 9, 2022 16:43 UTC (Fri)
by abatters (✭ supporter ✭, #6932)
[Link] (2 responses)
Posted Dec 9, 2022 16:51 UTC (Fri)
by kilobyte (subscriber, #108024)
[Link]
Posted Dec 11, 2022 20:38 UTC (Sun)
by mirabilos (subscriber, #84359)
[Link]
The better way is to have programs explicitly declare the need for executable stack and default to r/w-only. But, again, this is something OpenBSD can do much easierly. Just recompile the world.
Posted Dec 9, 2022 18:28 UTC (Fri)
by epa (subscriber, #39769)
[Link] (7 responses)
Posted Dec 9, 2022 19:29 UTC (Fri)
by jrtc27 (subscriber, #107748)
[Link] (5 responses)
Despite the confusing name, it doesn't actually make the memory itself read-only, it "freezes" the mapping itself; note that this is how it's being applied even to .data, which clearly needs to remain RW if you don't want to immediately segfault, but you can still enforce that it remains a RW private mapping of the on-disk data segment until the end of time.
Posted Dec 10, 2022 7:43 UTC (Sat)
by dottedmag (subscriber, #18590)
[Link] (4 responses)
Posted Dec 10, 2022 23:55 UTC (Sat)
by mathstuf (subscriber, #69389)
[Link] (3 responses)
Posted Dec 15, 2022 20:21 UTC (Thu)
by VadimP (guest, #159437)
[Link] (1 responses)
Posted Dec 22, 2022 21:41 UTC (Thu)
by mrugiero (guest, #153040)
[Link]
Isn't yours precisely an example of what makes it confusing? One is talking about mapping a m(emory) region. Then about unmapping it. Then about protection for it. Then about hints about its usage. Then you talk about the mapping itself? It could have made sense to call it something like ptimmutable (page-table immutable).
Posted Dec 15, 2022 21:04 UTC (Thu)
by Cyberax (✭ supporter ✭, #52523)
[Link]
Posted Dec 10, 2022 13:06 UTC (Sat)
by edeloget (subscriber, #88392)
[Link]
mimmutable() for OpenBSD
mimmutable() for OpenBSD
mimmutable() for OpenBSD
Text already read-only?
Text already read-only?
Text already read-only?
Text already read-only?
Text already read-only?
Text already read-only?
Text already read-only?
Text already read-only?