Azimuth Security: The Chrome Sandbox Part 2 of 3: The IPC Framework <body onload='MM_preloadImages(&apos;http://www.azimuthsecurity.com/images/a_02.gif&apos;,&apos;http://www.azimuthsecurity.com/images/r_02.gif&apos;,&apos;http://www.azimuthsecurity.com/images/t_02.gif&apos;,&apos;http://www.azimuthsecurity.com/images/s_02.gif&apos;)'><script type="text/javascript"> function setAttributeOnload(object, attribute, val) { if(window.addEventListener) { window.addEventListener('load', function(){ object[attribute] = val; }, false); } else { window.attachEvent('onload', function(){ object[attribute] = val; }); } } </script> <div id="navbar-iframe-container"></div> <script type="text/javascript" src="https://apis.google.com/js/platform.js"></script> <script type="text/javascript"> gapi.load("gapi.iframes:gapi.iframes.style.bubble", function() { if (gapi.iframes && gapi.iframes.getContext) { gapi.iframes.getContext().openChild({ url: 'https://www.blogger.com/navbar.g?targetBlogID\x3d509652393303233687\x26blogName\x3dAzimuth+Security\x26publishMode\x3dPUBLISH_MODE_HOSTED\x26navbarType\x3dBLUE\x26layoutType\x3dCLASSIC\x26searchRoot\x3dhttp://blog.azimuthsecurity.com/search\x26blogLocale\x3den\x26v\x3d2\x26homepageUrl\x3dhttp://blog.azimuthsecurity.com/\x26vt\x3d1038547295672672920', where: document.getElementById("navbar-iframe-container"), id: "navbar-iframe" }); } }); </script>
azimuth security services training resources about BLOG
project zeus
"You will not be informed of the meaning of Project Zeus until the time is right for you to know the meaning of Project Zeus."
Archives
Current Posts
April 2010
May 2010
August 2010
September 2012
February 2013
March 2013
April 2013
May 2013
June 2013
December 2013
March 2014
January 2015
Posts
The Chrome Sandbox Part 2 of 3: The IPC Framework
The Chrome Sandbox Part 2 of 3: The IPC Framework
posted by Anonymous @ 8/28/2010 07:55:00 AM  

This post is the second part of a 3-part series about the Chrome sandbox. In the first post, I presented a basic overview of the Chrome process architecture and presented a breakdown of the attack surfaces for performing privilege escalations. This post continues our exploration of Chrome by focusing on one of the major attack surfaces identified - the IPC framework. As detailed in the previous post, this framework is used by Chrome to expose functionality to other processes by exporting a number of callback methods that client processes may invoke, much in the same way that traditional RPC client/server interaction occurs. This post discusses the inner workings of the IPC framework - a background to how it works, how messages are serialized and routed, and how to enumerate the attack surface to find processing exposed to untrusted  inputs. Several vulnerabilities that were uncovered during my audit are also presented to help illustrate what kind of vulnerabilities can occur at various levels of process interaction.



IPC Framework

Chrome processes communicate with each other via an Inter-Process Communication (IPC) messaging framework built in to the browser. For Windows, named pipes are utilized, whereas linux builds use local Unix sockets. Most of the code that implements the IPC framework is located within the ipc/ directory in the Chrome source tree. Apart from external OS facilities (such as the kernel), the IPC interaction between Chrome processes represents the largest local attack surface for potential privilege escalation. In the event that a renderer process is compromised, it can provide arbitrary requests to other more privileged processes that either trigger a vulnerability in one of the privileged processes, or cause them to perform a restricted operation on behalf of the renderer that inadvertently exposes the system to further compromise. So, let's take a look at how it works!

Channels

The messaging framework provided by Chrome provides bidirectional communication channels between two processes through the use of a Channel object (defined in ipc/ipc_channel.cc). The Channel object has two key member objects of interest to us:
  1. channel_impl_ - A ChannelImpl object that actually implements the low level communications facilities.
  2. listener_ - A listener object whose OnMessageReceived() function is invoked whenever a message is successfully received from the communication channel. (The class definition of Listener is defined in ipc/ipc_channel.h, but several other classes derive from it.)
