Problems of access control lists. In a typical operating system, each process runs as a particular user. Each time the process attempts to access a system resource, such as a file, the system performs a security check. It compares the process's user id against an access control list to see if access should be permitted. Using this approach in the browser presents several problems:
- It would require a security check every time a script attempted to access a property of a DOM node. The number of security checks would be so great as to slow down the browser.
Overview of object capabilities. Gecko uses an object-capability security model instead. By default, when code accesses objects, there are no security checks. A reference to an object is essentially permission to access all properties and methods of that object.
What would happen if compartmentalization were to break down? Suppose domain 2 obtained a reference to object in the chrome compartment. Then code in domain 2 could access the properties and methods of that object without security checks. Furthermore, domain 2 could use those properties to obtain references to other chrome objects. So domain 2 would have unconstrained access to every chrome object reachable from the first one. (Note that though it is not drawn this way, almost every object in a graph holds a strong reference to the corresponding global object through the object's scope chain.)
Conversely, suppose chrome obtained a reference to an object in domain 2. This could be harmless as long as chrome treated the object with great care. But there are several major dangers. First, any time the chrome object touches the object from domain 2, a getter or setter might be called. Second, as discussed above, chrome cannot trust any information it gets from the object, since methods and properties with standard behavior may have been overwritten. Third, the leak naturally widens: any objects returned by the leaked object's methods or getters will also likely be from domain 2; and any objects assigned to properties of the leaked object, or arguments chrome passes to its methods, cause domain 2 to have references back to chrome objects.
Therefore, when an object must be exposed to code running in a different compartment, it is exposed only by proxy. A wrapper object is introduced into the compartment. The wrapper object holds a strong reference (in the garbage collection sense) across the compartment boundary, but it also enforces the appropriate security policy, so code in that compartment does not necessarily have unrestricted access to the underlying object. In the illustration below, a chrome object has been exposed to code in domain 1.
Membranes. Suppose a privileged compartment calls a method on an object in another compartment. Consider how this method call must proceed, in order to keep the compartments isolated.
The underlying method will of course run in the underlying object's compartment. So any objects that are passed to the method as arguments must not be passed directly. That would violate compartment isolation. Instead they must be proxied for that compartment. It is likely that one or more arguments were already wrappers of objects from that compartment. This is at least true for the
this argument, ordinarily. Those objects should be unwrapped, since they are being passed back into their compartment of origin.
When the method returns, if the return value is an object, it must be wrapped or unwrapped for the caller's compartment. The same goes for any exception thrown.
So as methods are called back and forth, the wrappers spread out as needed along the boundary to keep the compartments separate. This slim, porous, but non-pierceable layer of wrapper objects is what we mean by a membrane. The membrane defines and implements the boundary between compartments.
All wrappers have a lot of behavior in common. Every wrapper wraps a single object, for example, and the wrapper never changes to wrap something else. When calling a getter, setter, or method of a wrapper, the wrapper always takes certain steps to keep the membrane whole (that is, to avoid leaking objects across the boundary). (But see bug 565730 and bug 565735.)
Wrapper types differ in three ways.
- Access policy - Some wrappers check, on each property access, whether the compartment where the wrapper lives is permitted to access that property of the wrappee. Some don't. Those that do enforce a variety of policies.
- Delegation - When you get or set a property of a wrapper, sometimes that operation is transparently delegated to the wrappee. In other cases, the operation is actually delegated to an underlying XPCOM object instead. For details on this, see the section on XPCNativeWrapper below.
- Reverse wrapper - Every membrane has two sides. As described above, values passed to and from a wrapper's methods, getters, and setters must be rewrapped for the destination compartment. Different wrappers rewrap objects passed as arguments in different ways.
(Warning: This is too optimistic.
XPCNativeWrapper differs in an additional way, in that
xpcnw.foo = 1 creates a new own property of
xpcnw itself. The others never really have own properties. And
SystemOnlyWrappers are rather different from the other four types too.)
The table below explains (most of) the differences among the security wrapper types.
||content||chrome, usually||checks through security manager||X-ray||COW|
||content||chrome, usually||checks through security manager||transparent||COW|
||content||content||same origin policy plus whitelist||
X-ray, usually; but transparent if same-origin
||native anonymous content||everyone||by source URI||transparent||none|
someAnchor.href = "http://www.mozilla.com" operating on a content element. The content code may have defined an evil setter on someAnchor.href to prevent this from working correctly. If someAnchor is wrapped by an XPCNativeWrapper, this will not happen. The wrapper will always perform the original action, calling the
SetHref method of the underlying XPCOM object.
Intra-compartment XPCNW. XPCNativeWrapper objects can also be created explicitly by using
new XPCNativeWrapper(wrappedNative). In this case, the XPCNW does not act as a security wrapper. Both the wrapper and the wrappee are in the same compartment, so the security aspect of the XPCNW is moot. This is useful only to get the x-ray behavior.
Safe JSObject wrappers are implicitly created in some circumstances. All properties are visible through this wrapper.
The cross-origin wrapper, or XOW, brokers access between web sites. This wrapper is implicitly created by XPConnect.
If a XOW wrapper and the object it wraps happen to be from the same origin, then the XOW acts like a SafeJSObjectWrapper. It becomes as transparent as possible.
If the underlying object is not from the same origin, then the XOW checks every property access against a short whitelist of properties and methods that are exposed across domains, as special exceptions to the Same Origin Policy. When access to a property or method is granted, the XOW acts like an XPCNativeWrapper: it calls the method or accessor of the underlying XPCOM object, as defined by IDL, seeing through whatever the other site may have done to the JS object.