Re: More on mimmutable

2022-11-17 Thread Otto Moerbeek
On Thu, Nov 17, 2022 at 08:10:05PM -0700, Theo de Raadt wrote:

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

This will change.

I have code ready to change the init of that malloc data structure so
that the one page wil be modified to contain the right data, then made
readonly and then made immutable.

-Otto



More on mimmutable

2022-11-17 Thread Theo de Raadt
[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 into memory:
/usr/libexec/ld.so -- the shared library linker.  And instead of starting
to run the main program, execution starts in the shared library linker.

The shared library linker ELF image contains similar objects.  There is a
GNU_RELRO, which the kernel cannot mark immutable.  There is no OPENBSD_MUTABLE
because we don't request creation of one.  There is a special "boot.text"
section that the shared library linker unmaps upon startup, as a security
measure, and the kernel ignores that region.  All the other regions of
ld.so are marked immutable by the kernel automatically.

Now ld.so starts to execute, and the first job it does is to fix it's
own relro section, handle text relocations in the dynamic binary, repair
some permissions, and then mark itself and the main program immutable.
Completely immutable, except for the OPENBSD_MUTABLE page in malloc(2).

I was really surprised we got to this point without blowing up the ports
tree in a major way.  Some of these warts were found along the way and
changed the direction a little.  And I don't want to talk about sigaltstack
right now.

ld.so is now responsible for loading the shared libraries required by the
program.  Shared libraries have the same pieces as regular programs, so
they are loaded into memory, but here we hit a problem.  In the kernel we
could simplistically mark all the regions as immutable, and then reverse
it for the special cases.  Userland code cannot do that.  So we have to
keep track of the sections we want to be immutable, as well as the regions
that are immutable, and then subtract the differences, and apply the
immutables very late in the shared library loading process.  I call this
code the clipping engine, and I had a lot of bugs in it.

There is another way shared libraries are loaded:  via the dlopen()
call later at runtime.  With the flag RTLD_NODELETE, libraries can
be marked immutable.