The ROP-gadget stuff hasn't migrated to any other kernels that I have seen.
As I understand it, previous ROP-gadget hardening included compiler modifications on system/function call return, trap sleds, and some other stuff that I don't remember. This is on top of the kernel and library relink at every boot (to really make ASLR more potent).
These modifications include a verification that the jump is in the stack, and not at the tail of some function (and the other thing eludes my understanding).
These are some OpenBSD references on ROP hardening:
I think that the most notorious exploit of "Return-Oriented Programming" (ROP-gadget) flaws was Solarwinds; I understand that a ROP exploit sealed their fate. Maybe Windows is doing some of this in their kernel.
"At this point, we noticed that Serv-U.dll and RhinoNET.dll both have ASLR support disabled, making them prime locations for ROP gadgets..."
This is because the ROP gadget hardening OpenBSD does is generally considered to be mostly useless by other kernels, which are moving to hardware-enforced control flow integrity instead.
The issue is largely that the OpenBSD authors don’t really seem to understand how actual ROP chains work, so their mitigations are tailored towards things that don’t actually match reality. For example, trapsled is meant to protect against sloppy/partial overwrites writes that take advantage of nops to slide to where attackers actually want to go. The problem is that using nop slides in a ROP chain is Not A Thing. People don’t do it, it’s literally a thing they thought sounds like a reasonable attack but it actually protects against nothing in the real world. Similarly, gadget removal is cool and all but their understanding of how well it works is very flawed. Their analysis relies on really dumb, off-the-shelf gadget finders for one. But more critically, they claim something like 90% gadget reduction as if this actually protects against a significant majority of attacks. It does not. Exploit developers are usually not constrained by the gadgets they have available to them. If there are a thousand gadgets and you reduce it to hundred, yes you probably killed a lot of good gadgets…but in the remaining ones there’s usually enough to still perform whatever you want, maybe with a little more difficulty.
These mitigations don’t really harm anything so they’re not bad, per se, but they’re definitely not particularly impactful when to comes to security so nobody else really thinks it’s worth implementing them. There’s a lot of stuff like this in OpenBSD, but to be fair a lot of other projects are also bad at making mitigations that aren’t very useful. Reading writeups on CTFtime or, heaven forbid, imagining what exploits would look like is sadly too common.
ARM for instance has pointer authentication built into its hardware since version 8.3 of the ISA. While it's not the same implementation as OpenBSD, it has the same practical effect of making sure the return address is valid.
Right. So, how does that render OpenBSD's useless? Maybe I'm not being clear about what I'm asking here. I'm not comparing software vs hardware CFI. OpenBSD doesn't have support for PAC or CET (That I know of) but that doesn't mean they won't in the future. Should there be no software CFI then? Perhaps they would eventually complement each other?
And "This security tech usually..." is a bit too modest. Just one example - OpenBSD is the origin of OpenSSH, which has been rather widely used for a decade or two now.
OpenBSD does great work, but for the record OpenSSH started as a fork of the free SSH program developed by Tatu Ylönen; later versions of Ylönen's SSH were proprietary software offered by SSH Communications Security.
i just read that page[1] earlier this week. this gives a great view on the amount of work performed by the openssh project after the fork of the last open source version.
Almost every donation and purchase I have made would have been far more lucrative to the recipient had they kept it in BTC. But yes, I understand this is salty HN.
Really? Their biggest donors are large and extraordinarily profitable tech enterprises. (Like Google, Microsoft, and Facebook)
And thus the success of BSD-style licensing is thrown into sharp relief. (Also the fact that you or I can go use it as much as we want for free and do whatever we want to it.)
The flip side of course is that improvements also come at the pace of volunteerism, and there are no boardroom meetings where a manager tells the BSD developers that they didn't type enough lines of code that week.
If a corp wants a feature improved or implemented if it doesn't exist, they will have to pay someone and then they have to decide whether they will contribute it. If they do not, then they have to maintain a fork themselves which requires ongoing resources.
At the end of the day, I don't think it really matters that much. The corps are providing a service not a software application. If they weren't using BSD, they'd be using something else.
As a more general question, is there a resource available that shows which memory protection schemes are available on various OSes? It'd be nice to see what the state of the art is on Linux, macOS, the BSDs, etc. in one place.
Different. These are enhanced mitigations to detect when a program is calling into the kernel from a weird state that should never happen in a well-formed program. It basically prevents certain types of exploits from successfully calling into the kernel.
*the new mitigation just makes user-mode memory completely immutable. You can never "upgrade" a page's permissions or change its contents after it's been marked as immutable.
I'm curious why Theo used a new syscall. Wouldn't it be sufficient to add a new MAP_IMMUTABLE flag to mmap which would "fix" the given range of pages' mappings and protections permanently? Can't call it MAP_FIXED sadly :-)
mmap explicitly is allowed to create new maps in place of existing mappings by just freeing the underlying mapping, in fact that's one use for MAP_FIXED in safely create circularly mapped buffers.
And using MAP_IMMUTABLE with MAP_ANONYMOUS wouldn't seemingly be possible. I would think mprotect() would work, though. Since the new call works more like minherit() under the hood, he decided to just duplicate that mechanism for this.
That’s always a good question to ask :) Seems like the claim is that it prevents exploits where you’d map libc executable and then spray code there. IMO if you have the ability to do this you usually have lost already but ¯\_(ツ)_/¯
I was just discussing this sort of thing with some colleagues. Because the stack frame for main contains a bunch of other stuff - environment variables, cli args, etc - it makes it unreliable to try to instrument Linux systems and collect that information. A process can change its name, args, env, at any time. That sort of information is really helpful for forensics.
Currently your only option is to pull data directly from kernel structures, which is fine, but most people aren't doing that.
And then of course there's other cool stuff like being able to 'lock' system calls to the system provided libc, which openbsd can do since they already only support their provided libc.
Unfortunately none of this is likely to get to Linux at any point because:
a) I bet some insane userland processes fuck with their stacks
b) Linux doesn't mandate any libc
edit: Seems like there's some "what is this" being asked. Here's my loose understand!
For starters, libc is the interface to the kernel. Very few programs make syscalls directly (except go programs i guess? lol) on Linux, but on openbsd it's just flat out not allowed. OpenBSD doesn't have the "GNU/Linux" dichotomy - it's all one package deal. As such, they get to mandate how userland calls into the kernel.
Of course, you don't have to listen to openbsd today, because you can just... make those system calls directly. Easy. And this is relevant for security because attackers will do something called ROP, which effectively "reuses" bits of your already-executable code to issue system calls (you can google "return to libc" or "ret2libc").
So, first thing's first, have the kernel actually ensure that the sycall is issued from the memory that libc is mapped into. Cool, solved sort of.
But what if the attacker overwrites that libc? Now you've verified that the call is coming from libc but you don't have integrity.
With immutability the kernel will now enforce that system calls come from libc and that the code can't be overwritten.
Of course, this can extend beyond libc, but I'm assuming that's the main point. This would presumably be a general system call that programs can use themselves to do all sorts of fun things.
edit: Can't respond to replies, HN rate limited me :) sorry. Sounds like I need to read up a bit more, this doesn't address the use case I'd had in mind sadly, but still very cool nonetheless.
> Because the stack frame for main contains a bunch of other stuff - environment variables, cli args, etc -
[…]
> a) I bet some insane userland processes fuck with their stacks
The new OpenBSD `mimmutable()` call, when applied on the stack, does absolutely nothing to prevent writing or reading the stack. It prevents changing the (virtual memory/pagetable) mapping itself, i.e. mapping in something else, removing the mapping, or changing the mapping attributes (primarily RWX).
Even if some userland process requires an executable stack, that would be indicated with ELF flags and set up by the loader - and then frozen in place.
Well it technically does if you make a new stack with mmap(MAP_STACK) (or simply round down the stack pointer by a page size) and remove the write access to the top of the original stack and mimmutable it.
Problem is that those "magic variables" are defined by ABI to be directly above main()'s stack frame… if you're throwing that out, you may equally well leave the stack alone and put the magic stuff in some new place :)
…
Actually, now that I think about it, I guess you could try to put the boundary between main() and the magic bits right on a page boundary, and then change attrs on the magic bits?
You mean _start()'s stack pointer. Libc can secure them and it's fine. It would probably break the ANSI C standard though if you can't modify argv and environ though. So Libc would need to copy them. But that limits the usefulness of the original tooling proposal unless the operating system has something like /proc/pid/maps that tells you the extent of the system provided stack. I don't think OpenBSD has that. Please prove me wrong since if it does, I'd love to know how.
> It would probably break the ANSI C standard though if you can't modify argv and environ though.
AFAIK it would also break setproctitle(), unless my memory is off that works by just writing over the argv space.
On the plus side, on Linux /proc/<pid>/environ is a snapshot created at the time of execve() and kept by the kernel, nothing other than execve() can change it (don't quote me on this, I'm only 90% sure.)
> On the plus side, on Linux /proc/<pid>/environ is a snapshot created at the time of execve() and kept by the kernel, nothing other than execve() can change it (don't quote me on this, I'm only 90% sure.)
prctl(PR_SET_MM_ENV_{START,END}) will change the contents of /proc/<pid>/environ.
From what it looks like in Linux's source code, twiddling the bits at the base of the stack will also cause the procfs file to change--it seems to read directly from the process's memory. Ditto for /cmdline and /auxv, it seems.
Yep, dug into this the other day and POC'd it to prove that (even without prctl, just direct writing) you can overwrite those values and that will be shown in proc.
Looking at the sibling post to yours, I'm assuming the "better" approach these days is to allocate a new buffer distinct from argv space and use PR_SET_MM_ARG_{START,END} to point the kernel there.
Ah, interesting, I'll have to dig into it more :) I've never used OpenBSD so I've just had to watch on the sidelines. Anywhere I can read more about this?
The msyscall() man page. The Cosmopolitan Libc source code. In the cosmoverse, we produce static executables that run on six operating systems. The only Libc we use is obviously our own. So when our executables get loaded on OpenBSD, we do the same thing OpenBSD Libc does, which is calling msyscall() so that only a privileged subset of the application is able to use SYSCALL. In our static binary world, that's basically any function that uses the `privileged` keyword. It makes things a little dicey if we want to dynamically link OpenBSD Libc since we have to pull out some aggressive hacks like Xed in that case. But overall it's fine.
> a) I bet some insane userland processes fuck with their stacks
What do you mean by "insane" here? In standard C you get char** argv, and the arguments live on the stack. I believe you are allowed to modify those cli arguments through the pointers, it is not undefined behavior.
I never called it undefined behavior. But you shouldn't modify that stack space directly, you should either use setenv via libc or you should use prctl.
> And this is relevant for security because attackers will do something called ROP, which effectively "reuses" bits of your already-executable code to issue system calls (you can google "return to libc" or "ret2libc").
You’ve just described why system call integrity is useless without strong CFI :)
There was some work done on XOM (eXecute-only-memory) for arm64, but on at least x86 there isn't a separate page table bit for just read permissions, so there's no way[0][1] to express R^X, PROT_EXEC without PROT_READ is not possible.
Amusingly the 80286 supported execute-only segments, but this was dropped from 32-bit x86.
[0] It is possible on Intel in VM guests using EPT (Extended Page Tables), mlarkin@ experimented with protecting the host kernel in a special VM, called "Underjack". AMD SVM supports nothing like this.
[1] The custom AMD APU SoC in Sony's PS5 console supports "xotext" via NDA'd extensions, but there's no public documentation. (If _anyone_ knows details, pls share)
... btw, PROT_WRITE-only mappings are also impossible on x86 as well, so PROT_WRITE implicitly means PROT_READ. Not that I'm aware of any valid reason anyone might want this.
> there's no way[0][1] to express R^X, PROT_EXEC without PROT_READ is not possible.
I'll also add a [2]:
[2] There's no way to do it in the page tables. But, if you have Protection Keys for Userspace (PKU), you can get it ... kinda. You can have a PROT_READ|PROT_EXEC mapping, assign it a pkey, then set PKEY_DISABLE_ACCESS in the PKRU register for that key. In fact, if you have a PKU CPU and you do an unadorned mmap(PROT_EXEC), the kernel will allocate you a pkey and do this under the covers FOR you. Anyone who can execute WRPKRU can easily undo this protection, but it's better than nothing.
Why do you expect removing PROT_READ to provide a security gain when running open source software compiled using reproducible builds? Just the offset into an executable memory mapping is enough to know the executable code to be found. Sure it's a bit annoying, but it wouldn't prevent attackers from using it as ROP gadget.
It is not think that improves security as an attacker does not need to read the executable code (remotely) to find the gadget or attack.
More precisely, it is unreliable to rely on the confidentiality of such static information to achieve security.