Hacker News new | past | comments | ask | show | jobs | submit login
Synthetic Memory Protections: An update on ROP mitigations [pdf] (openbsd.org)
96 points by notaplumber1 on March 25, 2023 | hide | past | favorite | 55 comments



Dragos Ruiu (@dragosr) also provided the video recording on his Twitter account.

https://twitter.com/dragosr/status/1639015014177841153


It's amazed me that the first 50 years of computers were "this is how to structure memory for operating systems" and the next 30 have been "hackers take advantage of all of that so we need to do a bunch of convoluted stuff just to try to thwart them." Kind of unfortunate that so much energy has to be spent on this work, but I appreciate that it is.


no actually after 50 years of "how to shoehorn memory into something C can use" we reap the rewards


perhaps it's time we stopped shoehorning memory to fit C and just used more memory-safe languages?


Multics had both. It's the Unix inspiration. Imagine something more secure and with a better hardware design than Unix.


I'm wondering what an alternative model would look like, not oriented towards C.


Some real world examples I have used:

Harvard architecture machines (which are not uncommon in microcontrollers)

Segmented memory, or any non-flat memory

Addressable memory with non-uniform access time (cache doesn't count because cache lines can't be addressed directly)

Address spaces not a factor of 2. Variable byte sizes ("byte" did not mean "8 bits" until the 360, and even in those days, just in the IBM world)

Word length larger than address length.

Some hardware-tagged architectures.

Machines with hardware-supported transporting GCs.

Different regions of memory that are architecturally distinct (shared memory with machines of different architectures, which these days can mean GPUs).

And one I haven't used yet:

distributed-computation-in-RAM


One alternative CPU model were the various Lisp machines.

A really amazing example, though, was Intel's first 32 bit CPU, the iAPX 432 [1] -- it was actually object oriented. And it supported garbage collection (like Lisp machines).

It was kind of beautiful from one point of view, but it was absurdly impractical and complicated and slow. It was an extreme example of CISC, and the (simple and fast) RISC revolution killed off such things. Well, the iAPX killed itself, but...

[1] https://en.wikipedia.org/wiki/Intel_iAPX_432


Some of the 8-bit micros without many general purpose registers are relatively C unfriendly.


Ok, but they're not friendly to anything but assembler.

Well, since they typically have memory that cycles in the ballpark of instruction cycle times (unlike desktop and server CPUs where there's two orders of magnitude difference), that's friendly to Forth I suppose. But that is a small minority of usage even in those environments. It's more like Forth is friendly to slow architectures. :)


I've seen even those forced to C, with such gems I've run into like:

Link-time stack resolution(and aliased even, so pretty good density)

Default int is char. Please use short or ANSI int.(because 16-bit int would be painfully slow)

Recursion is not supported in this memory model(because the stack is resolved at link time, so...)

Function pointers must be compile-time resolvable(I think this machine didn't have a way to represent program counter values in GPRs/variable jumps)

"Function prototypes are an ANSI feature"(part of compiler the front-end also used on a bunch of UNIX box compilers).


Wow. Have you got a link? I hadn't heard about that. That's kind of a mind blowing list.


I think most of these solutions probably predate 8051 and ST7 cores(which both have stack pointers) where I ran into them but reduced memory models are still pretty useful for them, due to the overhead vs RAM usually fitted. It's too late to edit my comment. I'll mostly discuss the fallout from static stack variable allocation.

From https://www.st.com/resource/en/user_manual/um0015-st7-8bit-m... (8.3.5 Limitations put on the full implementation of C language)(I think this is talking about Hicross C, but COSMIC and Raisonance work the same depending on stack memory model): The ST7 family provides a limited RAM size, of which the stack takes only a part, that can be as small as 64 bytes. This does not allow the use of the stack for parameter passing. Thus, the implementation of C for the ST7 uses registers and a few memory locations to pass the parameters, and allocates local variables to RAM just like global variables. This works the same way as in a typical implementation, but with the following restrictions...

You can still get an evaluation copy of COSMIC C and try this out. Here I made a function call itself void port_init(void){port_init();}. Note that this error comes from the linker, clnk, not the compiler, because the linker is responsible for stack allocation globally, as described above): #error clnk vumeter.lkf:1 function _port_init is recursive.

There's a similar error if you call a function from anything called from main() and also from any interrupt entry point. This is because the memory model isn't re-entrant, so calling the same function from >1 path can cause them to overlap, corrupting their staticly allocated variables.

https://www.cosmicsoftware.com/pdf/RX.pdf has an explanation for "is recursive" and "is reentrant".

COMSIC C will also let you compile enum {x = x}, but I don't know what value x gets.


The fact that you must trust programs to do the right thing is the root cause of the problem. There exist operating systems and security models that stop this type of attack dead in its tracts.

This is fallout from the failure of Multics, and the rise of Unix.


Could you please elaborate? One must trust programs that handle data to handle data, that requirement is difficult to get around.


First let me offer an analogy

What would the use of electricity be like without circuit breakers? You'd have to carefully and completely vet each new device you wanted to connect to your house, and make sure that you weren't going to burn the wires up, or even take down the power grid. (AKA the power in the 1960s TV show Green Acres)

With circuit breakers, you carefully limit the availability of current to loads, and protect the wiring inside the house from many forms of trouble.

--

When you run a program on a PC, by default it runs with all of your credentials. There's nothing stopping it from ANY side effects. You're restricted to carefully considering each piece of software, and hoping it doesn't take your system down, or worse.

A system that specifies at/during runtime what resources a program is allowed to access and how (via capabilities) can't be subverted to reach outside those restrictions, no matter how clever or confused the program gets.


Awesome, thanks for the great analogy and explanation! What you are proposing is the compartmentalization approach, like sealing off areas of a ship or cordoning off a fire using walls (or fire trails in a forest). I am definitely a proponent of capability based security for tighter control over permissions and limiting the damage when things go wrong. The only problem with that is that some programs by definition MUST have access to user data and therefore by definition WOULD cause harm. So, for example, an email client needs network access and to upload attachments from the filesystem, in order to perform its function. Then, those same said permissions can be subverted. Tricky...


Capability Based Security is a much richer choice than the simple App Permissions you see on phones, it includes "powerboxes" which replace the Dialog Box your application calls with the same result... only the User picks the file, and the OS enforces the resulting selection (instead of trusting the app to do it)

As far as the user is concerned, it works the same way... but as far as we programmers are concerned, it now makes it impossible to get at files the user doesn't want the program to reach, in a very simple and transparent way.


         man login.conf


How does using this allow the user at run-time to specify that a task should only have read access to X and dynamically give write access to fu.bar?


Ideally, the scope of failure to do with the data what they should do, would be bounded. In reality, programmes asked to handle some data can fail to do the expected handling and produce undetermined side-effects.


Precisely! We need to structure programs so as to behave within a well defined transitive data closure.


> The fact that you must trust programs to do the right thing is the root cause of the problem.

Well, yeah, that's the issue. That's why relying on simple DAC is so inadequate. Really, some kind of MAC is needed. Things like pledge and unveil are nice but clearly inadequate (I actually had a pretty braindead discussion on that recently, with someone not understanding the differences and trying to equate them out of ignorance, sigh).


Ahem. It might not solve the issue but it's to late to bring Multics back. And I acknowledge Multics was far better in security.

Also, something I would like it's the polar opposite with the MIT/ITS philosophy + Emacs. There's GNU Guix, but I don't like Ice-9's crap on Guile as if it was the default, I prefer SRFI's. Something hackable from the start, with Scheme as the REPL and a Scheme based window manager. Gnome with Mutter bindings to Guile instead of GJS would be a dream.


Multics? I was talking more about things like RSBAC.


Was lucky enough to watch this in person. One of the best talks of the conference.

The immutable bit together with the syscall bit is going to be a real pain for shell code


This is literally going to be a minor annoyance at most for the handful of attackers that go after OpenBSD.


Today even doing something outside the syscalls is not allowed.


Which syscalls?


> iOS is execute-only; Android tried a few years ago (abandoned)

Wonder if the author is aware of the reasons why this was disabled (it's functionally gone on both platforms). On iOS newer processors have PAC which provides much stronger guarantees against ROP and Linux disabled it because execute-only mappings bypass PAN: https://blog.siguza.net/PAN/.

> Dumb applications that invent their own ABI (very few)

I mean I know this is meant to be bait but I'll take it, applications that use their own internal ABI are valid programs.

> On every kernel entry, if the RPKU register has been changed kill the process

> When a process does a system call, the SP register MUST point to stack memory!

Has https://xerub.github.io/ios/kpp/2017/04/13/tick-tock.html vibes

> Stack and Syscall Protection detect a variety of easier exploit patterns, pushing the ROP programmer to explore more challenging schemes, which may not be viable

> Increasing exploitation difficulty is a valid strategy

Ok so this is the actual interesting part of the paper, because it seems like they are trying to shore up their syscall-origin protections which are not very strong in the presence on ROP, except trying to do so on hardware that doesn't really have CFI protections.

As far as I can tell, this Xonly protection only attempts to disrupt blind ROP ("you can't read the code anymore"), rather than construction of a full ROP chain. There are some attempts to validate things on entry to the kernel (pc, sp) but they are under control of userspace so what probably will happen here is that they get switched back to sane values prior to kernel entry and then adjusted to attacker-controlled values again. I expect this to require some cleverness on the side of attackers but this is typically how such checks are bypassed, assuming that there is not some other overlooked way to get around it.

This brings us to OpenBSD's strategy for exploit mitigation, which is in my eyes has far too much tunnel vision: it tries to match on individual exploit strategies, rather than trying to protect against more general problems. The policy of "let's make exploitation harder" is actually very close to something I'm working on right now and it has a number of important caveats that I don't see addressed here.

These things are true:

* Reducing the reliability of an exploit makes it far less attractive.

* Adding non-perfect mitigations against common exploitation strategies makes it so that people can't just throw a proof-of-concept against another platform against your system.

However, these are also true:

* Attackers are very, very good at turning "we made this 99% secure!" into "this will basically never work".

* Attackers will construct new strategies that you didn't think of to attack the same underlying problem if you don't fix it, if given adequate time.

I am not an exploit author, so take this with a grain of salt, but I would guess that an experienced team could probably come up with a way to do either of the above in maybe a year. And at that point, once it's broken, the cost from the OpenBSD side to improve upon this protection is high, because they will break the entire design of this thing, which requires a human to revisit this and create a new clever design to keep attackers at bay. In that way it will become just a routine step in an exploit to evade the protection, as opposed to say NX, which completely killed the ability to ever do shellcode execution from the stack, necessitating the development of ROP over multiple years. Good mitigations are highly asymmetric in terms of effort required to design them versus how long an attacker needs to take to fully bypass them. Usually this means that if you're spending significant time designing something it will probably want to be sound rather than reducing the window of opportunity for an exploit.


> Wonder if the author is aware of the reasons why this was disabled (it's functionally gone on both platforms). On iOS newer processors have PAC which provides much stronger guarantees against ROP and Linux disabled it because execute-only mappings bypass PAN: https://blog.siguza.net/PAN/.

Yes, of course he is. He even mentions PAN being broken in the recording. What doesn't make sense is the Android/Linux decision to entirely abandon execute-only. Let PAN be broken, newer chips will eventually fix it in hardware (EPAN) and older chips without PAN (notably, the Raspberry Pis) still get full protection.


I suspect (though have no special knowledge) that the reason it's still off is that it just wasn't that valuable.


The Xonly stuff they talk about is so weird to me, because almost no one cares about exfiltrating assembly for dynamic ROP creation or whatever? Even if you are doing fingerprinting of binaries to pick an exploit version, you do that with a stack leak for return addresses to get relative offsets for a ROP or figure out a version. If someone is doing a ROP for an exploit they probably already have a built ROP chain to use with it!

Execute-only makes more sense for kernel exploits, and especially for the BSDs that do extremely aggressive per-codeunit kASLR at startup, but the fact Android dropped it should make you double think how worthwhile it is.


> This brings us to OpenBSD's strategy for exploit mitigation, which is in my eyes has far too much tunnel vision

While I understand where that comes from, I'd argue that OpenBSD does both. There is quite a few more general approaches in the system.

In my opinion (which might be wrong, please disagree!) you need both, because one tends to have that issue that layers and layers of general mitigation are added, but when someone takes a look the issues tend to arise where the specific setup and general context is exploited which is harder to protect against.

There is a great talk that I can't find right now, that is about a company network that was pretty securely set up, but taking a look at the constellation (including specifications of standard protocols) is abused to still compromise it.

I am not sure if that's the best approach, but while I agree it's overall better to completely rule out a whole class of bugs/attacks go for it, however it's usually with exceptions which is why these these things are even still a topic.


I think OpenBSD has tunnel vision on the wrong part of an exploit. Like they'll read a blog post on how to construct a ROP chain and instead of figuring out how you might prevent someone from subverting control flow they look at the tool used to find gadgets and try to make that harder. Or if they see exploits that spray syscall instructions they will block them in JIT regions. The problem here is that these aren't actually the hard parts that need significant effort to change for an attacker, they're really just whatever happened to be convenient. You can do a very specific mitigation that e.g. hardens a problematic API but you really want to make sure an attacker goes "hmm, I am not really sure what I would do if you blocked this, I guess I'll have to think for a while about how I might even get started" versus "sigh, this is annoying, guess I need to try the other way that is a little more work".


Can you share more about what you are working on right now? If not, hopefully we'll see something about it in the near future once you're done.


I hope so! There’s not much to share yet because I’m still working on evaluating how well it would work, but the aim is to make n-day exploits infeasible to deploy to devices that are still vulnerable. It’s difficult because we are have very little we can work with when dealing with an attacker who can fully compromise the device, but some preliminary analysis of the strategy against recent exploits and approximations of how they might change if we ship are promising. The key point is that we expect our strategies to “expire” on a given timeline and need to explicitly design in a way to respond to changing techniques in a way that is highly asymmetric. We’ve found that the closer to the actual bug you place a mitigation the harder it becomes to work around, and we think we have a new way to get very close cheaply.


I'm looking forward to it!


This is a fascinating topic, are there any practical real life implementations of this?


This presentation is an overview of many different mitigations spanning ~27 years. Some are quite widely implemented. I'm less familiar with the newer stuff, but here's some I've run into.

No-exec stacks: some UNIX machines, tons of systems today.

w^x: win xp+, Linux, several RTOS, OpenBSD, lots of others

aslr: requires MMU. OpenBSD, contemporary Windows(but partially opt in?), Linux.

X-only: Arm has supported X-only for their embedded stuff for a while, and it's fairly lightweight, though you lose PC-relative loads.


OpenBSD?


It's worth noting that the new mitigations discussed in the talk are only available in -current. They'll be in the next release though (which should be coming in the next few months).


April being the best guess currently, though end of likely, because May 1st is the usual goal. The best guess being April, because it's what the (non-finished) 7.3 page says for the month right now. Of course that's not a guarantee, but it makes it likely. Also a branch of 7.3 already exists.


Honestly the only thing that really needs to be said:

https://nso.group/@qwertyoruiop/110086216898968720


It would be cool for one of these folks (or anyone really) to show us why these things won't work. I see people I respect in that thread but I'm also very tired of hearing about how trivial these things are and not seeing someone spend, according to them, very little time to bypass these things.

Obviously, I'm not smart enough to do it or else I'd be doing it. However, I'm not going around making wild claims either. I think something like that would help rather than hinder OpenBSD.


People have done this in the past, at this point most people are just going to meme about it rather than respond. The response that they get is always “if it’s so easy why don’t you hack it?” which is quite frankly more effort than anyone wants to spend on an OS that doesn’t really harm anyone just sitting by itself layering all sorts of “mitigations” on itself. They’re basically completely divorced from what any real-world exploit these days looks like (blind ROP, really?) or how attackers work (“99% secure will stop them!!”) but somehow always really convoluted and optimized at stopping one very specific exploit flow rather than a general technique. The real solution for stopping ROP/JOP is going to be CFI, shadow stacks, etc. rather than trying to kludge something on hardware that doesn’t support it.


I hear you. I guess I'd just like to see more hacking and less of the memes. For me I think again that it would help more than hurt.

I'm an old man now and maybe I've gone a bit soft but I don't see much benefit in mocking and am more interested in helping even if that means wasting a bit of time.


Someone might make a CTF challenge out of it someday I guess.


While I don't say it is, this is also a classical tactic to discredit something that could be a problem. This strategy has been used with other projects. If you are worried that this has potential it will ruin your project/income you're going to shit-talk it.

Some years ago there was a leak of plans to do that very with Tor. Spreading FUD so less secure systems are used. Discrediting contributors, turning people against each other and so on.

Common theme. If someone has a way to break something, they'd at least gain publicity for it, if they have any positive interest they'll at least mention a source or provide any chance for rebuttal (the whole point of the scientific method), if neither happens be at least skeptical.


Are there any real CTF's done against OpenBSD to present this evidence?


Not that I'm aware of, but as someone who works in security, I've exploited a bunch of bugs against real world, hard targets both for my own educational purposes and also as part of my job with client engagements. I'm not going to pretend I'm the best in the world, but I'm decent. More importantly, I know a lot of folks with hats of many colors who are a lot better than I am.

When Luca Todesco (the person who wrote that toot) tells you your exploit mitigations are trash, you listen.

Like I said, I'm not going to make any claims to being an elite hacker. I have a cool job that I love, and I enjoy doing this stuff for fun too to keep my skills sharp. But reading through that presentation, there's nothing that made me pause and think "This is a game over scenario." If you have a moderately powerful bug with halfway decent primitives these mitigations aren't really going to stop anyone.

An elite team like NSO group? This isn't going to effect them one bit.


If you think Theo and co. are newcomers to security, y'all gonna have a bad time. You and the NSO group.


Could you explain what does "boomer" means here? I think I understand "Ok, boomer" in general but can not extrapolate this understanding to security talk.


Outdated, simplistic, incorrect.


This looks really nice, I'll have to dig into it more later.

Always nice to see the OBSD team actually implementing protections and controls instead of relying on audits.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: