An overview of the script security architecture in Gecko.
Gecko implements the following security policy:
- Objects that are same-origin are able to access each other freely. For example, the objects associated with a document served from https://example.org/ can access each other, and can also access objects served from https://example.org/foo.
- Objects that are cross-origin get highly restricted access to each other, according to the same-origin policy. For example, code served from https://example.org/ trying to access objects from https://somewhere-else.org/.
- Objects in a less privileged scope don't get any access to objects in a more-privileged scope, unless the more-privileged scope explicitly clones those objects. For example, web content accessing objects in a chrome-privileged scope.
Compartments are the foundation for Gecko's script security architecture. A compartment is a specific, separate area of memory. In Gecko, there's a separate compartment for every global object. This means that each global object and the objects associated with it live in their own region of memory.
Inside the same compartment, all objects share a global and are therefore same-origin with each other. Therefore there's no need for any security checks, there are no wrappers, and there is no performance overhead for the common case of objects in a single window interacting with each other.
Whenever cross-compartment access happens, the wrappers enable us to implement the appropriate security policy. Because the wrapper we choose is specific to the relationship between the two compartments, the security policy it implements can be static: when the caller uses the wrapper, there's no need to check who is making the call or where it is going.
As we've already seen, the most common scenario for same-origin access is where objects belonging to the same window object interact. This all takes place within the same compartment, with no need for security checks or wrappers.
When objects share an origin but not a global - for example two web pages from the same protocol, port, and domain - they belong to two different compartments, and the caller gets a transparent wrapper to the target object.
Transparent wrappers allow access to all the target's properties: functionally, it's just as if the target is in the caller's compartment.
If the two compartments are cross-origin, the caller gets a cross-origin wrapper.
Privileged to unprivileged code
The most obvious example of this kind of security relation is between system-privileged chrome code and untrusted web content, but there are other examples in Gecko. The Add-on SDK runs content scripts in sandboxes which are initialized with an expanded principal, giving them elevated privileges with respect to the web content they operate on, but reduced privileges with respect to chrome.
If the caller has a higher privilege than the target object, the caller gets an Xray wrapper for the object.
Xrays are designed to prevent untrusted code from confusing trusted code by redefining objects in unexpected ways. For example, privileged code using an Xray to a DOM object sees only the original, native version of the DOM object. Any expando properties are not visible, and if any native DOM properties have been redefined, this is not visible in the Xray.
The privileged code is able to waive Xrays if it wants unfiltered access to the untrusted object.
See Xray vision for much more information on Xrays.
Unprivileged to privileged code
If the caller has lower privileges than the target object, then the caller gets an opaque wrapper.
An opaque wrapper denies all access to the target object.
However, the privileged target is able to copy objects and functions into the less-privileged scope using the
cloneInto() functions, and the less-privileged scope is then able to use them.
To determine the security relation between two compartments, Gecko uses two concepts: security principals and the act of subsuming. To establish the security relationship between two compartments A and B, Gecko asks:
Does the security principal for compartment A subsume the security principal for compartment B, and vice versa?
|A subsumes B||A has all of the privileges of B, and possibly more, and therefore A is allowed to see and do anything that B can see and do.|
|A Subsumes B && B Subsumes A||A and B are same-origin.|
|A Subsumes B && B !Subsumes A||
A is more privileged than B.
A gets access to all of B, by default with Xray vision, which it may choose to waive.
B gets no access to A, although A may choose to export objects to B.
|A !Subsumes B && B !Subsumes A||A and B are cross-origin.|
The system principal passes all security checks. It subsumes itself and all other principals. Chrome code, by definition, runs with the system principal, as do frame scripts.
A content principal is associated with some web content and is defined by the origin of the content. For example, a normal DOM window has a content principal defined by the window's origin. A content principal subsumes only other content principals with the same origin. It is subsumed by the system principal, any expanded principals which include its origin, and any other content principals with the same origin.
An expanded principal is specified as an array of origins:
The expanded principal subsumes every content principal it contains. The content principals do not subsume the expanded principal, even if the expanded principal only contains a single content principal.
"http://moz.org" but not vice versa: so the expanded principal gets full access to the content principals it contains, with Xray vision by default, and the content principals get no access to the expanded principal.
Expanded principals are useful when we want to give code extra privileges, including cross-origin access, but don't want to give it full system privileges. For example, they're used in the Add-on SDK to give content scripts cross-domain privileges for a predefined set of domains, and to protect content scripts form access by untrusted web content, without having to give content scripts system privileges.
The null principal fails almost all security checks. It has no privileges and can't be accessed by anything but itself and chrome. It subsumes no other principals, even other null principals. (This is what is used when HTML5 and other specs say "origin is a globally unique identifier".)
The diagram below summarizes the relationships between the different principals. The arrow connecting principals A and B means "A subsumes B":
Computing a wrapper
The following diagram shows the factors that determine what kind of wrapper compartment A would get when trying to access an object in compartment B.