As indicated above, a Listener object is a special object that is associated with a channel that handles incoming messages. It is used to provide the desired functionality of a given process by exposing a number of method callbacks that are invoked via the Listener::OnMessageReceived() function. This function typically does not handle many of the messages it receives itself; instead, it routes the messages to one of a series of objects that has been previously registered with the Listener object in question. Each of these objects registered to the listener are uniquely identified by an ID known as a routing ID, and contain a series of numbered callback methods that clients may invoke. The number of the callback is unique to each object and is referred to as a message type. In the case of the browser process, the Listener object maintains a list of MessageFilter objects. The following diagram shows this arrangement:


As you can see, each method may be uniquely identified by a pair of IDs: A routing ID and a message type ID. We will see a bit later on when we look at the message format that this pair of IDs is present in the Message header, allowing messages to be routed with a simple two-step process:
  1. If the routing ID of the message is the special value MSG_ROUTING_CONTROL (0x7FFFFFFF), then the listener object itself is intended to handle the message. Otherwise, the message is routed to an appropriate object registered with the Listener that has a matching routing ID. If no registered object has the requested routing ID, an error is returned.
  2. Assuming an appropriate object has been located from step 1, a relevant callback function encapsulated within the selected object is selected based on the type value within the Message object.
Note
There is also a special case message sent at the initiation of a connection: it has the special routing value MSG_ROUTING_NONE (0xFFFFFFFE) and type HELLO_MESSAGE_TYPE (0xFFFF). This message is used to inform each side of the connection with the other's process ID.

Listener objects and registered message handling objects have an OnMessageReceived() function, which matches message types with callback functions using the IPC_MESSAGE_HANDLER() or IPC_MESSAGE_HANDLER_DELAY_REPLY() macros, as shown:

bool ResourceMessageFilter::OnMessageReceived(const IPC::Message& msg) {
... code ...
IPC_MESSAGE_HANDLER(ViewHostMsg_CreateWindow, OnMsgCreateWindow)
IPC_MESSAGE_HANDLER(ViewHostMsg_CreateWidget, OnMsgCreateWidget)
IPC_MESSAGE_HANDLER(ViewHostMsg_SetCookie, OnSetCookie)
IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetCookies, OnGetCookies)
IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetRawCookies, OnGetRawCookies)
IPC_MESSAGE_HANDLER(ViewHostMsg_DeleteCookie, OnDeleteCookie)
IPC_MESSAGE_HANDLER(ViewHostMsg_GetCookiesEnabled, OnGetCookiesEnabled)
#if defined(OS_WIN)  // This hack is Windows-specific.
IPC_MESSAGE_HANDLER(ViewHostMsg_LoadFont, OnLoadFont)
#endif
IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetPlugins, OnGetPlugins)
IPC_MESSAGE_HANDLER(ViewHostMsg_GetPluginPath, OnGetPluginPath)
IPC_MESSAGE_HANDLER(ViewHostMsg_DownloadUrl, OnDownloadUrl)
IPC_MESSAGE_HANDLER_GENERIC(ViewHostMsg_ContextMenu,
                            OnReceiveContextMenuMsg(msg))
IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_OpenChannelToPlugin,
                                OnOpenChannelToPlugin)
IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_LaunchNaCl, OnLaunchNaCl)
IPC_MESSAGE_HANDLER(ViewHostMsg_CreateWorker, OnCreateWorker)
IPC_MESSAGE_HANDLER(ViewHostMsg_LookupSharedWorker, OnLookupSharedWorker)
IPC_MESSAGE_HANDLER(ViewHostMsg_DocumentDetached, OnDocumentDetached)

... code ...
}

