Wednesday, March 28, 2012

vsftpd-3.0.0-pre1 and seccomp filter

For the brave, there now exists a pre-release version of vsftpd-3.0.0:

https://security.appspot.com/downloads/vsftpd-3.0.0-pre1.tar.gz

https://security.appspot.com/downloads/vsftpd-3.0.0-pre1.tar.gz.asc

The most significant change is an initial implementation of a secondary sandbox based on seccomp filter, as recently merged to Ubuntu 12.04. This secondary sandbox is pretty powerful, but I'll go into more details in a subsequent post.

For now, suffice to say I'm interested in testing of this new build, e.g.
  • Does it compile for you? (I've added various new gcc flags etc).

  • Any runtime regressions?

  • Does it run ok on 64-bit Ubuntu 12.04-beta2 or newer?

This last question is key as that is the configuration that will automatically use a seccomp filter. The astute among you will note that beta2 is not due out until tomorrow, but an apt-get dist-upgrade from beta1 will pull in the kernel that you need.

Will Drewry's excellent work on seccomp filter is the most exciting Linux security feature in a long time and the eventual vsftpd combined sandbox that will result should be a very tough nut to crack indeed.

Thursday, March 22, 2012

On the failings of Pwn2Own 2012

This year's Pwn2Own and Pwnium contests were interesting for many reasons. If you look at the results closely, there are many interesting observations and conclusions to be made.

$60k is more than enough to encourage disclosure of full exploits

As evidenced by the Pwnium results, $60k is certainly enough to motivate researchers into disclosing full exploits, including sandbox escapes or bypasses.

There was some minor controversy on this point leading up to the competitions, culminating in this post from ZDI. The post unfortunately was a little strong in its statements including "In fact, we don't believe that even the entirety of the $105,000 we are offering would be considered an acceptable bounty", "for the $60,000 they are offering, it is incredibly unlikely that anyone will participate" and "such an exploit against Chrome will never see the light of day at CanSecWest". At least we all now have data; I don't expect ZDI to make this mistake again. Without data, it's an understandable mistake to have made.

Bad actors will find loopholes and punk you

One of the stated -- and laudable -- goals of both Pwn2Own and Pwnium is to make users safer by getting bugs fixed. As recently noted by the EFF, there are some who are not interested in getting bugs fixed. At face value, it would seem to be counterproductive for these greyhat or blackhat parties to participate.

Enter VUPEN, who somehow managed to turn up and get the best of all worlds: $60k, tons of free publicity for their dubious business model and... minimal cost. To explore the minimal cost, let's look at one of the bugs they used: a Flash bug (not Chrome as widely reported), present in Flash 11.1 but already fixed in Flash 11.2. In other words, the bug they used already had a fixed lifetime. Using such a bug enabled them to collect a large prize whilst only handing over a doomed asset in return.

Although operating within the rules, their entry did not do much to advance user security and safety -- the bug fix was already in the pipeline to users. They did however punk $60k out of Pwn2Own and turned the whole contest into a VUPEN marketing spree.

Game theory

At the last minute at Pwn2Own, contestants Vincenzo and Willem swooped in with a Firefox exploit to collect a $30k second place prize. The timing suggests that they were waiting to see if their single 0-day would net them a prize or not. It did. We'll never know what they would have done if the $30k reward was already sewn up by someone else, but one possibility is a non-disclosure -- which wouldn't help make anyone safer.

Fixing future contests

The data collected suggests some possible structure to future contests to ensure they bring maximal benefit to user safety:
  • Require full exploits, including sandbox escapes or bypasses.

  • Do not pay out for bugs already fixed in development releases or repositories.

  • Have a fixed reward value per exploit.

Saturday, March 17, 2012

Some random observations on Linux ASLR

I've had cause to be staring at memory maps recently across a variety of systems. No surprise then that some suboptimal or at least interesting ASLR quirks have come to light.

1) Partial failure of ASLR on 32-bit Fedora

My Fedora is a couple of releases behind, so no idea if it's been fixed. It seems that the desire to pack all the shared libraries into virtual address 0x00nnnnnn has a catastrophic failure mode when there are too many libraries: something always ends up at 0x00110000. You can see it with repeated invocations of ldd /opt/google/chrome/chrome|grep 0x0011:

libglib-2.0.so.0 => /lib/libglib-2.0.so.0 (0x00110000)
libXext.so.6 => /usr/lib/libXext.so.6 (0x00110000)
libdl.so.2 => /lib/libdl.so.2 (0x00110000)

