Overview
A confluence of two risky design choices, combined with various implementation issues, makes drive-by downloads possible with Google Chrome on Fedora. In total, with the risky design choices first, the issues are:
- Chrome will auto download files to a user’s desktop with no confirmation.
- Fedora’s “tracker” software will auto crawl downloaded files to index them, including media files.
- The “gstreamer” framework, as used to handle media in the Fedora desktop, has questionable implementation quality from a security perspective.
- The “tracker” component responsible for parsing media files does not appear to be sandboxed (e.g. with SELinux).
- The Fedora default desktop install includes a range of fairly obscure media decoders that confer risk but are not necessary for a thorough desktop experience.
Demonstration and proof of concept
In the screenshot below, the user of the Chrome browser has visited a (malicious) website and you can see the result is a forced download leading to the side effect of a crash in a process unrelated to the web browser:
“We’re sorry, it looks like the Tracker Metadata Extractor crashed.” It certainly did, without the user needed to do anything other that visit a web page. It is not necessary to click on the button corresponding to the download.
This is in a default install of Fedora 24, but with the gstreamer updates applied (without them, gstreamer doesn’t work correctly). The crashing binary is /usr/libexec/tracker-extract. It has ASLR enabled, which is good. But ps -eZ identifies the security context as unconfined_u:unconfined_r:unconfined_t, which appears unsandboxed. The crash manifests as an out-of-bounds write.
This vulnerability also affects the default install of Ubuntu 16.04, as long as the “mp3” option was selected during install. (This vulnerability appears to go significantly back into history, at least affecting all the way back to Ubuntu 12.04 and it’s older gstreamer-0.10.) Ubuntu doesn’t seem to index desktop files by default though, so the impact on Ubuntu will be less severe but still nasty via e.g. triggering thumbnailing by opening the nautilus file browser in the Downloads directory. Or opening a USB drive. Or e-mailing someone the exploit file and have them open it in a media player.
0day details
(Absent a CVE, you can uniquely identify this as CESA-2016-0002.)
The vmnc decoder renders video for a VMware screen capture format. This format is usually contained within an AVI container. The vmnc decoder in the gstreamer code base contains a fairly obvious and simple width * height * depth integer overflow in the allocation of the render buffer. From gst-plugins-bad1.0/gst/vmnc/vmncdec.c:
static int
vmnc_handle_wmvi_rectangle (GstVMncDec * dec, struct RfbRectangle *rect,
const guint8 * data, int len, gboolean decode)
{
…
vmnc_handle_wmvi_rectangle (GstVMncDec * dec, struct RfbRectangle *rect,
const guint8 * data, int len, gboolean decode)
{
…
bpp = data[0];
…
dec->format.bytes_per_pixel = bpp / 8;
dec->format.width = rect->width;
dec->format.height = rect->height;
…
dec->format.width = rect->width;
dec->format.height = rect->height;
…
dec->imagedata = g_malloc (dec->format.width * dec->format.height *
dec->format.bytes_per_pixel);
…
dec->format.bytes_per_pixel);
…
In the above code, rect->width and rect->height are attacker controlled 16-bit unsigned values taken straight from the input file. bpp is also taken from the input file, and is validated to be one of the typical depth values 8, 16, 32.
To create the PoC, an existing sample file was located from the excellent MPlayerHQ test set: test.avi. This is a vmnc-inside-avi sample file. All that is changed in the PoC is that the width and height values at file offset 0x201c are changed to 65535 and 32769, respectively. The base image is a 16bpp image, so the calculation on the overflow line is 65535 * 32769 * 2. In signed 32-bit integer math, the result is 65534. That’s a reasonable sized buffer but subsequent decoding believes that it has a huge 65535 x 32769 canvas to render into. Heap based buffer overflow results.
Exploitability
Yes.
To be a bit more specific, there are a couple of challenges which would make this exploit tricky: firstly, this is 64-bit modern Linux (and in particular, Fedora), where the ASLR is generally pretty good. Secondly, the attacker does not have the luxury of attacking this vulnerability from a scripting environment. This is key, and makes things much harder, because pointers cannot be readily calculated based on information leaks, decisions cannot be made based on measurements of corruption side effects, etc.
However, on the flipside, there are a lot of ways in which the attacker has a lot of control and assistance to exploit this, even without script to help. Let’s enumerate them, we might find the range of possibilities surprising:
- Precise control over heap chunk size that is overflowed. By fiddling with the width, height and bpp values that factor into the integer overflow, we can choose a wide range of different chunk sizes to later go off the end of. This means we can corrupt a lot of different areas of the heap depending on what works best.
- Surprisingly stable heap layout. The vmnc decoding kicks off on a relatively fresh thread, which typically has a relatively fresh glibc per-thread malloc arena. Heap grooming may only be minimally required, if at all.
- Possibility for 3 byte partial pointer overwrites. Because the overflow is kicking off inside a glibc thread area, the alignment of the arena is typically to 16MB or so. This means you can potentially do up to a 3 byte partial pointer overwrite reliably. In the world of partial pointer overwrites, this is a luxury.
- Excellent heap grooming opportunities inside the vmnc decode loop. Inside the main decode loop, there are lots of opportunities to allocate and free memory, with great control over the sizes. This occurs in vmnc_handle_wmvi_rectangle(), which can reallocate the existing decode buffer, and vmnc_handle_wmvd_rectangle(), which allocates and frees cursor buffers. This level of control will allow good heap grooming, and the possibility of attacking heap metadata, if no other good opportunities present themselves.
- A copy primitive as one of the rendering modes. This is super big. vmnc_handle_copy_rectangle() is a rendering mode that will take pixels from one area of the image and copy them to another area of the image. Since our integer overflow leads to a small image buffer but a huge image canvas size, you can see that this will enable us to read some out-of-bounds content and write that out-of-bounds. This solves one of the hardest problems we might face, which is to synthesize valid pointers in the presence of good ASLR. We can read an existing valid pointer that is out-of-bounds, and then copy it somewhere else, also out-of-bounds.
To back up a couple of the above points, we can briefly look at the PoC crashing in gdb. We’ll run the PoC through the totem media player on both Ubuntu 16.04 and Fedora 24:
gdb totem
r vmnc_width_height_int_oflow.avi
[SEGV]
(gdb) bt
#0 __memcpy_avx_unaligned ()
…
…
(gdb) i r
...
rsi 0x7fffbb5e0015 140736336887829
rdi 0x7fffa40237fe 140735944996862
...
rdi 0x7fffa40237fe 140735944996862
...
The last three bytes of the rdi register at the time of the crash are reasonably consistent across both Ubuntu and Fedora, at 0x0237fe. This is just off the end of a glibc thread arena, which ends at 0x022000. This illustrates the fairly clean heap state, and the 16MB+ alignment of thread arenas.
It’s clear that writing this exploit would be a lot of fun. I’ll put it on my TODO list, but I’d love to see someone else pick it up.
Google Chrome’s culpability
Let’s be clear: Google Chrome is the most secure of the common general purpose browsers out there. It has had more effort and money put into sandboxing, code quality, mitigations, fuzzing and community security involvement than the other browsers. This is all backed by a large, strong security team -- the largest monetary investment of the major browser vendors, which speaks volumes.
However, the default download behavior is one where you can point to e.g. Firefox’s solution as demonstrably superior: the user has to accept any random attacker supplied bytes before they are dumped to disk in a well known and indexable location, with an attacker supplied filename and extension.
This could be a default behavior to re-align with other browsers, to avoid known security headaches, and probably some as-yet-undiscovered ones too.
Absent action from the Chrome developers, there is fortunately a setting that can be used in environments where security is a concern: chrome://settings -> Show advanced settings -> Downloads -> Ask where to save each file before downloading.
Fedora’s culpability
You can clearly see the appeal of parsing and indexing all files that appear on disk. It enables desktop functionality that some users will consider userful. Certainly, other non-Linux operating systems do some of this too. But, this cannot be done carelessly. The lack of a robust sandbox for automatically parsing media formats seems like the largest gap Fedora has.
This is a problem that lends itself nearly ideally to sandboxing: the inputs and outputs and clear, and the rights needed to transform the inputs into the outputs are minimal. The input is just a memory chunk of the media file to be indexed. The output is a few metadata strings, perhaps some fixed format thumbnail canvases, etc. And the rights needed to do the transform are probably minimal: no writable filesystem access, no network access, no other process access, etc.
There’s also the concern that the media attack surfaces are a little out of hand. The default desktop install offers a range of fringe decoders that provide risk, for little benefit. In the case of Fedora 24, the faulty plug-in is pulled in via the default package gstreamer1-plugins-bad-free. From the gstreamer documentation: “these plug-ins are Bad with a capital B”. I’m not the first to notice this possible disconnect. From Ted Unangst’s post: “I do not want something called gstreamer-plugins-bad to be connected to the internet”. For some of the most dodgy decoders, we probably shouldn’t even be providing a nice automatic UI to offer to install them, because users will just click “yes”.
We can note that one quick way to break the link between the two risky designs is to remove the user/Downloads folder from the list of indexed paths. This can be achieved via the tracker-preferences tool, see below.
Disabling or removing tracker is non-trivial. This thing is like a virus! There’s some good information here: How do I disable tracker? To distill it:
- There’s a non-default-install tool, tracker-preferences, that appears to offer the ability to disable indexing, or configure which paths are indexed.
- Or you may prefer to just eviscerate tracker from your install. A lot of things depend on it, but rpm -e tracker --nodeps does not appear to blow things up too badly.
- Beware that even after removing the tracker package, a log out and then in still left the daemons running for me. Perhaps do a reboot to be safe.
Bonus 0day
(Absent a CVE, you can uniquely identify this as CESA-2016-0003.)
The render canvas, as allocated in the code snippet quoted way above, is not black filled or otherwise initialized. The call to g_malloc() is just a thin wrapper around malloc(), which does not initialize any returned heap area. Therefore, there’s an easy information leak in thumbnailing a simple 1 frame vmnc movie that does not draw to the allocated render canvas at all. This could be a problem for anyone using gstreamer in a server environment to provide thumbnailing services.
Closing notes
Did I mention that ffmpeg has a vmnc decoder too and it appears more robust than the gstreamer one?
This was too easy. It should not be possible to find a serious memory corruption vulnerability in the default Linux desktop attack surface with just a few minutes of looking. Although it’s hard to say it, this is not the kind of situation that occurs with a latest Windows 10 default install. Is it possible that Linux desktop security has rotted?