The first parameter is a callback class name that contains an ID member that signifies the message type, and the second parameter is the callback function to invoke. We will discuss callback classes in more detail further on, but for now we just need to be aware that this class is used to associate a message type to a callback function. There are a vast number of message types exposed by each different type of host, each with unique sets of parameters and return values. For convenience, these message types are declared (along with their parameter types) in the following files:

chrome​/​common​/​render_messages_internal.h
  • ViewMsg_* - defines messages sent by the browser process to a renderer process
  • ViewHostMsg_* - defines messages sent by a renderer process to the browser process
chrome​/​common​/​plugin_messages_internal.h
  • PluginProcessMsg_* - defines messages sent from the browser to the​ plugin​ process
  • PluginProcessHostMsg_* - defines messages sent from theplugin​process to the browser
  • PluginMsg_* - defines messages sent from the renderer to the ​plugin​ process
  • PluginHostMsg_* - defines messages sent from the plugin​ process to the renderer process
  • NPObjectMsg_* - defines messages for dealing with NP Objects across process boundaries. Exchanged by both the renderer and the​ plugin process
chrome​/​common​/​worker_messages_internal.h
  • WorkerProcessMsg_* - defines messages sent from the browser to the worker process
  • WorkerProcessHostMsg_* - defines messages sent from the worker process to the browser
  • WorkerMsg_* - defines messages sent from the renderer process to the worker process
  • WorkerHostMsg_* - defines messages sent from the worker process to the renderer
chrome​/​common​/​utility_messages_internal.h
  • UtilityMsg_* - defines messages from the browser to the utility process
  • UtilityHostMsg_* - defines messages from the utility process to the browser
chrome​/​common​/​gpu_messages_internal.h
  • GpuMsg_* - defines messages sent from the browser to the GPU process
  • GpuHostMsg_* - defines messages sent from the GPU process to the browser
  • GpuChannelMsg_* - defines messages sent from the renderer process to the GPU process
  • GpuCommandBufferMsg_* - defines messages sent from the renderer process to the GPU process
chrome​/​common​/​nacl_messages_internal.h
  • NaClProcessMsg_*- defines messages sent from the browser to the​ NaCl​ process

Inspection of these files will show that a message type is declared with the IPC_MESSAGE_CONTROLXXX(), IPC_MESSAGE_ROUTEDXXX(), IPC_MESSAGE_CONTROLXXX_YYY(), or IPC_MESSAGE_ROUTEDXXX_YYY() macros (where XXX and YYY can be 0 - 4, indicating the number of parameters a message receives and returns respectively). Some example declarations are shown.

// Used to set a cookie.  The cookie is set asynchronously, but will be
// available to a subsequent ViewHostMsg_GetCookies request.
IPC_MESSAGE_ROUTED3(ViewHostMsg_SetCookie,
                    GURL /* url */,
                    GURL /* first_party_for_cookies */,
                    std::string /* cookie */)

// Used to get raw cookie information for the given URL.  This may be blocked
// by a user prompt to validate a previous SetCookie message.
IPC_SYNC_MESSAGE_ROUTED2_1(ViewHostMsg_GetRawCookies,
                           GURL /* url */,
                           GURL /* first_party_for_cookies */,
                           std::vector<webkit_glue::webcookie></webkit_glue::webcookie>
                           /* raw_cookies */)

// Used to delete cookie for the given URL and name
IPC_SYNC_MESSAGE_CONTROL2_0(ViewHostMsg_DeleteCookie,
                            GURL /* url */,
                            std::string /* cookie_name */)

We will revisit these macros a bit later on, but for now we can see that cross-referencing all the defined message type classes from the above-mentioned files with where they are associated with a callback function (ie. where the message class is used as a parameter to the IPC_MESSAGE_HANDLER() macro), we can find the implementation of each routine exposed over IPC, what data types they take, and what data they return. (We will discuss the marshalling of such parameters shortly.) Therefore, we are able to enumerate the exposed function attack surface and review each callback for possible flaws. For each of these functions, we must be mindful of typical vulnerabilities related to integer manipulation, dangerous memory management patterns (use after free etc), and standard buffer or pointer-related errors.

