Linux Kernel BPF JIT Spraying

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.

For the curious, the updated attack has been published at https://github.com/01org/jit-spray-poc-for-ksp and is based off the initial pre-random-offset BPF JIT attack published here: http://mainisusuallyafunction.blogspot. … -with.html .

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:

Date: Sun, 31 Aug 2014 12:45:16 -0400

From spender@grsecurity.net Sun Aug 31 12:45:16 2014

From: Brad Spengler <spender@grsecurity.net>

To: ast@plumgrid.com

Cc: pageexec@freemail.hu , keescook@google.com

Subject: BPF security

[– PGP output follows (current time: Tue May 3 08:33:12 2016) –]

gpg: Signature made Sun Aug 31 12:45:09 2014 EDT using RSA key ID 2525FE49

gpg: Good signature from "Bradley Spengler (spender) <spender@grsecurity.net>"

[– End of PGP output –]

[– The following data is signed –]


The way you are improving BPF is not secure (not that the original was

either, but you’re making it much worse in a way that’s much more difficult

to correct). I’ve wasted several days on it already and have decided to

revert the BPF code in the kernel back to that in the 3.14 stable kernel

for our users until you get your act in order.

First off, your entire threat model needs to be rethought. Random padding

at the beginning of the JIT code does nothing against an attacker with

an arbitrary read vuln (the JIT buffer can be leaked). Much more importantly,

there is *zero* protection against the BPF interpreter buffer from being

corrupted at runtime. Since unprivileged users are allowed to create

filters, control the size of the filters, and these filters go into the generic

slab caches, they are an easy target for heap overflows or arbitrary writes.

This isn’t some ivory tower theory, real exploits are *already* taking

advantage of this. I discovered it by seeing the WARN() about unknown

opcodes in someone’s published exploit video. Since that WARN() should never

be 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 — if

it’s corrupted after that point, *everything* is possible, including arbitrary

read, arbitrary write, and now arbitrary function calling as well.

Please please please before 3.17 is released, do *proper* validation of the

interpreter buffer. This involves generating a final representation

and marking the underlying allocation read-only, *then* performing the

validation. I highly doubt you will implement any real JIT defense like

constant blinding as clearly performance is more important than any semblance

of security, but at bare minimum the interpreter buffer needs to be secured

against runtime corruption.


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.



转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Linux Kernel BPF JIT Spraying

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址