Which exact library is placed at the fixed address is random. However, any fixed address can be a real problem to ASLR. For example, in the browser context, take a bug such as Chris Rohlf's older but interesting CSS type confusion. Without a fixed address, a crash is a likely outcome. With a fixed address, the exact library mapped at the fixed address could easily be fingerprinted, and the BSS section read to leak heap pointers (e.g. via singleton patterns). Bye bye to both NX and ASLR.

Aside: in the 32-bit browser context with plenty of physical memory, a Javascript-based heap spray could easily fill most of the address space such that the attacker's deference has a low chance of failure.

Aside #2: my guess is that this scheme is designed to prevent a return-to-glibc attack vs. strcpy(), by making sure that all executable addresses contain a NULL byte. I'm probably missing something, but it seems like the fact that strcpy() NULL-terminates, combined with the little-endianness of Intel, makes this not so strong.


2) Missed opportunity to use more entropy on 64-bit

If you look at the maps of a 64-bit process, you'll see most virtual memory areas correspond to the formula 0x7fnnxxxxxxxx where all your stuff is piled together in xxxxxxxx and nn is random. At least, nothing is in or near a predictable location. One way to look at how this could be better is this: If you emit a 4GB heap spray, you have a ~1/256 chance of guessing where it is. Using the additional 7 bits of entropy might be useful, especially for the heap.


3) Bad mmap() randomization

Although the stack, heap and binary are placed at reasonably random locations, unhinted mmap() chunks are sort of just piled up adjacent, typically in a descending-vm-address fashion. This can lead to problems where a buffer overflow crashes into a sensitive mapping -- such a JIT mapping. (This is one reason JIT mappings have their own randomizing allocator in v8).


4) Heap / stack collision likely with ASLR binary

On a 32-bit kernel you might see:

b8105000-b8124000 rw-p 00000000 00:00 0 [heap]
bfae5000-bfb0a000 rw-p 00000000 00:00 0 [stack]

Or on a 64-bit kernel running a 32-bit process:

f7c52000-f7c73000 rw-p 00000000 00:00 0 [heap]
ff948000-ff96d000 rw-p 00000000 00:00 0 [stack]

In both cases, the heap doesn't have to grow too large before it cannot grow any larger. When this happens, most heap implementations fall back to mmap() allocations, and suffer the problems of 3) above. These things chained together with a very minor infoleak such as my cross-browser XSLT heap address leak could in fact leak the location of the executable, leading to a full NX/ASLR bypass.


Conclusion

A 32-bit address space just isn't very big any more, compared with todays large binaries, large number of shared library dependencies and large heaps. It's no surprise that everything is looking a little crammed in. The good news is that there are no obvious and severe problems with the 64-bit situation, although the full entropy isn't used. Applications (such as v8 / Chromium) can and do fix that situation for the most sensitive mappings themselves.

Wednesday, February 29, 2012

Chrome Linux 64-bit and Pepper Flash

Flash on Linux hasn't always been the best experience in the stability and security departments. Users of 64-bit Linux, in particular, have to put up with NSPluginWrapper, a technology which bridges a 64-bit browser process to the 32-bit Flash library.

In terms of sandboxing, your distribution might slap a clunky SELinux or AppArmor policy on Flash, but it may or may not be on by default.

Given the above, and the fact I'm a 64-bit Linux user, I was really happy to see Chrome's latest dev channel include a native 64-bit Pepper Flash plug-in. What does this mean?
  • Security: sandboxing. Pepper plug-ins run inside Chrome's renderer sandbox. On Linux, this is chroot() and PID namespace based, so Flash in this context has no filesystem access, nor the ability to interfere with other processes.

  • Stability: native 64-bit build. Generally, stability and performance should be better than NSPluginWrapper on account of not having to bounce through an extra layer and process.

  • Security: 64-bit address space. It's harder to heap spray or JIT spray a 64-bit address space. Physical memory will typically run out long before the spray achieves a statistical likelihood of being at any particular memory location.

There are some warts of course. Although it works ok on my Ubuntu box, there are lots of comments on the releases blog which indicate Flash is broken, particularly from Fedora users. There's also an ASLR failure (missing position independent executable) which will be fixed in the next revision.

Overall, though, seems like a promising boost to Linux Flash security is heading towards the Chrome stable channel.

Saturday, January 28, 2012

The dirty secret of browser security #1

Here's a curiousity that's developing in modern browser security: The security of a given browser is dominated by how much effort it puts into other peoples' problems.

This may sound absurd at first but we're heading towards a world where the main browsers will have (with a few notable exceptions):
  • Rapid autoupdate to fix security issues.

  • Some form of sandboxing.

  • A long history of fuzzing and security research.
