Tuesday, 21 December 2010

A common misconception about the Chrome sandbox

A common misconception about the Chrome web browser is that its sandbox protects one web site from another.

For example, suppose you are logged into your e-mail account on mail.com in one tab, and have evil.com open in another tab. Suppose evil.com finds an exploit in the renderer process, such as a memory safety bug, that lets it run arbitrary code there. Can evil.com get hold of your HTTP cookies for mail.com, and thereby access your e-mail account?

Unfortunately, the answer is yes.

The reason is that mail.com and evil.com can be assigned to the same renderer process. The browser does not only do this to save memory. evil.com can cause this to happen by opening an iframe on mail.com. With mail.com's code running in the same exploited renderer process, evil.com can take it over and read the cookies for your mail.com account and use them for its own ends.

There are a couple of reasons why the browser puts a framed site in the same renderer process as the parent site. Firstly, if the sites were handled by separate processes, the browser would have to do costly compositing across renderer processes to make the child frame appear inside the parent frame. Secondly, in some cases the DOM allows Javascript objects in one frame to obtain references to DOM objects in other frames, even across origins, and it is easier for this to be managed within one renderer process.

I don't say this to pick on Chrome, of course. It is better to have the sandbox than not to have it.

Chrome has never claimed that the sandbox protects one site against another. In the tech report "The Security Architecture of the Chromium Browser" (Barth, Jackson, Reis and the Chrome Team; 2008), "Origin isolation" is specifically listed under "Out-of-scope goals". They state that "an attacker who compromises the rendering engine can act on behalf of any web site".

There are a couple of ways that web sites and users can mitigate this problem, which I'll discuss in another post. However, in the absence of those defences, what Chrome's multi-process architecture actually gives you is the following:

  • Robustness if a renderer crashes. Having multiple renderer processes means that a crash of one takes down only a limited number of tabs, and the browser and the other renderers will survive. It also helps memory management.

    But we can get this without sandboxing the renderers.

  • Protection of the rest of the user's system from vulnerabilities in the renderer process. For example, the sandboxed renderer cannot read any of the user's files, except for those the user has granted through a "File Upload" file chooser.

    But we can get this by sandboxing the whole browser (including any subprocesses), without needing to have the browser separated from the renderer.

    For example, since 2007 I have been running Firefox under Plash (a sandbox), on Linux.

    In principle, such a sandbox should be more effective at protecting applications and files outside the browser than the Chrome sandbox, because the sandbox covers all of the browser, including its network stack and the so-called browser "chrome" (this means the parts of the GUI outside of the DOM).

    In practice, Plash is not complete as a sandbox for GUI apps because it does not limit access to the X Window System, so apps can do things that X allows such as screen scraping other apps and sending them input.

The main reason Chrome was developed to sandbox its renderer processes but not the whole browser is that this is easier to implement with sandboxing technologies that are easily deployable today. Ideally, though, the whole browser would be sandboxed. One of the only components that would stay unsandboxed, and have access to all the user's files, would be the "File Open" dialog box for choosing files to upload.

2 comments:

CuriousLurker said...

Thanks for a great article!

Can you say more about why it is easier to sandbox just the renderer, rather than the entire browser, using sandboxing technologies that are currently easy to deploy widely?

Mark Seaborn said...

@CuriousLurker: That is an excellent question. Having thought about this some more, I think I was being too easy on Chrome in my post. It would not be too difficult to sandbox the browser process on Linux (the platform I am most familiar with). It would involve proxying the following:

* File access: This would cover profile files (i.e. Chrome config and cache) and user files (granted via a trusted file chooser). Plash's file proxying demonstrates that this is easy to do.
* Network access (assuming the sandboxing mechanism blocks network access). This is trivial to proxy because only the connect() system call needs to be proxied.
* Access to windowing: On Linux, the browser process uses Gtk for widgets, including menus and the config window. For the browser process to be sandboxed, it would have to draw its widgets without Gtk, and render graphics directly into a buffer, as the renderer process does. This is because Gtk assumes it has access to X11, and safely sandboxing access to X11 is difficult because X is so complex. Since Chrome's UI is very minimal, changing the browser process to draw its UI the same way as the renderer process would not be too arduous, although it would still be the largest chunk of work involved in sandboxing the browser.
* Access to other devices, e.g. geolocation and printing.

The fact that Chrome already proxies many of these resources for the renderer process on Linux, Mac and Windows tends to be an existence proof that they could be proxied for the browser process too.

The remaining question is whether it is worthwhile to sandbox the browser process too, or whether it would add unnecessary extra complexity.

One could argue that the browser process is mostly just a proxy for the renderer processes, so adding an extra layer of indirection for sandboxing the browser would be redundant. However, I don't think that is the case. A lot of stuff happens in the browser process that is not part of the mechanics of sandboxing. This includes:

* Network stack: HTTP, SSL, cache, cookie management
* URL bar, tab UI, favicons
* History and bookmarks
* Config UI

Staggeringly, there are around 1 million lines of code in chrome/browser:

$ (cd chrome/browser && wc -l `git ls-files`) | tail -1
1110002 total
$ wc -l `git ls-files chrome/browser/ | egrep '\.(cc|h|mm)$'` | tail -1
931462 total

This is a huge quantity of code that is currently trusted. And these counts do not include code in libraries that the browser process uses such as Sqlite, zlib and OpenSSL.

If one were to sandbox the browser process, the trusted code for proxying access to files, networking, windowing etc. would be tiny in comparison.