July 13, 2009

Jetpack: Summer 2009 State of Security, Part 1

Security is hard! It’s tough enough designing a platform that’s powerful, well-documented, and easy to use; but what about security? If we aren’t careful, adding a incorrectly tuned or naive security model negatively affects generativity and usability. Jetpack needs to balance all three.

The following is something I wrote at the beginning of June, but didn’t post until now because I’ve had my head in code for a bit too long. What follows is still accurate, except in cases where noted otherwise. In my next post, I’ll explain the research and tools we’ve created so far to solve the problem of security.

In order to understand Jetpack’s current security model, one has to first understand how Jetpack got to where it is.

Previous History

In May of 2009, I implemented the initial Jetpack prototype. This was originally intended to be securable in the sense that a Jetpack Feature (sometimes called “a Jetpack”) should be sandboxed within either a standard Web page or a Components.utils.Sandbox with a limited principal and be given only the objects that it needed to do what it wanted to accomplish.

However, this quickly met with difficulties due to either limitations in the Mozilla platform or limitations in my understanding of it. For instance, the simple use case of a single JS script communicating securely with two open Web pages via XPCNativeWrapper objects proved difficult. Here’s some code from the Unad demo included with the Jetpack prototype:

  $(widget).click(toggleState);
In this case, widget is the XPCNativeWrapped contentDocument property of an iframe XUL element with content privileges embedded in the status bar, which contains an HTML file on the server hosting the Jetpack. The $() function is a jQuery-like interface to simplify DOM manipulation at a level that is possible with XPCNativeWrapped objects.

Elsewhere in the same script, we have the following function:

  function removeAds(doc) {
    if (doc.location.protocol == “http:” ||
        doc.location.protocol == “https:“)
      $(doc).find(”[src]“).filter(function() {
        var el = $(this);
        if (blocklist && blocklist.match(el.attr(“src”)))
          el.remove();
        });
   }
In this case, doc is the XPCNativeWrapped HTML document object for a browser tab whose DOMContentLoaded event has just fired. blocklist is a simple interface for detecting whether a URL matches a known list of domains whose content should be removed from the page (presumably because they contain unwanted advertisements).

Ideally, to make this as easy on the developer as possible while remaining secure, both of the above code snippets should be contained in the same script, and executed within the same global object so that variables and so forth can be freely accessed without having to go through a cumbersome low-level barrier, such as a JSON message-passing bridge.

As far as I could find, however, such a solution wasn’t possible due to the Mozilla platform’s “binary” approach to codebase principals: either a script’s security principal could be associated with a single domain, in which case it could access the contents of pages on a single domain easily but not those of pages on other domains; or it could be associated with chrome, in which case it had free access to everything on the end-user’s system via the Components global. There appeared to be no “in-between” principal allowing for more granular permissions that would allow unfettered access to certain things (for instance, the DOM structures of pages on two or three explicitly-mentioned domains) while restricting access to others.

Above all, the Jetpack team believed that the initial prototype should focus on making the platform as easy and generative as possible. The main reason for this was simply that we didn’t actually know what people were going to do with such creative power once they had immediate access to it: better in the early stages to allow them to experiment unfettered, which allows us to see what they build and develop clean, secure APIs to encapsulate the kind of functionality they need.

For the reasons outlined above, we decided to go the route of giving Jetpacks a chrome codebase principal.

The Original Security Model

The original, highly-tentative plan was simply to allow Jetpacks to remain chrome-privileged, but introduce lightweight sandboxing mechanisms that would ultimately allow the Jetpack script to function as a high-privileged broker between less-privileged code. An early attempt at this can be seen in the Unad demo previously mentioned: the status bar panel is a content-space iframe, and the block-listing logic is contained in a locked-down script—actually a naive implementation of a SecurableModule, which I’ll talk about later—and the Jetpack script itself manages everything else.

It was then envisioned that Jetpacks could be made secure by encouraging developers to minimize the amount of code placed into their chrome-privileged Jetpack script; we could then rely on a healthy code review community to perform reviews of Jetpacks to ensure they were non-malicious. Further easing this process would be the simplicity and power of the Jetpack API: the benefit of replacing 20 lines of XPCOM boilerplate code to retrieve the clipboard contents with a simple call to jetpack.os.clipboard.get() not only makes the developer’s life easier, but the code reviewer’s as well.

