Thursday, August 14, 2014

BadUSB, VT-d and Compartmentalization

This post stems from a "conversation" (for lack of better term) on twitter with Joanna Rutkowska about BadUSB and Qubes. One of her points was that IOMMU/VT-d and TXT are the way to defend from BadUSB. I'm extremely bad at jamming good replies into 140 characters, so I promised some extra reasoning on that. Having been called upon the promise, here is a blog post.

Incidentally, turns out I'm also bad at blogs.

"Orthogonal or not, VT-d is the way how you protect against these"


A little context. The discussion is about BadUSB, the recent BlackHat talk that got lots of press. You can see the slides here - I won't go in much details into it. The Reader's Digest is basically that an untrusted firmware is, well, untrusted, and may act on you. In the USB case, the classic example (which has been carried over through other projects in the past, e.g. with Teensy) is a USB stick that advertises itself also as a HID device (e.g. a keyboard) and as such behaves under the cover. Similarly a keyboard might silently sniff/tamper with your data.

It's perhaps worth pointing out that firmware attacks are by no means limited to USB devices: pretty much any modern device has a (more or less) flashable firmware that can be (ab)used. The reason why not many rootkits of this type are found publicly is mostly linked to the fact that attackers usually go for the easiest route AND that organized attackers don't want to lose valuable IP (Intellectual Property). If the machine you've just rooted doesn't control binaries integrity, a sshd backdoor will do wonders. If you're not planning to use the machine you just rooted any further than a bounce/stage for another attack, there is no point in risking to lose a sophisticated rootkit.

Enter IOMMU. The may idea of an IOMMU is to prevent a device from seing address space it has no business in seeing. With IOMMU properly configured, your network card won't be able to use DMA on the PCI bus to access arbitrary kernel memory. That's great. Extending this concept to Virtual Machines, the host can assign individual devices/controllers in passthrough to guests, without having to worry for one of such guests to abuse the device/controller to read/write host memory.

In case of USB, one can certainly confine the USB controller (and "be creative"), preventing a certain range of attacks, but in the case of a device acting on you, the moment the user needs to use the device, all bets are off. And it's by no means IOMMU fault, at all. IOMMU is doing its job, it's simply not the solution to the problem.

In fact the right approach doesn't even lie on the OS side too much, but rather builds on the fairly logical observation that firmware is just software and as such needs to be treated. In other words, signing and verifying (in trusted chains, from bootstrap to use) the firmware of devices needs to become a much more standard practice. This is where any form of Secure/Trusted/Verified Boot is/should be headed.

"Signing and verifying is NOT a solution. I believe only compartmentalization is"
The first part is easy to agree on: there is not a single solution in security. Defense is hard (which is why it's absolutely entertaining) and comes from multiple sides. Signing and verifying are there to defeat "persistence", compartmentalization is a form of "damage control" and anti-exploitation/analysis techniques are there to prevent compromise in the first place. A secure system has something to gain from each one of those.

Let's see compartmentalization in more details, since it's where the discussion shifted. To understand the pros and cons of compartmentalization, look at it this way: if the applications you use have 10 exploitable bugs, compartmentalization will leave all the 10 bugs exploitable. In other words, compartmentalization won't reduce the attack surface, but will rather aim at making a successful attack pointless for an attacker.

You surf the Web and go on both a malware filled website and your banking account. The compartmentalization approach is to separate those two instances so that having you exploited by the malware website has no advantages for the attacker: he will find himself into a boring, transient, read-only environment that doesn't have any valuable data. Your banking session is safe and sound in another compartment, which is completely separated from the compromised one.

This is cool and it's certainly useful. But it's not the only side to the story. In fact, in OS development, we are always faced with another key point: usability. Users want to permanently store data and access it easily. They want to pass some of this data from one entity to another. They want to do that without having to largely rethink their usage model. And they want to do it safely.

Attackers, on the other end, more often than not are not after persistence or full compromise of the machine: they are after data. If your application contains sensitive data and needs to parse some external input, and this external input can exploit it, then compartmentalization won't do anything for you. The attacker will exploit the application and read up to the last bit of your precious data. Expecting any single third party out there to rethink their development model is unrealistic (is like expecting everybody to move to a safe language) and there's no hard evidence that the compartmentalization model can be a drop-in in every scenario.

So, are we screwed or we can do better?

Enter defense. Enter pro-active security.

Unless you've been living on the moon in the past 10 years, you may have heard of the great work brought on day in day out by PaX and Grsecurity (and is now taken on by various operating systems). The idea is to hamper attackers' techniques (or completely eradicate bug classes) so that vulnerable programs are no longer exploitable. It's a game of costs, ultimately: if it takes months and only certain very specific conditions/bugs to be able to successfully exploit a system, then defense brings home a win.

If you have been writing exploits for a while, try remembering how was it when there was no randomization, abundance of writable and executable mappings, writable sensitive structures (e.g. GOT), userspace dereference from kernel land and so forth. Life was great, wasn't it?

Then anti-exploitation techniques came along.

Are they the only solution? Again, no. They aren't.

Pro-active defense is just one part of the story, a certainly crucial one, but still one part of the story. Anti-exploitation techniques benefit from the research in eradicating/detecting bugs at compile time (e.g. static analysis) or execution time (dynamic analysis). They benefit from compartmentalization in making moot those cases where it's hard to raise defenses (e.g. architecture design/logical bugs) and indeed benefit from a signed and verified chain of trust.

In other words, every layer matters. Losing track of this means either constraining our view of the threat model to fit our world (rather than the world our users live in) or leaving a window open while we're all focused on hammering nails and locks on the front door.