Example 1 - Worker Process Message Forwarding

Here is an example of a memory manipulation error that can be triggered in the browser process by a sandboxed renderer process. Chrome has the notion of 'Worker Processes' (which are also sandboxed) which renderers may start by sending the browser a ViewHostMsg_CreateWorker message. It may then subsequently send arbitrary messages destined for the worker process that are routed through the privileged browser process. The code to perform this forwarding is implemented in the WorkerProcessHost::OnMessageReceived() function (from chrome/browser/worker_host/worker_process_host.cc):


for (Instances::iterator i = instances_.begin(); i != instances_.end(); ++i) {
    if (i->worker_route_id() == message.routing_id()) {
        if (!i->shared()) {
            // Don't relay messages from shared workers (all communication is via
            // the message port).
            WorkerInstance::SenderInfo info = i->GetSender();
            CallbackWithReturnValue<int>::Type* next_route_id =
                GetNextRouteIdCallback(info.first);
            RelayMessage(message, info.first, info.second, next_route_id);
        }

        if (message.type() == WorkerHostMsg_WorkerContextDestroyed::ID) {
            instances_.erase(i);
            UpdateTitle();
        }
        break;
    }
}

Assuming an appropriate worker process has been found, the RelayMessage() function is called:

void WorkerProcessHost::RelayMessage(const IPC::Message& message,
                                     IPC::Message::Sender* sender,
                                     int route_id,
                                     CallbackWithReturnValue<int>::Type* next_route_id) 
