Hacker News new | ask | show | jobs
by notaplumber1 1284 days ago
> 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.

2 comments

I think your comment would stand on its own better if it didn’t include the last line, both before and after you edited it. I do actually read up on what other OSes are doing, you know. And I look at what attackers target. What you’re missing here is the actual threat model this is supposed to protect against, and how this doesn’t actually work to protect against it, despite using building blocks that are “valid” in the sense that they can be used to build other, legitimate mitigations.

pledge is a good mitigation by itself. It’s a great way to sandbox things, and solve the problem of “this process no longer has any need to do these operations, so let’s just remove its capabilities to do that”. If you use it correctly it’s one of the strongest ways to make your program secure.

mimmutable by itself can be used to force a mapping to remain non-writable. This can be valuable because if you make the code that reads from that data also immutable, you know it will always read the same data, regardless of what an attacker can do in your address space. That is also a useful thing to have in certain cases.

If you read the thread where mimmutable was introduced you can see that the primary usecase proposed by Theo is to shore up msyscall. The threat model he has in mind is an attacker who can mprotect libc to be writable, which if I remember correctly was some minor detail of a Linux writeup once. However, there are several problems here.

One is that mimmutable by itself doesn’t actually fix the problem here because of the concern brought up at the top of this thread. You will note that the thing you quoted practically screams “but what if there was a way to prevent new executable mappings?” I actually did this intentionally, with full knowledge of how you can use pledge to do this. I think it is important for people who are not security experts to be able to follow the thinking that goes into analyzing these things, and be able to synthesize the usecase I mentioned at the start of this comment for themselves.

You will still note that I called msyscall weak in my original comment, and it still remains weak even after mimmutable+pledge is implemented. As others have mentioned as well using ROP to jump to the syscall instructions in libc with your own arguments (it’s not special…) bypasses restrictions in the current design.

In fact I can extend it with something OpenBSD does not have a good implementation of yet, strong CFI, which would prevent jumping into the middle of a function to execute that syscall instruction. But there are more fundamental reasons why this doesn’t work.

Protecting certain “sensitive” operations is actually something you can do. PPL as I mentioned below does this. It’s built on top of strong CFI (PAC) and code integrity guarantees (KTRR), which we are assuming you can construct in userspace on OpenBSD. The difference is that PPL has a tiny surface, and OpenBSD’s libc has a large one. In practice this means that libc cannot actually tell whether a request coming from an application is legitimate or not. If you look at the threat model for mimmutable again, consider that it assumes an attacker has enough control to call mprotect with controlled arguments: this means they know the layout of the address space and they can subvert control flow. With this kind of control, it’s not that hard to just decide to do other system calls instead. And who needs a syscall for that? You can literally just call the libc function which is a wrapper around the syscall. How is it to know that my call to write is malicious and the seven thousand the program already made was not?

> As others have mentioned as well using ROP to jump to the syscall instructions in libc with your own arguments (it’s not special…) bypasses restrictions in the current design.

...ignoring other mitigations.

> In fact I can extend it with something OpenBSD does not have a good implementation of yet, strong CFI, which would prevent jumping into the middle of a function to execute that syscall instruction. But there are more fundamental reasons why this doesn’t work.

Sounds like you need to spend 5 more minutes reading about retguard.

I need you, just for a moment, to understand that I am familiar with most OpenBSD mitigations and how they work, and that I have chosen my words carefully in my response to account for them. The fact that I even have to do this because you seem intent on leveling attacks at me, personally, in your responses is unfortunate because it would take me far less time to respond if I could assume to have a good faith conversation with you, which is what this site is really for, and not have to qualify every single thing I said with every possible "rebuttal" you are going to respond to me with. There is a world of difference between responding with genuine curiosity, e.g. "wouldn't retguard help protect against this?", and what you've done here.

Anyways, the critical word in the thing you've quoted is "strong"–retguard falls apart if the attacker has the ability to leak the value being used to protect the stack. In almost all cases an attacker in the position to make a controlled call into libc has the ability to also leak or forge any value of their choosing in the address space. A strong implementation would typically move this value completely out of the reach of such an attacker, for example by implementing signing in hardware (e.g. PAC) or or at a higher privilege level. I don't think these things are really feasible yet for OpenBSD so I don't blame them for not doing it, but without strong CFI it's hard to actually prevent this kind of thing from happening. (And, as I mentioned, this was only a tangent anyways; even with good CFI the more fundamental issue remains.)

I will agree that you have chosen your words carefully, and with obvious intent.
Isn’t pledge(2) trivially easy to escape anymore? This used to be one of the differences compared to eg capsicum(2).
is pledge easy to escape? can you give some examples?
For a while you could just execute another binary, it would run without restrictions imposed on the (pledged) parent. This is a stark contrast to Capsicum, where the monotonicity (ie the fact that once you loose the permission to something you'll never ever get it back, unless being explicitly passed it again) is one of the fundamental assumption behind the design.
No, not really.
pledge(2) aborts.
And? How’s that relevant?
man 3p pledge, try escaping it with a simple Perl script.
In other words you don't know what you're talking about, otherwise you would be able to answer a simple question.