These factors, combined with an ever more balanced distribution of browser usage, are making it uneconomical for mass malware to go after the browsers themselves.

Enter plug-ins

Plug-ins are an attractive target because some of them have drastically more market share than even the most popular browser. And a lot of plug-ins haven't received the same security attention that browsers have over the past years.

The traditional view in security is to look after your own house and let others look after theirs. But is this conscionable in a world where -- as a browser vendor -- you have the power to defend users from other peoples' bugs?

As a robust illustrative point, a lot of security professionals recently noticed some interesting exploit kit data, showing a big difference in exploitation success between Chrome (~0%) and IE / Firefox (~15%).

The particular exploits successfully targeted are largely old, fixed plug-in bugs in Java, Flash and Reader. So why the big difference between browsers?

The answer is largely the investment Chrome's security team has made in defending against other peoples' problems, with initiatives such as:
  • Blocking out-of-date plug-ins by default and encouraging the user to update.

  • Blocking lesser-used plug-ins (such as Java, RealPlayer, Shockwave etc). by default.

  • Having the Flash plug-in bundled such that it is autoupdated using Chrome's fast autoupdate strategy (this is why Chrome probably has the best Flash security story).

  • The inclusion of a lightweight and reasonably sandboxed default PDF viewer (not all sandboxes are created equal!)

  • The Open Type Sanitizer, which defends against a subset of Windows kernel bugs and Freetype bugs. Chrome often autoupdates OTS faster than e.g. Microsoft / Apple / Linux vendors fix the underlying bug.

  • Certificate public key pinning. This new technology defends against the generally gnarly SSL Certificate Authority problem, and caught a serious CA compromise being abused in Iran last year.
In conclusion, some of the biggest browser security wins over the past couple of years have come from browser vendors defending against other peoples' problems. So I repeat the hypothesis:

The security of a given browser is dominated by how much effort it puts into other peoples' problems

Funny world we live in.

Sunday, July 3, 2011

Alert: vsftpd download backdoored

[With thanks to Mathias Kresin for being the first to notice]

An incident, what fun! Earlier today, I was alerted that a vsftpd download from the master site (vsftpd-2.3.4.tar.gz) appeared to contain a backdoor:

http://pastebin.com/AetT9sS5

The bad tarball is (sha256sum):

2a4bb16562e0d594c37b4dd3b426cb012aa8457151d4718a5abd226cef9be3a5 vsftpd-2.3.4.tar.gz

And, of course, the GPG signature notices:

$ gpg ./vsftpd-2.3.4.tar.gz.asc
gpg: Signature made Tue 15 Feb 2011 02:38:11 PM PST using DSA key ID 3C0E751C
gpg: BAD signature from "Chris Evans <chris@scary.beasts.org>"

Check your signatures :)

Ideally, you'll see something like:

gpg: Signature made Tue 15 Feb 2011 02:38:11 PM PST using DSA key ID 3C0E751C
gpg: Good signature from "Chris Evans <chris@scary.beasts.org>"
Primary key fingerprint: 8660 FD32 91B1 84CD BC2F 6418 AA62 EC46 3C0E 751C


Signatures aside, I also took the liberty of moving most of the vsftpd site and latest download to a hosting provider I have more faith in:

https://security.appspot.com/vsftpd.html
https://security.appspot.com/downloads/vsftpd-2.3.4.tar.gz
https://security.appspot.com/downloads/vsftpd-2.3.4.tar.gz.asc

The backdoor payload is interesting. In response to a :) smiley face in the FTP username, a TCP callback shell is attempted. There is no obfuscation. More interestingly, there's no attempt to broadcast any notification of installation of the bad package. So it's unclear how victims would be identified; and also pretty much guaranteed that any major redistributor would notice the badness. Therefore, perhaps someone was just having some lulz instead of seriously trying to cause trouble.

Friday, May 27, 2011

libxml vulnerability and interesting integer issues

A while ago, I was playing with grammar-based XPath fuzzing and I found and fixed an interesting libxml bug. The commit, for the curious, is here:

http://git.gnome.org/browse/libxml2/commit/?id=d7958b21e7f8c447a26bb2436f08402b2c308be4

The trigger for this bug was the XPath expression: //@*/preceding::node()/ancestor::node()/ancestor::foo['foo'] which for some reason I haven't yet analyzed leads to a pathologically large collection of nodes within libxml.

As the nodeset is grown and grown, things get interesting when the system runs out of memory whilst trying to double the size of the nodeset:

