Security wrappers and Add-on development

Importance of security

One of the most important benefits of developing an add-on instead of a regular website is the extra privileges that you can get. What most people don’t consider is that power always comes with responsibility. It’s difficult to decide how much freedom an add-on developer should have. I think the best answer: is as much he needs. No more, no less… If life were only that simple… On the one hand we want add-on development to be simple and natural for a web developer without continuously worrying about questions like ‘is it safe to do this and that?’. On the other hand we want to give them full access to APIs that should only be used by thoroughly reviewed and tested system code… It would not be fair to put all the responsibility on add-on developers and add-on reviewers. If add-ons can be a security threat for the end user, that will harm both the reputation of the browser and the usage of add-ons in general. So the general principle that we’re trying to follow here is: only give access to things that you really need. And if you want to do some wicked things, unleashing the full capacity that the Gecko environment has to offer, then yes, you need to understand what you’re doing and can expect a longer review process…
The usual way add-on developers discover the security layer is by bumping into some security limitation that might be a totally confusing experience in some cases. I’m writing this blog to collect a few typical cases where this can occur, and to give you a brief description of the security layer in general. I think this is important, because knowing at least a little about it can help you understand what can it do for you. Not to mention that it can be extremely frustrating when you bump into an unexpected security limitation and don’t even have a clue what is going on.
I put security scenarios in three big categories for now.

  • Regular web security (XSS, same origin, cross origin, etc.)
  • Preventing leaking out system (chrome) privileges
  • Accessing untrusted DOM safely from trusted code

I don’t want to discuss the first topic this time. It’s something every browser have in common and most web developers have a fairly good understanding of it, and the spec is available for everyone.
Chrome (or system) privilage: In Gecko some javascript code runs without any restriction and can have access the chrome API that is unavailable for regular home pages. Either system code, or some addons that went through a review process. It can also have access to any origin. The worst possible thing that can happen security wise is leaking out chrome privilege to web content. It has to be prevented at all cost. So exposing object from chrome scope to content is rarely a good idea. Sometimes it’s necessary, and there is a security layer to prevent leaking, but still, any bug or mistake here can cause an immediate security critical bug. First the content-script of the Add-on SDK had system privileges. Since it’s a code that meant to interact with content, it turned out not to be a good idea. It is simply to ‘close’ to content. Right now it has the same privileges as the site it can interact with. This however turned out to be too much of a restriction in some cases. Currently a new version is being released where you can specify an array of origins for content-script it will gain same origin access to. And in the near future I hope I can add another feature that will enable wild cards in those origins.

And finally, when we want to manipulate the DOM of the content, or simply want to interact with it, there is nothing that would guarantee us that the the DOM has not been mutated by content at the time. We don’t want some untrusted site to trick our add-on code, or the other way around, our add-on should not have any unexpected side effect on the site. For example the content could have replaced getElementById with some custom magic, that returns something else than we expected, or does some crazy things. Or we might use a global variable that is used by the site as well and we get in conflict with it. We need a mechanism to see through all these ‘mutations’ and being able to reach the original DOM functionality we know from the specs.

Security wrappers

Making every operation ask the security layer whether it’s allowed or not would be way too slow. So instead, as in the Object-capability model, the idea is that getting a direct reference to an object means simply that permission is granted. Objects from the scope of a different global will never get direct
references to each other. Instead we create a filtering wrapper for the object from the other
scope and reference that. Let’s see what that looks like.
Cross Compartment Wrappers

Each global object and every object in their scope ends up in the same memory block. These separated blocks of memory are called compartments. Different globals are always in different compartments. On the picture circles are JS objects, rectangles are cross compartment wrappers, filled circles are the global objects in the given compartment. It is really simple: whenever an object from compartment A gets a reference to an object from compartment B, xpconnect kicks in, creates a wrapper, and references that instead. Now whenever you try to do an operation on the given referenced object, the wrapper decides what to do. Basically there are two kind of wrapper behaviors. Filter and Xray. A particular wrapper can be both filtering and have xray behavior in the same time.

Filtering wrappers

The first one is really simple, it decides whether an action should be allowed or denied. For example the cross origin behavior is implemented with a filtering wrapper. Another example you might come across (if you plan to implement a Jetpack module at some point…) is that if you try to expose an object from chrome to content, by default none of the exposed object’s properties will be accessible to the content. If you want to expose some properties of your object (and unless it’s a function, you probably do…) you need to specify them with the help of the __exposedProps__ property on the chrome side. It’s easy to see that this is just another filtering wrapper.

Xray wrappers

