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