Thursday, February 4, 2016

Racing MIDI messages in Chrome

This is a guest blog post by Oliver Chang from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Chrome Security team.

This post is about an exceptionally bad use after free bug in Chrome’s browser process that affected Linux, Chrome OS and OS X. What makes this bug interesting is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fact that it could be directly triggered from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 web without compromising cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sandboxed renderer process. I discovered this while participating in a Chrome IPC bug bash with Project Zero (during which 3 ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r bugs were discovered).

Background

Let’s get some basic Chrome fundamentals out of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 way before getting into this post. Chrome is made up of multiple processes, and each process can contain several threads. Each thread usually runs an infinite loop processing tasks (callbacks) in its MessageLoop. Chrome provides cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following interface for posting tasks to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se message loops.

// Posts cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 given task to be run.  Returns true if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task may be
// run at some point in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 future, and false if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task definitely
// will not be run.
//
// Equivalent to PostDelayedTask(from_here, task, 0).
bool PostTask(const tracked_objects::Location& from_here,
             const Closure& task);

// Like PostTask, but tries to run cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 posted task only after
// |delay_ms| has passed.
//
// It is valid for an implementation to ignore |delay_ms|; that is,
// to have PostDelayedTask behave cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same as PostTask.
virtual bool PostDelayedTask(const tracked_objects::Location& from_here,
                            const Closure& task,
                            base::TimeDelta delay) = 0;

A base::Closure is used to represent a task. It is essentially a callback with bound arguments and a dash of some magic Chrome sauce. When a Closure is created for a member function, by default a reference is added for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 this argument (as long as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 class subclasses one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 base reference counted classes) so that when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task runs, this is guaranteed to point to a live object. An unretained pointer (no references added) can also be passed by wrapping this in base::Unretained. For example,

// No reference is added for |this| because of base::Unretained(). Callers
// must ensure |this| is alive when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 closure is run.
base::Closure closure = base::Bind(
   &Class::Function, base::Unretained(this), argument);

More details about cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se abstractions can be found here and here.

What’s this MIDI thing in my browser?

It was a surprise to discover that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is a draft of a web standard for accessing MIDI devices from your web browser. In Chrome, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer process implements cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 user facing JavaScript APIs, and IPCs cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 privileged browser process for brokered access to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MIDI devices. I later found out that this wasn’t cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first Web MIDI related critical severity bug.

This diagram summarises cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 relationship between cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 relevant players here:


MidiHost is a subclass of BrowserMessageFilter, which is installed on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IPC channel for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RenderProcessHostImpl that corresponds to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer process. It’s responsible for handling MIDI related IPC messages from renderer process. It also subclasses MidiManagerClient, which provides callbacks that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MidiManager can call to return results.

Subclasses of MidiManager contain cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 platform specific implementation for interfacing with MIDI devices. One thing to note here is that MidiHosts live only as long as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer process. The MidiManager lives for as long as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser lives.

In this post we’ll mostly focus on MidiManagerAlsa (implementation for Linux and Chrome OS). This particular MidiManager implementation owns two independent threads:
  • send_thread_, for handling cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sending of MIDI messages to output devices
  • event_thread_, for handling events from ALSA

In cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer process, MidiMessageFilter does cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer-side IPC handling.

The actual IPC messages that can be sent to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser are quite simple:

// Renderer request to browser for access to MIDI services.
IPC_MESSAGE_CONTROL0(MidiHostMsg_StartSession)

IPC_MESSAGE_CONTROL3(MidiHostMsg_SendData,
                    uint32_t /* port */,
                    std::vector /* data */,
                    double /* timestamp */)

IPC_MESSAGE_CONTROL0(MidiHostMsg_EndSession)

On Linux and Chrome OS, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re should be a default dummy MIDI input/output port, so no physical devices are necessary to trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se bugs. By default, everything we pass to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 output port appears to be echoed back by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 input port. No special permissions are necessary to access cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se ports (unless you want to send SysEx messages, but more on that later).

$ aconnect -o
client 14: 'Midi Through' [type=kernel]
   0 'Midi Through Port-0'

The bugs

Bug #1

While looking for race condition bugs in Chrome IPC handling, I discovered a suspicious bit of code in media/midi/midi_manager_alsa.h (implementation for Linux and Chrome OS). It’s called by MidiHost::OnSendData during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 handling of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MidiHostMsg_SendData message, after doing some basic checks.

void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
                                          uint32 port_index,
                                          const std::vector& data,
                                          double timestamp) {
 ...
 send_thread_.message_loop()->PostDelayedTask(
     FROM_HERE, base::Bind(&MidiManagerAlsa::SendMidiData,
                           base::Unretained(this), port_index, data),
     delay);
 // Acknowledge send.
 send_thread_.message_loop()->PostTask(
     FROM_HERE, base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
                           base::Unretained(client), data.size()));
}

In cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second PostTask, a closure is created by binding an unretained client to MidiManagerClient::AccumulateMidiBytesSent as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 this pointer. client here is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MidiHost that called this function.

There is a race condition here: if a renderer dies (and thus cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MidiHost gets destroyed) before send_thread_ runs cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 AccumulateMidiBytesSent task, when it gets run we get a virtual call on a freed this.

In ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r words, if we send cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following IPC message to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser (this corresponds to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MidiOutput.send API on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 JavaScript side):

MidiHostMsg_SendData(port_num, /* up to 10 MB worth of */ data, 0.0);

And cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n force cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer to exit, it’s possible that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser will run cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second closure after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MidiHost has been freed, resulting in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use after free.

The OS X implementation (MidiManagerMac) also appeared to be vulnerable, but I’ll focus on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ALSA implementation.

Bug #2

Race condition bugs can be very subtle, as demonstrated by this second bug which I discovered while writing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 draft for this post...

void MidiManager::ReceiveMidiData(uint32_t port_index,
                                 const uint8_t* data,
                                 size_t length,
                                 double timestamp) {
 base::AutoLock auto_lock(lock_);

 for (auto client : clients_)
   client->ReceiveMidiData(port_index, data, length, timestamp);
}

void MidiHost::ReceiveMidiData(uint32_t port,
                              const uint8_t* data,
                              size_t length,
                              double timestamp) {
 TRACE_EVENT0("midi", "MidiHost::ReceiveMidiData");

 base::AutoLock auto_lock(messages_queues_lock_);
 ...
 while (true) {
   ...

   // Send to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer.
   Send(new MidiMsg_DataReceived(port, message, timestamp));
 }
}

MidiManager::ReceiveMidiData (ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r similar functions can likely also trigger this bug) is called from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 event_thread_ when a MIDI input port has data for us (for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 dummy device, this would be echoed data of what we sent to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 output port). It cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n calls cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 corresponding function for each client, which IPCs cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 data to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 corresponding renderer. When BrowserMessageFilter::Send is called from a thread that isn’t cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IO thread, it will do a PostTask:

bool BrowserMessageFilter::Send(IPC::Message* message) {
  ...

 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
   BrowserThread::PostTask(
       BrowserThread::IO,
       FROM_HERE,
       base::Bind(base::IgnoreResult(&BrowserMessageFilter::Send), this,
                  message));
   return true;
 }
 ...

In MidiManager::ReceiveMidiData (and likely similar functions), a lock is taken to protect clients_ and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 subsequent call on each client. The destructor for MidiHost calls MidiManager::EndSession, which takes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lock and removes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 client from clients_, ensuring that no functions can be called on it afterwards.

The race condition is here: if we are in or about to enter cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 destructor (but before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 EndSession call), cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 event_thread_ can still call ReceiveMidiData, and eventually post a Send() task to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IO thread. The base::Bind in BrowserMessageFilter::Send will attempt to increase cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reference count from 0 to 1, but at that point it is already too late to stop cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 destruction. The IO thread will cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n go on to call Send() on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 deleted MidiHost.

Unlike cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first bug, this bug likely affects cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Windows implementation as well, although I didn’t explore this furcá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r.

Patches

Bug #1 for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ALSA implementation was fixed here, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 OS X implementation here. It was shipped in Chrome 47.0.2526.106. Bug #2 was fixed here, and shipped in first stable release for Chrome 48.

Overwriting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vtable pointer

Let’s focus on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first bug here since it’s easier to exploit. Since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 UaF is a virtual member call on a deleted this, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vtable pointer can be overwritten to redirect execution. Here’s a PoC that demonstrates this in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser process after clicking somewhere on a page.

The PoC consists of 2 files. The first file (start.html) opens up a second renderer when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 user clicks somewhere on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 page. The MidiHost for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second renderer (haq.html) is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 one that we will be targeting for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use after free. The second renderer should simply send a MIDI message, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n close itself. The first renderer cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n causes repeated allocations in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser to overwrite cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed object.

By default, every renderer that has started a MIDI session will receive MIDI messages from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser for every single input device, even if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y are “closed” on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer side. Recall that everything we send to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 dummy output device gets echoed through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 input device. This isn’t desirable for us for 2 reasons. As discussed in bug #2, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Send() calls from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser (event_thread_) will cause cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MidiHost of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 renderer we want to die to be referenced, possibly delaying its deletion after it sends cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MIDI data necessary to cause cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use after free. Also, we could run into bug #2 itself, which appears to be much harder to exploit.

There is one type of MIDI message that won’t always be echoed back to every renderer: SysEx messages. These messages are only sent back to renderers with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SysEx permission, so what we could do here is to flood event_thread_ with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first renderer (with SysEx), giving cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second renderer (without SysEx) a larger window to get its own MidiHost deleted before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser gets to send back cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 echoed payload.

Chrome requires SysEx permissions to be granted by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 user through an info bar asking for permissions to “Use your MIDI devices”. If we don’t have SysEx access, we can still overwrite cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vtable pointer -- it is just more unreliable (about 1 successful attempt in every 5 tries on my machine).

start.html

Click anywhere on this page.

haq.html

(must be accessible from a different origin e.g. http://0.0.0.0:8001)

hello.

And running this (on 64-bit Linux Chrome 47.0.2526.80)...

(In two separate terminals, start HTTP servers. It’s not actually necessary to have 2 different ports, but I’ve found python -m SimpleHTTPServer to be unreliable.)

$ cd path/to/poc; python -m SimpleHTTPServer 8000
$ cd path/to/poc; python -m SimpleHTTPServer 8001

$ gdb -ex r --args ./chrome --user-data-dir=/tmp/profile1 'http://localhost:8000/start.html'

(Allow MIDI access for more reliability, and click somewhere on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 page)

...

Program terminated with signal SIGSEGV, Segmentation fault.
...

(gdb) i r
rax            ...
rbx            ...
rcx            0x41 65   # by coincidence, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 index into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vtable.
rdx            0x4141414141414141 4702111234474983745 # vtable pointer
...

(gdb) x/4i $pc
=> 0x7f9446d74cd4: mov    rcx,QWORD PTR [rcx+rdx*1-0x1]
  0x7f9446d74cd9: mov    rsi,QWORD PTR [rdi+0x30]
  0x7f9446d74cdd: mov    rdi,rax
  0x7f9446d74ce0: jmp    rcx

At this point we are stuck because of ASLR. It is likely that at least one ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r bug is required (ideally a browser leak -> uncompromised renderer) to fully exploit this bug, and I wasn’t able to figure out a way to turn cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use after free into a reliable info leak (but it may be possible!).

Conclusion

This blog post was about a particularly severe race condition bug that was found while auditing for such bugs in Chrome’s browser IPC. Typically, a Chrome exploit chain would need to start with a renderer exploit to compromise a renderer, after which arbitrary (and possibly malformed) IPC messages can be sent to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser process. Bugs in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 browser process are cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n needed to achieve a sandbox escape. This bug however, can be potentially exploited for unsandboxed code execution straight from legitimate JavaScript API calls.
Dealing with threads is hard, and race conditions can exist in subtle and unexpected places. There is likely much value in continuing to target cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se types of bugs in complex applications such as Chrome.

1 comment:
Newer Posts Older Posts Home