There is a problem that cannot be solved with a filtering type of wrapper. When system code accesses the DOM of an untrusted site, because of the flexible nature of JavaScript, nothing guarantees that methods and properties are not mutated by the site. It’s not just a security problem, that the site can trick system code: system privilege will not be ‘leaked’ in this way. So even if system code calls content.document.getElementById(‘something’) and the site has replaced getElementById with some custom function to do something evil, that function will NOT be called with system privileges. So in a way we’re safe… But still, it would be nice if I could call the original getElementById reliably and get the expected result. Also we don’t want to cause any side effect on the site accidentally in this way.
Xray wrapper is designed to solve this problem. But to understand how it is possible, we need to look under the hood for a second to see how binding is implemented between a C++ instance and a JavaScript object. This is a very complex part of XPConnect: there are several ways to do it, there is a lot of optimization, currently a new DOM binding is being introduced… so I will not get too deep into this topic for now. From our perspective it does not matter. What is important is that there is a native layer here. By native I mean that it is implemented in C++. (Actually it might be implemented in JavaScript and then wrapped in a special wrapper to fake a native object, but let’s not go there for now…)
The good news that while JavaScript is very flexible when it comes to mutating instances at runtime, C++ is not. No one can change the vtable of a C++ instance during runtime, so we can safely assume that if we call a method on a C++ instance, it will do what we expect it to do. By now you probably guessed that what xray wrapper does: it sees through the JavaScript reflector object and calls the native method. But to make it less foggy, let me explain how this really works in a nutshell.
So we have an instance in C++, for example a WebSocket. It has C++ methods. To bind them to the JavaScript API we have a reflector class called XPCWrappedNative, that wraps the native instance and propagates the JavaScript calls to this instance. There is also a JavaScript object called the holder, which is the WebSocket object we see from JavaScript usually. The holder will either propagate the operation to the XPCWrappedNative, or if the operation is a mutation, this is the layer that will store it. So when someone adds a new property, it will be added to this holder (we call these expando properties). If someone overwrites a method, an expando property will be created on the holder which will shadow the native one.
What xray wrappers do is ignore this holder object with all its mutations and call the XPCWrappedNative directly.
Please note that this is an oversimplified version and the actual implementation is a lot more complex, but my goal is only to give some high level picture of what is going on, and more detail would just defeat that purpose.
Xray behavior

Jetpack content-scripts

Let me start with a scary looking picture I found in the Add-on SDK docs:

multiple-workers

I have to note first of all, that Content process and Add-on process are actually the same process, since the whole Firefox engine is pretty much single processed. At some point that was about to change, but right now it seems like it will not (at least not anytime soon). But the two sides in fact act like two separate processes from the add-on developer’s perspective: content scripts and workers communicate via messages, and that serves security purposes (and maybe in the future they will be separate processes, who knows?).
What’s important for us now is: where are the different scopes, or: where are the compartment boundaries? And what kind of privileges does each have, and what ‘side effects’ can we run into because of them? In the next section I collected a couple of examples, but let’s get the big picture first.
Everything on the right side lives in a chrome compartment. Actually each module has its own global, and all of them have system principal rights. For now: one global on the right side, with system level of privileges: that is, ‘it has access to everything’. On the left side each web page has its own global. In fact each iframe within the web pages has its own globals… Apart from that there is our content script that has the same origin by default as the web page it is ‘injected’ into. It has its own global and scope. Actually as I mentioned, content scripts in the future will have optionally slightly higher privileges than web sites.
Anyway, what is important now is to understand that each content script has its own global, and that by default it is using xray! The xray is to make your life better, but sometimes it can get in your way. You can kind of turn it off when you have to (although I don’t encourage anyone to do it, in some special cases I can see the use of it). The other important thing to note is that expandos of the content-script are invisible from the web-page, and the other way around too. This is very important, since if that were not the case, content scripts from different add-ons would get in conflict with each other very easily.
(If someone wondered, iframes with same origin can see each others expandos, otherwise the specs were broken, but I will not get into implementation details here how that problem is solved…)

Examples

When content scripts interact with web content, there are a few cases where you should be aware of the existence of these wrappers to understand what is going on. I will try to explain in each cases what is happening and why, and also where you need to work around unexpected behavior.

Expandos

As I mentioned earlier, web content and content scripts have their own separate globals and compartments for their objects. Since they have different globals, and their objects live in different compartments, they can interact with each others’ objects only via wrappers. So when you write something like ‘document’ or ‘window’ inside a content script, you can be sure you are working with a wrapper. Expando properties cannot be seen through this barrier, and global variables are actually expandos on the global… So if the web content sets a global variable or puts a new property on a DOM node for example, by default it cannot be seen from the content script.

Content

// Global variables
var div1 = document.getElementById("div1");
// Expando properties
div1.blah = "WAT?";

To make these expandos visible, there is a way to unwrap xray wrappers by accessing the wrappedJSObject property. Or in case of window there is a special unsafeWindow object for this purpose.

