Contributed by
Peter N. M. Hansteen
on
from the unmute the immutable dept.
In a recent message to the tech
mailing list, Theo de Raadt (deraadt@
) summarized the state of the new memory protections work. The thread also includes a followup from Otto Moerbeek (otto@
) on consequent changes to the memory allocation mechanisms.
Theo writes,
From: "Theo de Raadt"Date: Fri, 18 Nov 2022 03:10:05 +0000 To: openbsd-tech Subject: More on mimmutable [LONG] I am getting close to having the big final step of mimmutable in the tree. Here's a refresher on the how it works, what's already done, and the next bit to land. DESCRIPTION The mimmutable() system call changes currently mapped pages in the region to be marked immutable, which means their protection or mapping may not be changed in the future. mmap(2), mprotect(2), and munmap(2) to pages marked immutable will return with error EPERM.
That's the system call. In reality, almost no programs call it. Let me start by explaining a process's address space, starting with the simplest programs and then heading into more complicated cases. A process runtime has a - stack (rwS permissions, S being the annotation used at system call entry to ensure the "sp" register points to stack, and thus prevent a class of ROP pivot methods), - a stack-guard (for growing the stack in case rlimits are changed, this is is permission NONE) - a signal trampoline page, randomly placed, which will perform sigreturn(2), (permission rwe, e being the annotation used at system call entry to ensure the "pc" register points at a region allowed to system calls, thus preventing attackers from uploading direct system call instruction code) Those objects are automatically marked immutable by the kernel. On to static executables. The kernel loads a static ELF binary into memory as a text segment (rx permission), followed by a data segment (rw permission), a bss (zero'd data, rw permission), and a rodata segment (ro permission). The order of these varies per architecture. There is an overlay of this called the "GNU_RELRO", which is pretty uhm special. I've created a new overlay called "OPENBSD_MUTABLE", which is page-aligned and must not be made immutable. As it happens, these two special regions are the only part of the image load that cannot be marked immutable, so the kernel proceeds to mark everything else immutable. When that static program starts running, it will run the crt0 ("c run time") startup code, which can make some small changes in the GNU_RELRO region, and them mark it immutable. 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. It happens to be an page. There is another ugly old wart called "text relocations", and I won't get into it except to say the kernel recognizes such binaries, and skips some immutabilities, but of course crt0 finishes the job. I want to speak a bit about the mechanism. Inside the kernel, immutability is applied to all the regions. And then the exceptions are marked mutable. The kernel is allowed to reverse setting immutability, but userland cannot. This will come up later. Now let's talk about dynamic executables. The same applies as above for the main program, but then the kernel also loads another object i