Verifying Extensions' Compliance with Firefox's Private Browsing Mode
Tags: Browsers, JavaScript, Programming Languages, Security, Verification, Types
Posted on 19 August 2013.All modern browsers now support a “private browsing mode”, in which the browser ostensibly leaves behind no traces on the user's file system of the user's browsing session. This is quite subtle: browsers have to handle caches, cookies, preferences, bookmarks, deliberately downloaded files, and more. So browser vendors have invested considerable engineering effort to ensure they have implemented it correctly.
Firefox, however, supports extensions, which allow third party code to run with all the privilege of the browser itself. What happens to the security guarantee of private browsing mode, then?
The current approach
Currently, Mozilla curates the collection of extensions, and any extension must pass through a manual code review to flag potentially privacy-violating behaviors. This is a daunting and tedious task. Firefox contains well over 1,400 APIs, of which about twenty are obviously relevant to private-browsing mode, and another forty or so are less obviously relevant. (It depends heavily on exactly what we mean by the privacy guarantee of “no traces left behind”: surely the browser should not leave files in its cache, but should it let users explicitly download and save a file? What about adding or deleting bookmarks?) And, if the APIs or definition of private-browsing policy ever change, this audit must be redone for each of the thousands of extensions.
The asymmetry in this situation should be obvious: Mozilla auditors should not have to reconstruct how each extension works; it should be the extension developers' responsibility to convince the auditor that their code complies with private-browsing guarantees. After all, they wrote the code! Moreover, since auditors are fallible people, too, we should look to (semi-)automated tools to lower their reviewing effort.
Our approach
So what property, ultimately, do we need to confirm about an extension's code to ensure its compliance? Consider the pseudo-code below, which saves the current preferences to disk every few minutes:
  var prefsObj = ...
  const thePrefsFile = "...";
  function autoSavePreferences() {
    if (inPivateBrowsingMode()) {
      // ...must store data only in memory...
      return;
    } else {
      // ...allowed to save data to disk...
      var file = openFile(thePrefsFile);
      file.write(prefsObj.tostring());
    }
  }
  window.setTimeout(autoSafePreferences, 3000);The key observation is that this code really defines two programs that happen to share the same source code: one program runs when the browser is in private browsing mode, and the other runs when it isn't. And we simply do not care about one of those programs, because extensions can do whatever they'd like when not in private-browsing mode. So all we have to do is “disentangle” the two programs somehow, and confirm that the private-browsing version does not contain any file I/O.
Technical insight
Our tool of choice for this purpose is a type system for JavaScript. We've used such a system before to analyze the security of the ADsafe sandbox. The type system is quite sophisticated to handle JavaScript idioms precisely, but for our purposes here we need only part of its expressive power. We need three pieces: first, three new types; second, specialized typing rules; and third, an appropriate type environment.
- We define one new primitive type: Unsafe. We will ascribe this type to all the privacy-relevant APIs.
- We use union types to define Ext, the type of “all private-browsing-safe extensions”, namely: numbers, strings, booleans, objects whose fields areExt, and functions whose argument and return types areExt. Notice thatUnsafe“doesn’t fit” intoExt, so attempting to use an unsafe function, or pass it around in extension code, will result in a type error.
- Instead of defining Boolas a primitive type, we will instead defineTrueandFalseas primitive types, and defineBoolas their union.
- If an expression has some union type, and only one component
      of that union actually typechecks, then we optimistically say
      that the expression typechecks even with the whole union type.
      This might seem very strange at first glance: surely, the
      expression 5("true")shouldn't typecheck? But remember, our goal is to prevent privacy violations, and the code above will simply crash---it will never write to disk. Accordingly, we permit this code in our type system.
- We add special rules for typechecking if-expressions.  When
      the condition typechecks at type True, we only check the then-branch; when the condition typechecks at typeFalse, we only check the else-branch. (Otherwise, we check both branches as normal.)
- We give all the privacy-relevant APIs the
    type Unsafe.
- We give the API inPrivateBrowsingMode()the typeTrue. Remember: we just don't care what happens when it's false!
  Put together, what do all these pieces achieve?
  Because Unsafe and Ext are disjoint from
  each other, we can safely segregate any code into two pieces that
  cannot communicate with each other.  By carefully initializing the
  type environment, we make Unsafe precisely delineate
  the APIs that extensions should not use in private browsing mode.
  The typing rules for if-expressions plus the type
  for inPrivateBrowsingMode() amount to disentangling the
  two programs from each other: essentially, it implements dead-code
  elimination at type-checking time.  Lastly, the rule about union
  types makes the system much easier for programmers to use, since they
  do not have to spend any effort satisfying the typechecker about
  properties other than this privacy guarantee.
In short, if a program passes our typechecker, then it must not call any privacy-violating APIs while in private-browsing mode, and hence is safe. No audit needed!
Wait, what about exceptions to the policy?
Sometimes, extensions have good reasons for writing to disk even
  while in private-browsing mode.  Perhaps they're updating their
  anti-phishing blacklists, or they implement a download-helper that
  saves a file the user asked for, or they are a bookmark manager.  In
  such cases, there simply is no way for the code to typecheck.  As in
  any type system, we provide a mechanism to break out of the type
  system: an unchecked typecast.  We currently write such casts
  as cheat(T).  Such casts must be checked by a human
  auditor: they are explicitly marking the places where the extension
  is doing something unusual that must be confirmed.
  (In our original version, we took our cue from Haskell and wrote
  such casts as unsafePerformReview, but sadly that is
  tediously long to write.)
But does it work?
Yes.
We manually analyzed a dozen Firefox extensions that had already
  passed Mozilla's auditing process.  We annotated the extensions with
  as few type annotations as possible, with the goal of forcing the
  code to pass the typechecker, cheating if necessary.
  These annotations found five extensions that violated
  the private-browsing policy: they could not be typechecked without
  using cheat, and the unavoidable uses
  of cheat pointed directly to where the extensions
  violated the policy.
Further reading
We've written up our full system, with more formal definitions of the types and worked examples of the annotations needed. The writeup also explains how we create the type environment in more detail, and what work is necessary to adapt this system to changes in the APIs or private-browsing implementation.