Content-script

// unsafeWindow:
unsafeWindow.div1;
// unwrapping
var div1 = document.getElementById("div1");
div1.wrappedJSObject.blah;

I don’t encourage anyone doing this trick in general. It can lead to all kinds of nasty surprises. But for example if we are the developer of an actual website, and just want to implement an add-on that is extending the capabilities of that particular site only… Why not?

Xray

In the next example I want to demonstrate what the xray behavior means in practice. Again, if you for some reason want to avoid this behavior you can by unwrapping…

Content

var div1 = document.getElementById("div1");
var origAppendChild = div1.appendChild.bind(div1);
// let's overwrite a well known DOM method on a node:
div1.appendChild = function(child) {
  // the modified version
  // first calls the original version
  origAppendChild(child);
 
  // then adds an extra text node to the document
  origAppendChild(
      document.createTextNode(
          "An additional text node."));
}
// this call will call the new version obviously
// and result an addition text node appended to 
// the document after the one we pass in as an argument.
div1.appendChild(
    document.createTextNode(
        "Child inserted from the content"));

‘document’ in the content script is an xray wrapper for the content’s document. Xray wrappers work in a transitive way: any object its properties or methods return is an xray wrapper too. If that logic is hard to follow then you can look at any node and ask: where does this node belong to? If it’s a node from the document of the content, and you are writing content script code, you can safely assume it’s a wrapper. Content scripts do not have their own document, only the document of the content, so the answer is really simple usually

Content-script

// div1 is an xray wrapper here since the node is from the document of the content
var div1 = document.getElementById("div1");
 
// because of the xray behavior this call will ignore the modified version of appendChild
// and will call the original one, so no extra text node will be appended, only the one we
// pass in as an argument
div1.appendChild(
    document.createTextNode(
        "Child appended from content.js"));

Events and callbacks

As an exercise here are three ways to add an image with an onclick listener to the content from a content script. And finally a 4th version that just injects the whole thing as an HTML string using innerHTML.

Content-script

function myFunction(){
    alert("WAT?");
}
var img = document.createElement('img');
img.src = "wat.jpeg";
 
// three alternative ways to add the event
// (obviously using only one of these at a time)
// version 1:
img.setAttribute("onclick", "myFunction()");
 
// version 2:
img.onclick = myFunction;
 
// version 3:
img.addEventListener("click", myFunction, false);
 
// finally adding the img to the document
document.getElementById("div1").appendChild(img);
 
// version 4: instead of using createElement/appendChild
// using innerHTML:
document.body.innerHTML = '<img onclick="myMethod()" src="wat.jpeg" />'

And here comes the solution and explanation. Version one will not work. Why? Because the event will be executed in the scope of the content. ‘document.createElement’ will create the element in the scope of the document which is the content. Now in the scope of the content there is simply no function with the name ‘myFunction’. In case you wonder if there is one with the same name accidentally, it will be called! Version 2 will work. There we pass in the ‘myFunction’ as an object not just its name, so it will be called (via a wrapper since from the scope of the content that object is only accessible via a wrapper). Version 3 will work just fine too. The fourth version will not work again for the same reason as for the first version.

“can’t access dead object”

Finally I wanted to take a look at this cute exception. You might bump into it in the future and there is some info out there about it already, but since at this point you know a bit about cross compartment wrappers already, it’s quite simple to fully understand what’s going on here. From GC perspective, sometimes it’s really hard to unload compartments, because of all the cross compartment references. So you close a tab for example but your add-on hooked in some listener to it’s window, or you disable an add-on, but some site holds a references to one of it’s object for some reason, and the GC will decide to leave all those object in that compartment alone in the end… And the user just experiences that the browser needs all the memory on the word and then some more (OK I’m exaggerating a bit).
In both cases (add-on gets disabled, window gets closed) what happens is that the compartment of the window or add-on, with all the objects it contains, is just getting nuked, and all the cross compartment wrappers of other compartments that points to some object from it is getting replaced by a stub wrapper, that basically just throws whatever you’re trying to do with it. For example: your add-on sets up an onclick listener on an image from the content, and the callback function is defined in the content-script. Then the user disables your add-on, and click the button. At this point your add-on and all it’s object included the callback function is just gone. The content had access to the callback function via a wrapper (since it’s a different scope/compartment), and that wrapper is replaced by this stub wrapper. So when the callback is tried to be called by content, it will try to access this stub and the “can’t access dead object” exception will be thrown. True story.
Well, this was it for now, I hope you enjoyed it, and I really hope it helps some people out there. If you have any questions, feel free to ping me any time on irc, or if you have some wrapper related bug to report please don’t hesitate to file a bug.

Categories: Uncategorized