Live data from Hacker News

mimmutable() for OpenBSD

lwn.net

1–10 of 41 posts

Re: mimmutable() for OpenBSD

#2
Im not a c programmer or otherwise work this low level, so im probably missing something obvious.

What is the point of making something immutable if you can just create a new write-exec region? Surely if you have enough control over the process to change permissions on an existing memory segment you have enough to mmap a new region with the permissions you want?

Re: mimmutable() for OpenBSD

#3
post #2

Im not a c programmer or otherwise work this low level, so im probably missing something obvious. What is the point of making something immutable if you can just create a new write-exec region? Surely if you have enough control over the process to change permissions on an existing memory segment you have enough to mmap a new region with the permissions you want?

>What is the point of making something immutable if you can just create a new write-exec region?

You aren't the only thing using the memory. If you create a new region only you will be the one using it.

Re: mimmutable() for OpenBSD

#4
Can't stop the instruction set being able to write. Can stop higher logic invoking them, sometimes. Good to do. I really like the level of integration with compiler, low level boot, trusted code regions, "you will almost never call this, but your OS might on your behalf"

Live patching un-stoppable systems may be a challenge unless you code to passing live IO and state across process boundaries

Re: mimmutable() for OpenBSD

#5
post #2

Im not a c programmer or otherwise work this low level, so im probably missing something obvious. What is the point of making something immutable if you can just create a new write-exec region? Surely if you have enough control over the process to change permissions on an existing memory segment you have enough to mmap a new region with the permissions you want?

No, you're not missing anything. The primary reason this thing was designed was so try to patch out some holes in another unusual "mitigation", where system calls are checked to make sure they come from OpenBSD's libc. In this case adding new pages with syscalls in them would not work, but it seems like Theo read a paper at some point where they mprotected libc and patched its code and this spooked him into realizing that if you can mprotect libc itself this address range could be rewritten to contain attacker-controlled code, and make syscalls, which would break msyscall. So mimmutable prevents an attacker from ever remapping those pages, so they can never modify them even with mprotect.

Of course, msyscall itself is a weak mitigation, because an attacker can (as you mentioned) allocate another page with arbitrary code in it save for the syscall instruction, construct a call state with it, and then jump to any syscall instruction in libc. So this doesn't protect against much, anyways.

mimmutable itself is not really a dud, it's just not that great if you try to layer it on top of something already useless. It's great if you want to make sure a page (of data, usually) really stays immutable. But using it to prevent the introduction of new code is not all that effective, unless you are far more stringent about how you allow processes to allocate executable regions. For example, if you prevent a program from mapping in any new PROT_EXEC pages after it's initialized itself and then mimmutable all its code, then no new code can be introduced, which is a useful property. But you need that extra bit which mimmutable by itself doesn't give you.

(FWIW, macOS already has something similar to this called VM_FLAGS_PERMANENT, which prevents a mapping from being removed. This, together with the maximum protection on a mapping, can be used to prevent a region from ever gaining a certain permission. I believe you can reduce it to PROT_NONE though.)

Re: mimmutable() for OpenBSD

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

This is, unfortunately, a continuation of OpenBSD's long tradition of security "mitigations" that are entirely detached from any actual understanding of exploitation [1]. The above suggestion that attackers can no longer make syscalls from their shellcode, while ostensibly true, is moot because you can simply JOP to one of the authorized syscall instructions and entirely defeat the mitigation. Even if mimutable outright prevented any new code from being mapped, this still wouldn't really stop anyone; just look at iOS where attackers have thrived despite there only being one process on the entire OS which is able to map arbitrary code.

> 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 too is trivially defeated. You simply need to add another stage to your payload in which you copy your ROP payload onto the legitimate stack, pivot, and continue executing. If you managed to pivot off the original stack, you already have a pivot gadget and so you only then need to trigger a call to memcpy. This mitigation isn't stopping any real attacker.

While I never hope to discourage kernel developers from thinking about security mitigations, I think it is really important that people working on these mitigations actually converse with people who write exploits. Other vendors have seen considerable success with their mitigations [2] in that they specifically target things that attackers need and want to do, such as heap spray.

[1] See: ROP gadget elimination, a feature which does not actually stop ROP but merely hopes that "a substantial reduction of gadgets is powerful." https://marc.info/?l=openbsd-tech&m=150317547021396&w=2>. Speaking from experience and having written plenty of actual exploits on attacker hostile platforms, trying to block code execution against an attacker who already has kernel read/write is an utterly ridiculous idea because even if you manage to stamp out every single ROP gadget, they can just go and stomp on your kernel page tables to map new kernel code. Even baring that, kernel R/W is still a complete compromise even if kernel code execution is somehow impossible because you can still just use R/W to bypass any control the kernel could ever hope to enforce.

[2] Life and death of an iOS attacker by Luca Todesco https://www.youtube.com/watch?v=8mQAYeozl5I>, a discussion of exploit mitigations on iOS and the exponential cost of defeating mitigations on iOS. Luca's company writes exploits for iOS devices as a service and he is extremely knowledgable in this area.

Re: mimmutable() for OpenBSD

#7
post #6

>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). This is…

No post body was provided.

Re: mimmutable() for OpenBSD

#8
post #6

>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). This is…

> trying to block code execution against an attacker who already has kernel read/write is an utterly ridiculous idea

iOS does this. Kernel code is immutable (obviously), page tables are locked at reset and cannot be modified except by PPL code. ROP and JOP are mitigated against using hardware CFI (PAC). As you mentioned, when vendors actually understand what exploits look like and what attackers need to do, it enables them to do a good job. OpenBSD remains a system with a few good ideas and a model of what exploits look like that appears to be informed by dreams and idle musings, rather than actual research.

Re: mimmutable() for OpenBSD

#9
post #6

>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). This is…

> trying to block code execution against an attacker who already has kernel read/write is an utterly ridiculous idea iOS does this. Kernel code is immutable (obviously), page tables are locked at reset and cannot be modified except by PPL code. ROP and JOP are mitigated against using hardware CFI (PAC). As you mentioned, when vendors actually understand what exploits look like and what attackers need to do, it enable…

I believe I should have couched that statement with a "for most everyone else" :)

The PPL is a strong mitigation, I agree, but it is definitely one that is really only supported through their ability to ship custom hardware. Pulling off a PPL-like thing using MPK or a hypervisor is technically possible but in practice no real platform other than Apple is actually able to enforce memory mappings on all devices on an SoC and so anything you dream up to protect page tables is as durable as a wet paper bag since there's usually a half dozen other chips that will hapilly DMA any and all parts of physical memory for you. I dream of a day where we have an SMMU in front of every single device on a regular smartphone SoC (or, hell, even a PC platform), but that day has not yet come and I'm not really holding my breath either. As such, nobody else can actually meaningfully guarantees kernel code integrity under kernel R/W and so even if they had a perfect PAC implementation (which, as I'm sure you know, is incredibly difficult), you could just corrupt the backing code pages and sidestep it.

Re: mimmutable() for OpenBSD

#10
post #6

>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). This is…

> you can simply JOP to one of the authorized syscall instructions and entirely defeat the mitigation

But now you need to know where the libc is mapped in order to find those syscalls instructions yeah? Which increases the complexity of the exploit, because instead of making a syscall you have to find where the libc is mapped, and the entire point of ASLR is making that difficult, no?

Post reply on HN