After discussing this model with Mike Connor and Lucas Adamski on June 1, 2009, however, it was quickly found that this model had a number of vulnerabilities:

  1. Code reviews don't scale well in relation to the magnitude of code created in an extension model. Even in a heathy code review community where members have excellent social incentives to perform good reviews, performing a meaningful review of code that runs in the context of a high-privileged broker requires significant understanding about the technical details of security.
  2. Even assuming that the intent of a Jetpack is completely benign, running a Jetpack in a chrome context still means that it's very easy for its code to accidentally crash the user's browser, break something important, or—perhaps most worrisome—create a new security hole through which untrusted web pages can exploit an end-user's system. Further exacerbating this potential is the fact that because Jetpack's development model is designed to make extending Firefox as easy as writing a webpage, more developers with very little knowledge of security fundamentals will be extending Firefox than ever before.

Given the above points, it was determined that the original security plan, taken as a whole, was untenable.

A New Plan

A tenable security model for Jetpack involves the following components:

  • A mechanism for extensible, securable code reuse. The Mozilla platform is incredibly powerful, and wrapping it in a secure, versioned/backwards-compatible, and elegant API is time-consuming but parallelizable. In the short-term, we need a straightforward way for modules that encapsulate parts of the platform to define their own privilege levels and parameters, as this will allow the community to contribute to the creation of Jetpack's core API via Jetpack Enhancement Proposals and their reference implementations.

    In the long term, however, the ability to easily share and reuse code is the foundation for any healthy development ecosystem. To do this securely, we need to follow the principle of least privilege: for instance, even though my Jetpack Feature may need access to the local filesystem, I want to make sure that the Twitter library I load doesn't have access to the filesystem, and that it has the ability to contact twitter.com on the user's behalf even if the rest of my Jetpack doesn't.

    I'm not sure what the best way to do this is. One compelling standard I've seen is the ServerJS group's SecurableModules. Brendan Eich also discussed the notion of object tainting at the Mozilla All-Hands in April 2009, and another potential solution may be an object-capability model like that of Caja (though perhaps SecurableModules are a variant as well, I'm not sure). Yet another key to the solution may involve implementing a new, extensible codebase principal whose capabilities can be defined as a set of key-value pairs by the Jetpack Runtime. I honestly don't know enough about security or the internals of Spidermonkey/XPConnect to know what the ultimate solution is—but given the volatility of the domain, it's preferable that it be something that's malleable from JS chrome code, so that the details of the security model can be rapidly changed without building and distributing new binary components. (Note: we've since made a lot more progress on this, which will be explained in part 2.)
  • A humane user interface for presenting risk and trust information to end-users. This can be done either before the user first installs a Jetpack or when an installed Jetpack tries to do something that requires privilege escalation, but it clearly needs to avoid the pitfalls presented by solutions like Vista's User Account Control. This is an unsolved problem, and ideally the solution will be something that's technical as well as social: for instance, imagine a visual that displays both the privileges required by the Jetpack as well as head-shots of the user's trusted friends who use it. Striking artwork based on the security profile of the Jetpack could be used to make the user pay attention and not simply perceive the presentation as "yet another click-through". As this is also a volatile domain, it needs to be easy to change; it's also open enough to iteration by designers that we could expose this UI to Extensions and hold a Design Challenge for it. There's plenty of room to leverage the community here.

The Immediate Future

Most importantly, we need to define the interface through which Jetpacks and Jetpack modules declare their security requirements and dependencies, so that we can at least mock it out in the Jetpack extension. For the time being, all that’s needed is a mechanism to tell Jetpack authors that they’re doing things “the right way”, so that once the security model is fully implemented, their Jetpack will work without requiring any changes. While a preview page of what the Jetpack risk UI will look like once the security mechanism is implemented will be useful for authors and will also allow us to iterate on the UI, the actual page that will be displayed when users try installing Jetpacks—the one that looks eerily familiar to Ubiquity’s red screen of death—will remain the same, since the Jetpacks are actually insecure until the security model is fully implemented.

Furthermore, for the immediate future, we’d actually like to promote the fact that Jetpacks have chrome privileges, and encourage developers to copy-and-paste snippets from MDC into their Jetpacks and use Components to their heart’s content, because only once we see what they want to make can we know what APIs need to be made to securely wrap them. In fact, we’d ideally like to always make it possible for Jetpacks to be run in a sort of “developer mode” context, so that it’s possible for a developer to create their Jetpack in a setting where security is a concern but not an impediment, and deal with locking-down the Jetpack once they’re done experimenting, possibly even delegating the creation of an appropriate “security manifest” to another person or party.

© Atul Varma 2021