|
|
Subscribe / Log in / New account

mimmutable() for OpenBSD

By Jonathan Corbet
December 9, 2022
Virtual-memory systems provide a great deal of flexibility in how memory can be mapped and protected. Unfortunately, memory-management flexibility can also be useful to attackers bent on compromising a system. In the OpenBSD world, a new system call is being added to reduce this flexibility; it is, though, a system call that almost no code is expected to use.

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
SecurityOpenBSD


to post comments

mimmutable() for OpenBSD

Posted Dec 9, 2022 16:43 UTC (Fri) by abatters (✭ supporter ✭, #6932) [Link] (2 responses)

Not all programs need to be converted at once. For example an ELF flag was previously added to indicate which programs don't need an executable stack:

https://wiki.gentoo.org/wiki/Hardened/GNU_stack_quickstart

mimmutable() for OpenBSD

Posted Dec 9, 2022 16:51 UTC (Fri) by kilobyte (subscriber, #108024) [Link]

And we do enable optimization or hardening features all the time. Stuff like LTO or relro. It's a bunch of of projects at first, then it comes as a dh flag (like optimize=+lto), then it's packaging default, then compiler default. All while old software continues to work, even if compiled with libc5 or whatever.

mimmutable() for OpenBSD

Posted Dec 11, 2022 20:38 UTC (Sun) by mirabilos (subscriber, #84359) [Link]

And this is still biting us, especially with .S files.

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.

Text already read-only?

Posted Dec 9, 2022 18:28 UTC (Fri) by epa (subscriber, #39769) [Link] (7 responses)

I’m surprised, I thought the text section of a process (its executable code) was already read-only. If not on every system then certainly on OpenBSD. It must be for demand paging executables to work. Is the difference here just that the process cannot remove the immutability no matter what system calls it makes?

Text already read-only?

Posted Dec 9, 2022 19:29 UTC (Fri) by jrtc27 (subscriber, #107748) [Link] (5 responses)

It's two things: you can't mprotect it to something else (even a strict subset, not just something that adds permissions) and you can't mmap something else over the top (even with the same permissions).

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.

Text already read-only?

Posted Dec 10, 2022 7:43 UTC (Sat) by dottedmag (subscriber, #18590) [Link] (4 responses)

Yep, it's "mapping immutable", not "memory immutable". Confusing.

Text already read-only?

Posted Dec 10, 2022 23:55 UTC (Sat) by mathstuf (subscriber, #69389) [Link] (3 responses)

Indeed. Abbreviating the noun to a single letter and spelling out the `immutable` adjective (which could easily be `immut` without collisions) seems like quite an odd decision to me.

Text already read-only?

Posted Dec 15, 2022 20:21 UTC (Thu) by VadimP (guest, #159437) [Link] (1 responses)

mmap, munmap, mprotect, madvise, ... mimmutable.

Text already read-only?

Posted Dec 22, 2022 21:41 UTC (Thu) by mrugiero (guest, #153040) [Link]

> mmap, munmap, mprotect, madvise, ... mimmutable.

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).

Text already read-only?

Posted Dec 15, 2022 21:04 UTC (Thu) by Cyberax (✭ supporter ✭, #52523) [Link]

When I saw this headline, I was for a few seconds confused. What is "mimmu"? And how is it related to tables?

Text already read-only?

Posted Dec 10, 2022 13:06 UTC (Sat) by edeloget (subscriber, #88392) [Link]

Text mapping is rx (read-execute), not ro. This new BSD system call means that whatever you use to try to change that will fail.


Copyright © 2022, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds