[HN Gopher] mimmutable() for OpenBSD
___________________________________________________________________
mimmutable() for OpenBSD
Author : signa11
Score : 105 points
Date : 2022-12-10 05:15 UTC (17 hours ago)
(HTM) web link (lwn.net)
(TXT) w3m dump (lwn.net)
| bawolff wrote:
| 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?
| charcircuit wrote:
| >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.
| saagarjha wrote:
| 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.)
| notaplumber1 wrote:
| > 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.
|
| You mean like pledge(2)? The vast majority of the base system
| is pledged. So programs without the prot_exec promise cannot
| make new PROT_EXEC mappings, or add it to any existing pages.
|
| https://man.openbsd.org/pledge#prot_exec
|
| Perhaps you should spend more than 5 minutes reading about
| OpenBSD's layers of mitigations.
| trasz2 wrote:
| Isn't pledge(2) trivially easy to escape anymore? This used
| to be one of the differences compared to eg capsicum(2).
| anthk wrote:
| pledge(2) aborts.
| trasz2 wrote:
| And? How's that relevant?
| anthk wrote:
| man 3p pledge, try escaping it with a simple Perl script.
| beachhead wrote:
| is pledge easy to escape? can you give some examples?
| ggm wrote:
| 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
| Sirened wrote:
| >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.
| notaplumber1 wrote:
| If you look at the mitigations OpenBSD is doing as attack
| surface reduction, it means ultimately fewer tools in the
| attackers toolbox.
|
| It seems many of you are missing the forest for the trees.
| Sirened wrote:
| The question we have to ask ourselves is whether OpenBSDs
| mitigations are taking away enough attack surface (or in this
| case, exploitation tools) to make it worth the engineering
| and user cost.
|
| I'd argue that things like msyscall and mstack don't at all
| because they cost attackers only a couple of minutes of time
| _once_ to develop a bypass technique (ie move the stack
| pointer before a syscall, reuse the authorized syscall
| instruction) that they can apply everywhere. This greatly
| contrasts with mitigations like ASLR where each time an
| attacker wants to bypass the mitigation they are forced to
| develop a novel, program dependent strategy to leak some
| information. This is a huge pain and has definitely killed
| some otherwise exploited bugs because no such leak could be
| devised.
| 10000truths wrote:
| It also means fewer tools in developers' toolboxes. Not being
| able to make your own system calls directly, or exercise
| control over your own address space, means that anything that
| doesn't conform to a C runtime won't run in OpenBSD. Perhaps
| that's a tradeoff that the OpenBSD developers are willing to
| make, but even so, these particular "mitigations" do not
| address the root cause of many security vulnerabilities:
| failure to verify untrusted input.
| vbezhenar wrote:
| I think that only Linux had some syscall stability. Every
| other operating system provides libc or other kind of
| library with stable interface but syscalls are not stable.
| Yes, technically you can execute those instructions and
| your software will work on a fixed kernel version, but
| that's not what people do.
| Sirened wrote:
| So just ROP to the syscall you want in libc. Instead of
| targeting the syscall instruction, directly call mmap.
| System call randomization is a feature designed to
| discourage _legitimate_ software from hardcoding syscall
| numbers. It is not meant as an exploit mitigation.
| 10000truths wrote:
| Lack of syscall stability isn't a showstopper, it just
| means that the functions invoking the syscall need to be
| versioned according to uname(). The uname() call itself
| can be done via direct syscall if you're confident that
| its ABI won't change; or, if you want to be extra sure,
| you can use the libc wrapper for uname() and then
| munmap() the C runtime to replace it with whatever.
| Either way requires the maintainer to be proactive with
| implementing code paths for upstream upcoming kernel
| versions before they are mainlined.
|
| OpenBSD will make the above approach impossible, because
| with mimmutable(), libc will not be unmappable.
| saagarjha wrote:
| > 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.
| Sirened wrote:
| 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.
| saagarjha wrote:
| Right, iOS definitely has a different threat model which
| requires (and takes advantage of) Apple's ability to make
| custom hardware. In this case, though, OpenBSD is looking
| to protect userspace, and they could provide guarantees for
| that (iOS does this via codesigning, but you could also
| just force security-sensitive programs to stop using
| dlopen).
| ChoHag wrote:
| daneel_w wrote:
| _> "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."_
|
| Have you, or someone else as knowledgeable as you, been able to
| provide a succesful POC doing this? I admit that I haven't gone
| about looking for results on this topic and method of attack
| but I would be very interested in reading about it and see a
| practical example achieving it, since it's (in your own words)
| trivially defeated and simply needs just this or that thing.
| Sirened wrote:
| Fine, you've nerd sniped me. I'll write a blogpost defeating
| both mitigations next week :)
| beachhead wrote:
| i really hope you do this because it's super annoying to
| see all these folks talk about how these mitigations don't
| work but nobody really _shows_ it.
|
| it's always the same thing whenever openbsd is mentioned.
|
| "these mitigations don't work."
|
| - "okay, please show us that they don't work"
|
| "well, i don't use openbsd"
|
| rinse. repeat.
|
| it would also be nice to see some
| patches/fixes/suggestions/etc submitted after you've
| bypassed/defeated/whatever these things sent to the mailing
| lists. i don't suppose you'd agree to that?
| masklinn wrote:
| > 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?
| cmeacham98 wrote:
| The main binary has to be able to call into libc somehow, and
| thus it's typically easy to get a libc pointer if you have an
| existing method of code execution.
|
| I don't know how it works on OpenBSD, but on Linux there's a
| section of memory called the PLT that contains pointers for
| all the functions from dynamic libraries, including those
| from libc. Also, there's typically libc pointers all over the
| stack.
| Sirened wrote:
| If you've managed to map shellcode at all, you necessarily
| _already know where a valid syscall instruction is_ since
| otherwise you would not have been able to trigger the mmap
| syscall to map the shellcode. In any event, mapping shellcode
| is entirely optional anyways since to even get to that point
| you must already have full control through ROP, at which
| point you could do everything the program could (albeit in a
| significantly more annoying way).
| beachhead wrote:
| someone show us bypasses for all these mitigations that don't
| mitigate anything please
___________________________________________________________________
(page generated 2022-12-10 23:02 UTC)