While news is spreading around today about an updated attack against the Linux kernel's BPF JIT that works around the random offset it applies to the start of the JITted code, I wanted to share our perspective on this, why everyone should have already seen this coming (as we did), and why these JIT spraying attacks have been irrelevant in grsecurity for many years and especially now with the release of RAP.

It was this latter post from 2012 that inspired me to develop GRKERNSEC_BPF_HARDEN, which I believe was published within a day of my reading of the post. My implementation involved using the well-known constant-blinding technique against immediate values used in the encoded instruction stream generated by the JIT. As JIT spray attacks rely on returning into the middle of JITted code where these attacker-controlled immediates are treated as short, yet legitimate instructions, randomizing the immediates themselves effectively kills off the attack (at some performance hit). I chose this defense because it's effective even in the case of an arbitrary read. Upstream chose a different approach of applying a random offset of breakpoint instructions to the start of a JITted BPF filter, and the updated attack speaks for itself. As mentioned before though, everyone should have already seen this coming given the weakened threat model used for upstream's mitigation.

Some time later, in an attempt to demonstrate defeating the defenses of a grsecurity kernel, Comex (aka Pinkie Pie) published a youtube video using an unpublished vulnerability and exploit. Though in his first attempt he forgot to enable important protections like UDEREF, he finally got it right on the second try and demonstrated a valid attack. Unfortunately for him, he left too many indicators in the output (the kernel warned about an invalid BPF instruction, but let execution continue) and I deduced the method he had used: by overwriting a BPF interpreter buffer, he had been able to achieve arbitrary read/write through the BPF interpreter -- effectively a data-only attack. I believe he also made the mistake of mentioning he used a stack overflow as part of the exploit.

What happened next was the hardening of the BPF interpreter in grsecurity to prevent such future abuse: the previously-abused arbitrary read/write from the interpreter was now restricted only to the interpreter buffer itself, and the previous warn on invalid BPF instructions was turned into a BUG() to terminate execution of the exploit. I also then developed GRKERNSEC_KSTACKOVERFLOW which killed off the stack overflow class of vulns on x64.

A short time later, there was work being done upstream to extend the use of BPF in the kernel. This new version was called eBPF and it came with a vastly expanded JIT. I immediately saw problems with this new version and noticed that it would be much more difficult to protect -- verification was being done against a writable buffer and then translated into another writable buffer in the extended BPF language. This new language allowed not just arbitrary read and write, but arbitrary function calling.

For posterity, here's the email I had sent to the eBPF JIT developer and Kees Cook back in 2014:

The way you are improving BPF is not secure (not that the original waseither, but you're making it much worse in a way that's much more difficultto correct). I've wasted several days on it already and have decided torevert the BPF code in the kernel back to that in the 3.14 stable kernelfor our users until you get your act in order.

First off, your entire threat model needs to be rethought. Random paddingat the beginning of the JIT code does nothing against an attacker withan arbitrary read vuln (the JIT buffer can be leaked). Much more importantly,there is *zero* protection against the BPF interpreter buffer from beingcorrupted at runtime. Since unprivileged users are allowed to createfilters, control the size of the filters, and these filters go into the genericslab caches, they are an easy target for heap overflows or arbitrary writes.This isn't some ivory tower theory, real exploits are *already* takingadvantage of this. I discovered it by seeing the WARN() about unknownopcodes in someone's published exploit video. Since that WARN() should neverbe hit, it really should be a BUG() that terminates the attacker's process.

You're only doing verification of the interpreter buffer at load time -- ifit's corrupted after that point, *everything* is possible, including arbitraryread, arbitrary write, and now arbitrary function calling as well.

Please please please before 3.17 is released, do *proper* validation of theinterpreter buffer. This involves generating a final representationand marking the underlying allocation read-only, *then* performing thevalidation. I highly doubt you will implement any real JIT defense likeconstant blinding as clearly performance is more important than any semblanceof security, but at bare minimum the interpreter buffer needs to be securedagainst runtime corruption.

-Brad

Though they somewhat took one of my recommendations and made the interpreter buffer read-only in a later kernel, I'm still not happy with the new BPF implementation and particularly with exposing it at this early a stage to unprivileged users (it is still restricted to privileged users in grsecurity). Opening it up to unprivileged users actually exposed an exploitable vulnerability just recently: http://git.kernel.org/cgit/linux/kernel/git/davem/net.git/commit/?id=8358b02bf67d3a5d8a825070e1aa73f25fb2e4c7 which grsecurity was not vulnerable to.

With the release of RAP, the fear of JIT spraying goes away completely. It may not be immediately obvious, but it is for the same reason that unaligned instructions are no longer a threat, as described in the FAQ I wrote up last weekend: https://grsecurity.net/rap_faq.php. Since RAP ensures call sites can only transition to valid functions, and functions can only return to valid call sites, it's no longer possible to redirect execution to the middle of a JITted instruction stream, provided that the JIT doesn't allow attacker-controlled 64-bit immediates.

Keep watching this space for more in-depth discussions of current events that relate to grsecurity. Hopefully you find it more useful than 140 characters.