<int>{
    if (message.type() == WorkerMsg_PostMessage::ID) {
        // We want to send the receiver a routing id for the new channel, so
        // crack the message first.
        string16 msg;
        std::vector<int> sent_message_port_ids;
        std::vector<int> new_routing_ids;
        if (!WorkerMsg_PostMessage::Read(
            &message, &msg, &sent_message_port_ids, &new_routing_ids)) {
            return;
        }
        DCHECK(sent_message_port_ids.size() == new_routing_ids.size());

        for (size_t i = 0; i < sent_message_port_ids.size(); ++i) {
            new_routing_ids[i] = next_route_id->Run();
            MessagePortDispatcher::GetInstance()->UpdateMessagePort(
                sent_message_port_ids[i], sender, new_routing_ids[i], next_route_id);
        }

The code here reads two vectors from the input message and proceeds to fill one of them (new_routing_ids) out. The loop featured above will write elements in to new_routing_ids according to the size of send_message_port_ids. Therefore, if new_routing_ids is smaller than sent_message_port_ids, then this loop will end up writing data to an out of bounds memory location, resulting in a buffer overflow. This bug of course hinges on the fact that the vector operator[] function does not do validation on the index passed to it, which is the case for g++, but not for Microsoft STL implementations. This bug was fixed in February of this year (http://src.chromium.org/viewvc/chrome?view=rev&revision=38209).

Note
The code above appears to do a sanity check with the DCHECK() line preceding the loop, however closer inspection will reveal that DCHECK() assertions are only present in debug builds, and are not compiled in to release builds.

In addition to memory corruption, we must also keep in mind the higher-level consequences of being able to randomly call any of these functions:

  1. Is there a way to call certain functions in an unexpected way due to an expected sequence of IPC calls?
  2. Does the function perform any operation that would represent a potential security problem? For example, will it let us write arbitrary files to locations that we couldn't normally write to due to sandbox restrictions?

Example 2 - Clipboard Type Confusion Vulnerability

This bug is actually a type confusion vulnerability but stems from an unexpected invocation of one of the standard clipboard messages that a renderer may send a browser. The browser exposes two functions for writing to the clipboard - ViewHostMsg_ClipboardWriteObjectsSync and ViewHostMsg_ClipboardWriteObjectsAsync. They are both exposed to IPC via the ResourceMessageFilter object (implemented in chrome/browser/renderer_host/resource_message_filter.cc). As their respective names imply, they are used to write objects to the clipboard - with the former function performing a synchronous write and the latter performing an asynchronous one. In each case, the parameter passed by the user is a std::map < int, vector <> >. For each pair in the map, the integer value denotes the type of object to be written to the clipboard, and the vector <> is essentially a buffer containing the contents. The function that stores the data in the clipboard is the Clipboard::DispatchObject() function, which performs sanity checking on the data for various object types before storing them. The object type of interest to us is the CBF_SMBITMAP type:

    case CBF_SMBITMAP: {
      using base::SharedMemory;
      using base::SharedMemoryHandle;

      if (params[0].size() != sizeof(SharedMemory*))
        return;

      // It's OK to cast away constness here since we map the handle as
      // read-only.
      const char* raw_bitmap_data_const =
          reinterpret_cast<const char*>(&(params[0].front()));
      char* raw_bitmap_data = const_cast<char*>(raw_bitmap_data_const);
      scoped_ptr<SharedMemory> bitmap_data(
          *reinterpret_cast<SharedMemory**>(raw_bitmap_data));

      if (!ValidateAndMapSharedBitmap(params, bitmap_data.get()))
        return;
      WriteBitmap(static_cast<const char*>(bitmap_data->memory()),
                  &(params[1].front()));
      break;
    }

As can be seen, the buffer contents supplied by the user is actually interpreted as a pointer to a SharedMemory object. Thus, it would seem that the user is able to specify an arbitrary pointer that is interpreted as a pointer to an object. However, this is not entirely correct. We see that in ResourceMessageFilter::OnClipboardWriteObjectsSync() (the function exposed to IPC For ViewHostMsg_ClipboardWriteObjectsSync), the buffer supplied by the user for CBF_SMBITMAP where the pointer is taken from is actually replaced with a valid pointer by the browser process.


void ResourceMessageFilter::OnClipboardWriteObjectsSync(
    const Clipboard::ObjectMap& objects,
    base::SharedMemoryHandle bitmap_handle) 
{
    DCHECK(base::SharedMemory::IsHandleValid(bitmap_handle))
           << "Bad bitmap handle";
    // We cannot write directly from the IO thread, and cannot service the IPC
    // on the UI thread. We'll copy the relevant data and get a handle to any
    // shared memory so it doesn't go away when we resume the renderer, and post
    // a task to perform the write on the UI thread.
    Clipboard::ObjectMap* long_living_objects = new Clipboard::ObjectMap(objects);

    // Splice the shared memory handle into the clipboard data.
    Clipboard::ReplaceSharedMemHandle(long_living_objects, bitmap_handle,
                                      handle());

    ChromeThread::PostTask(
        ChromeThread::UI,
        FROM_HERE,
        new WriteClipboardTask(long_living_objects));
}


The actual replacement is performed by Clipboard::ReplaceSharedMemHandle(). The problem is that objects of type CBF_SMBITMAP are only ever expected to appear in ViewHostMsg_ClipboardWriterObjectsSyncmessages, and not expected in ViewHostMsg_ClipboardWriteObjectsAsync messages. Therefore, the Clipboard::ReplaceSharedMemHandle() function is never called for the latter:


void ResourceMessageFilter::OnClipboardWriteObjectsAsync(
    const Clipboard::ObjectMap& objects) 
{
    // We cannot write directly from the IO thread, and cannot service the IPC
    // on the UI thread. We'll copy the relevant data and post a task to preform
    // the write on the UI thread.
    
    Clipboard::ObjectMap* long_living_objects = new Clipboard::ObjectMap(objects);

    ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, 
                  new WriteClipboardTask(long_living_objects));
}


Therefore, by calling the ViewHostMsg_ClipboardWriteObjectAsync method from a renderer with a CBF_SMBITMAP object in our parameter array, it is possible to provide an arbitrary pointer that will be later cast to an object and utilized, thus resulting in potential arbitrary execution. This bug was fixed in June of this year (http://src.chromium.org/viewvc/chrome?view=rev&revision=46639).

You should keep in mind that the attack surface might also include IPC functions exposed by the local unprivileged process. If the local process exposes methods that return data to a more privileged process, they might be able to supply malformed or unexpected responses that will result in vulnerabilities when being processed by the client.

Messages

So far we have looked at the functions exposed over IPC, how to enumerate them, and how to determine what parameters may be passed to them. But we skipped some important details - how exactly are these functions invoked? And how are parameters transported from one process to the other? The answer to these questions requires us to look at some of the lower level transmission details a little more closely, which we will do here.

The basic unit for transmission across IPC channels is the Message object (defined in ipc/ipc_message.h). Message objects are structured as follows.


Messages are processed in the order they are received from the communications channel by the ChannelImpl::ProcessIncomingMessage() function (implemented within ipc/ipc_channel_win.cc or ipc/ipc_channel_posix.cc depending on the target platform). This is perhaps the lowest-level input vector (save for OS-level problems) for targeting higher-privileged processes via the IPC framework. Here, we can examine how messages are received, buffered, and decapsulated. We might expect to find integer-related errors due to message lengths, out-of-state errors problems due to unexpected flag fields and such (which we didn't cover here - this is an exercise left to the reader), or poor descriptor manipulation for Unix implementations.

As we already know, once messages have been successfully received, they are routed to an appropriate callback function through the use of a routing ID and message type, both of which are present in the Message header. The IPC_MESSAGE_HANDLER() macros actually expand to a case statement based on the message type. The remainder of the message data is interpreted as a set of parameters for the called function, or for results of a called function in the case of reply messages.

Parameter Deserialization

Previously, we looked at how callback classes were associated with callback routines exposed over IPC. We also introduced the IPC_MESSAGE_ROUTED() families of macros that indicate the parameters that each function will take. These macros actually build definitions of the named callback classes based on the parameters provided to the macro. Callback classes indicated by the first parameter are all derived from IPC::Message or IPC::MessageWithTuple < > and contain key elements needed to invoke the callback function correctly. Firstly, each object contains the aforementioned static ID member that is used to match messages type IDs to desired functions, and secondly, a Dispatch() method is implemented for each class that performs the necessary parameter deserialization from the message blob and passes the resultant parameters to the destination callback function. Note that in the case of callback functions with no parameters, the IPC::Message class is used, which does not need to do any parameter deserialization. Otherwise, IPC::MessageWithTuple < > is derived from, with the data types of the expected parameters indicated as template parameters to the class.

So, the MessageWithTuple < >::Dispatch() function must unpack parameters intended for the callback function from the message data. This is achieved with the MessageWithTuple < >::Read() function, which uses several layers of templating indirection to call the correct deserialization routine. Likewise, MessageWithTuple < >::Write() will cause relevant output parameters to be written to a message for transmitting a result. The Message class derives from the Pickle class (implemented in base/pickle.cc), which contains functions for reading basic data types such integers, strings, and bools. Callbacks that take basic types as parameters essentially use these functions directly.

For more complicated data structures, unpacking and packing is achieved by calling another templatized method: ParamTraits <> ::Read() and ParamTraits <> ::Write(), where 'type' is the expected data structure being read or written. So, for every possible data structure received by a callback function, there is a matching ParamTraits <> implementation. An example for the gfx::Point object is shown:

bool ParamTraits<gfx::Point>::Read(const Message* m, void** iter,
                                   gfx::Point* r) {
  int x, y;
  if (!m->ReadInt(iter, &x) ||
      !m->ReadInt(iter, &y))
    return false;
  r->set_x(x);
  r->set_y(y);
  return true;
}

void ParamTraits<gfx::Point>::Write(Message* m, const gfx::Point& p) {
  m->WriteInt(p.x());
  m->WriteInt(p.y());
}


Most of the ParamTraits < > implementations are located within the following files:

chrome/common/common_param_traits.h
chrome/common/common_param_traits.cc
chrome/common/gpu_messages.h
chrome/common/plugin_messages.h
chrome/common/render_messages.h
chrome/common/utility_messages.h
chrome/common/webkit_param_traits.h
ipc/ipc_message_utils.cc
ipc/ipc_message_utils.h

The type deserialization routines are another broad attack surface that can be targeted by untrusted processes looking to perform privilege escalation attacks. There are a large number of data structures that contain relatively complex data structures, and they make likely candidates for memory corruption-style vulnerabilities, especially due to integer manipulation problems.

Example 3 - SkBitmap Deserialization

SkBitmap structures are used to transmit bitmap data between processes over IPC. The SkBitmap structure looks like this:


class SkBitmap {
...
    uint32_t    fRowBytes;
    uint32_t    fWidth;
    uint32_t    fHeight;
    uint8_t     fConfig;
    uint8_t     fFlags;
    uint8_t     fBytesPerPixel; // based on config
};

ParamTraits < SkBitmap > ::Read() allows a largely unchecked SkBitmap_Data structure to be used to create an SkBitmap:

bool ParamTraits<SkBitmap>::Read(const Message* m, void** iter, SkBitmap* r) {
  const char* fixed_data;
  int fixed_data_size = 0;
  if (!m->ReadData(iter, &fixed_data, &fixed_data_size) ||
     (fixed_data_size <= 0)) {
    NOTREACHED();
    return false;
  }
  if (fixed_data_size != sizeof(SkBitmap_Data))
    return false;  // Message is malformed.
 
  const char* variable_data;
  int variable_data_size = 0;
  if (!m->ReadData(iter, &variable_data, &variable_data_size) ||
     (variable_data_size < 0)) {
    NOTREACHED();
    return false;
  }
  const SkBitmap_Data* bmp_data =
      reinterpret_cast<const SkBitmap_Data*>(fixed_data);
  return bmp_data->InitSkBitmapFromData(r, variable_data, variable_data_size);
}

While the allocPixels() function (called from InitSkBitmapFromData()) protects from integer overflows, it does so by checking that fHeight * fRowBytes do not overflow. However, fWidth can be desynchronized from fRowBytes (that is, fWidth is not checked to be valid in relation to the fHeight and fRowBytes members), which in turn can create problems in the privileged process. For example, when the SkBitmap is stored in a Thumbnail store (via the ViewHostMsg_Thumbnail message), the JPEGCodec encodes the data from the SkBitmap, which does calculations based on the images' fWidth value, not fRowBytes. This bug was fixed in December of last year (http://src.chromium.org/viewvc/chrome?view=rev&revision=35371).

In addition to memory corruption-style vulnerabilities, it is important to keep an eye out for data structures that maintain some sort of internal state information. In this case, it might be possible to alter that state in an unexpected way and cause processing vulnerabilities due to the desynchronized structure.

Example 4 - Plugin Messages

Quite a large number of messages exchanged between the renderer process and the plugin process were found to contain data structures that had internal pointer fields. These pointers were never used by the renderer (as they are only valid in the context of the plugin process), but were transmitted back and forth as part of a way to maintain state of persistent data structures. One such example is NPVariant_Params structure defined in chrome/common/plugin_messages.h. This data structure was defined as:


struct NPVariant_Param {
    NPVariant_ParamEnum type;
    bool bool_value;
    int int_value;
    double double_value;
    std::string string_value;
    int npobject_routing_id;
    intptr_t npobject_pointer;
};

Essentially, it is used to transmit an NPVariant object between processes as part of invocation or property getting/setting of plugin scriptable objects. The NPVariant data structure is read using ParamTraits < NPVariant_Param > ::Read():

static bool Read(const Message* m, void** iter, param_type* r) {
    ... code ...

    } else if (r->type == NPVARIANT_PARAM_OBJECT_ROUTING_ID) {
        result =
            ReadParam(m, iter, &r->npobject_routing_id) &&
            ReadParam(m, iter, &r->npobject_pointer);
    } else if (r->type == NPVARIANT_PARAM_OBJECT_POINTER) {
        result = ReadParam(m, iter, &r->npobject_pointer);
    } else if ((r->type == NPVARIANT_PARAM_VOID) ||
             (r->type == NPVARIANT_PARAM_NULL)) {
        result = true;
    } else {
        NOTREACHED();
    }
    return result;
}

As can be seen, the NPVariant_Params passed as arguments to Invoke() can contain arbitrary pointers if they are of type NPVARIANT_PARAM_OBJECT_ROUTING_ID or NPVARIANT_PARAM_OBJECT_POINTER. These pointers are subsequently treated as valid NPObject pointers when passed to the destination plugin function. This can easily lead to arbitrary execution.

Handle Sharing

You will notice that there are numerous instances where handles to objects are shared across processes. For Windows versions of Chrome, passing handles to other processes is typically achieved using the DuplicateHandle() function, and then passing the resultant HANDLE value to the remote process. For Unix, socket descriptors are passed over the local Unix socket using SCM_RIGHTS ancillary messages. The descriptors passed over the sockets are then referenced by index from the array of socket descriptors received by the same message. Most of the socket descriptor sanitation takes place in the ChannelImpl::ProcessIncomingMessage() function that was mentioned earlier. Passing resources between processes needs to be done quite carefully, as potentially sensitive resources are being handed to privileges of lesser privilege. I found an example of this type of problem, however the Google Chrome security team had already found it and patched it by the time I reported it. Still, it makes a good case study, so we will briefly mention it here.

Example 5 - Database File Manipulation

Chrome provides a number of methods to manipulate database files, exposed through the DatabaseDispatcherHost object(implemented in chrome/browser/renderer_host/database_dispatcher_host.cc). Several of these messages resulted in the opening of a database file of the callers choosing, although the database file given would be pre-pended with a fixed database path. The work for generating the filename is implemented within DatabaseUtil::GetFullFilePathForVfsFile() and DatabaseUtil::CrackVfsFilename (both implemented in webkit/database/database_util.cc).

Conclusion

The IPC framework is a large and ripe attack surface. Hopefully this article has helped to demystify some of its inner workings and provided a rough guide to how one might go about auditing it. Tune in for my final post where I will discuss the sandbox implementation itself, how it works, and also provide some examples of vulnerabilities that were uncovered there.

Labels: , , , ,

7 comments:

At February 13, 2011 at 12:21 PM , Anonymous Anonymous said...

Will part 3 be published any soon?

At April 25, 2011 at 6:17 AM , Blogger Buffer G. Overflow said...

Was part 3 ever published? I'm curious to read that together with Dino Blazakis's paper on OS X sandboxing (http://www.semantiscope.com/research/BHDC2011/BHDC2011-Paper.pdf).

At June 6, 2011 at 9:56 PM , Anonymous Marcos Álvares said...

Hi Mark,

how are you, man? first of all, congratulations by the awesome article! So, I'm trying to study the Chrome sandbox architecture. Have you some reference for help me? Maybe you already have something for the third part of these posts about Chrome. i'm looking for everything, before go to directly the code. : ]

[]'s
Marcos Álvares

At July 6, 2011 at 11:08 PM , Anonymous Anonymous said...

Still hanging out for part 3 ;)

At September 29, 2011 at 4:37 AM , Anonymous Anonymous said...

I'm really looking forward to part 3!

At September 6, 2013 at 2:32 PM , Blogger Unknown said...

+1 I'm waiting for part3.

At June 10, 2014 at 8:20 PM , Anonymous Mark Dowd said...

I'm looking forward to part 3 as well. I am not Mark Dowd though.

Post a Comment

Subscribe to Post Comments [Atom]

<< Home

© Copyright 2013 Azimuth Security Pty Ltd