cur->nodeMax *= 2;
temp = (xmlNodePtr *) xmlRealloc(cur->nodeTab, cur->nodeMax *
sizeof(xmlNodePtr));
if (temp == NULL) {
xmlXPathErrMemory(NULL, "growing nodeset\n");
return;
}
cur->nodeTab = temp;

Notice how the max number of allocated nodes in the "cur" structure is doubled before doing, checking and assigning the reallocation. This means that in the event of a realloc() failure, one of two things will happen:
  • If you malloc() implementation exits the process upon alloc failure (such as Chromium), lucky you! You dodged a bullet.

  • More typically, you'll exit this function with "cur" in an inconsistent state, i.e. it indicates it can hold more data than it really can.
Despite the call to xmlXPathErrMemory(), XPath processing continues with "cur" in an inconsistent state, leading to a heap-based buffer overflow.

The fix is to only update the structure's "max size" member after a successful expansion of the underlying array. libxml already did this in most places, using a paradigm such as:

temp = (xmlNodePtr *) xmlRealloc(cur->nodeTab, cur->nodeMax * 2 *
sizeof(xmlNodePtr));
if (temp == NULL) {
xmlXPathErrMemory(NULL, "growing nodeset\n");
return;
}
cur->nodeMax *= 2;
cur->nodeTab = temp;

... which leads us nicely into part 2:

Interesting integer issues

Even with the fix applied, there are some really interesting integer issues going on here. The astute will have noticed a possible integer overflow in the argument to xmlRealloc(), which is a general hazard with the common pattern of "double the size of the array if we ran out of room". Digging in to more detail, we see a fascinating difference in behaviour between 32-bit and 64-bit builds and maybe even learn a thing or two about integer promotion rules.

On 32-bit builds

First, let's quickly note that cur->nodeMax is of type int. That's likely the wrong type for a couple of reasons, but we will run with it for our analysis.
On 32-bit, sizeof(int) == sizeof(size_t) so we're looking at a fairly basic possible integer overflow. But can it ever be triggered? Does the 32-bit address space offer enough room?
The case with the least space requirements for the 32-bit address space is the case where we already have a 2GB (2^31) allocation and are attempting to double it -- leading to an integer overflow at 2^32 and an attempt to realloc() to 0 bytes.
But to arrive at the 2^31 allocation, we need to have a 1GB -> 2GB realloc() succeed first.
This is actually unlikely on 32-bit Linux -- which typically has just the lower 3GB of address space usable. 1GB + 2GB + program text etc. won't simultaneously fit, so the only way 1GB -> 2GB realloc() will succeed is if the allocation can be expanded in-place. glibc malloc() will typically use mmap() for large allocations, and put them towards the top of address space in order to reserve room for standard heap expansion. So realloc() on such an mmap()ed chunk typically won't have room to mremap() to a larger size.
All in all, this seems to be a hard-to-exploit bug, unless the attacker has a lot of control over other allocations (to influence the placement of the 1GB mmap() lower down in the address space and then free() whatever chunks were needed to do that, before the realloc()). Further research might be merited for other allocators (e.g. tcmalloc) and 32-bit-process-on-64-bit-host (>3GB address space available).

On 64-bit builds

With 64-bit, we have a much larger address space. This means that we should be able to successfully allocate the growing array -- and the objects contained within it -- right up until the fault condition.

The fault condition is that cur->nodeMax * 2 will eventually become negative. This negative int is then multiplied by sizeof(xmlNodePtr). Remember that sizeof() returns a size_t, so we have an int -> size_t promotion at this point. This will result in sign-extension to a 64-bit size, which will end up massive and the system allocator will not be able to satisfy an allocation of that size. Therefore, a lucky lack of impact on 64-bit.

Finally, note that there's a real subtlety in the ordering of the expression given to xmlMalloc():

cur->nodeMax * 2 * sizeof(xmlNodePtr)

Things would be very different if we had:

cur->nodeMax * sizeof(xmlNodePtr) * 2

Promotions for left-to-right operator sequences are done in strict left-to-right ordering, as opposed to some kind of overall max(precedence) promotion. Therefore, in this latter case, the initial negative-related failure will be avoided; we're looking at:

(int) 2^30 -> (int) -(2^31) -> (size_t) 0xffffffff80000000 * 8 == (size_t) 0xfffffffc00000000
vs.
(int) 2^30 -> (size_t) 2^30 * 8 -> (size_t) 2^30 * 8 * 2 == (size_t) 2^34

Seeing that nodeNr is just an int, we would likely see subsequent memory corruption if nodeNr goes subsequently negative after its increment in a statement such as cur->nodeTab[cur->nodeNr++] = xmlXPathNodeSetDupNs(node, ns);