Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / docs / scriptSecurity / xray_vision.rst
blob0e2eb2caf490ecb29d835baed7e0302b0e50b43c
1 Xray Vision
2 ===========
4 .. container:: summary
6    Xray vision helps JavaScript running in a privileged security context
7    safely access objects created by less privileged code, by showing the
8    caller only the native version of the objects.
10 Gecko runs JavaScript from a variety of different sources and at a
11 variety of different privilege levels.
13 -  The JavaScript code that along with the C++ core, implements the
14    browser itself is called *chrome code* and runs using system
15    privileges. If chrome-privileged code is compromised, the attacker
16    can take over the user's computer.
17 -  JavaScript loaded from normal web pages is called *content code*.
18    Because this code is being loaded from arbitrary web pages, it is
19    regarded as untrusted and potentially hostile, both to other websites
20    and to the user.
21 -  As well as these two levels of privilege, chrome code can create
22    sandboxes. The security principal  defined for the sandbox determines
23    its privilege level. If an
24    Expanded Principal is used, the sandbox is granted certain privileges
25    over content code and is protected from direct access by content
26    code.
28 | The security machinery in Gecko ensures that there's asymmetric access
29   between code at different privilege levels: so for example, content
30   code can't access objects created by chrome code, but chrome code can
31   access objects created by content.
32 | However, even the ability to access content objects can be a security
33   risk for chrome code. JavaScript's a highly malleable language.
34   Scripts running in web pages can add extra properties to DOM objects
35   (also known as expando properties)
36   and even redefine standard DOM objects to do something unexpected. If
37   chrome code relies on such modified objects, it can be tricked into
38   doing things it shouldn't.
39 | For example: ``window.confirm()`` is a DOM
40   API that's supposed to ask the user to confirm an action, and return a
41   boolean depending on whether they clicked "OK" or "Cancel". A web page
42   could redefine it to return ``true``:
44 .. code:: JavaScript
46    window.confirm = function() {
47      return true;
48    }
50 Any privileged code calling this function and expecting its result to
51 represent user confirmation would be deceived. This would be very naive,
52 of course, but there are more subtle ways in which accessing content
53 objects from chrome can cause security problems.
55 | This is the problem that Xray vision is designed to solve. When a
56   script accesses an object using Xray vision it sees only the native
57   version of the object. Any expandos are invisible, and if any
58   properties of the object have been redefined, it sees the original
59   implementation, not the redefined version.
60 | So in the example above, chrome code calling the content's
61   ``window.confirm()`` would get the original version of ``confirm()``,
62   not the redefined version.
64 .. note::
66    It's worth emphasizing that even if content tricks chrome into
67    running some unexpected code, that code does not run with chrome
68    privileges. So this is not a straightforward privilege escalation
69    attack, although it might lead to one if the chrome code is
70    sufficiently confused.
72 .. _How_you_get_Xray_vision:
74 How you get Xray vision
75 -----------------------
77 Privileged code automatically gets Xray vision whenever it accesses
78 objects belonging to less-privileged code. So when chrome code accesses
79 content objects, it sees them with Xray vision:
81 .. code:: JavaScript
83    // chrome code
84    var transfer = gBrowser.contentWindow.confirm("Transfer all my money?");
85    // calls the native implementation
87 .. note::
89    Note that using window.confirm() would be a terrible way to implement
90    a security policy, and is only shown here to illustrate how Xray
91    vision works.
93 .. _Waiving_Xray_vision:
95 Waiving Xray vision
96 -------------------
98 | Xray vision is a kind of security heuristic, designed to make most
99   common operations on untrusted objects simple and safe. However, there
100   are some operations for which they are too restrictive: for example,
101   if you need to see expandos on DOM objects. In cases like this you can
102   waive Xray protection, but then you can no longer rely on any
103   properties or functions being, or doing, what you expect. Any of them,
104   even setters and getters, could have been redefined by untrusted code.
105 | To waive Xray vision for an object you can use
106   Components.utils.waiveXrays(object),
107   or use the object's ``wrappedJSObject`` property:
109 .. code:: JavaScript
111    // chrome code
112    var waivedWindow = Components.utils.waiveXrays(gBrowser.contentWindow);
113    var transfer = waivedWindow.confirm("Transfer all my money?");
114    // calls the redefined implementation
116 .. code:: JavaScript
118    // chrome code
119    var waivedWindow = gBrowser.contentWindow.wrappedJSObject;
120    var transfer = waivedWindow.confirm("Transfer all my money?");
121    // calls the redefined implementation
123 Waivers are transitive: so if you waive Xray vision for an object, then
124 you automatically waive it for all the object's properties. For example,
125 ``window.wrappedJSObject.document`` gets you the waived version of
126 ``document``.
128 To undo the waiver again, call Components.utils.unwaiveXrays(waivedObject):
130 .. code:: JavaScript
132    var unwaived = Components.utils.unwaiveXrays(waivedWindow);
133    unwaived.confirm("Transfer all my money?");
134    // calls the native implementation
136 .. _Xrays_for_DOM_objects:
138 Xrays for DOM objects
139 ---------------------
141 The primary use of Xray vision is for DOM objects: that is, the
142 objects that represent parts of the web page.
144 In Gecko, DOM objects have a dual representation: the canonical
145 representation is in C++, and this is reflected into JavaScript for the
146 benefit of JavaScript code. Any modifications to these objects, such as
147 adding expandos or redefining standard properties, stays in the
148 JavaScript reflection and does not affect the C++ representation.
150 The dual representation enables an elegant implementation of Xrays: the
151 Xray just directly accesses the C++ representation of the original
152 object, and doesn't go to the content's JavaScript reflection at all.
153 Instead of filtering out modifications made by content, the Xray
154 short-circuits the content completely.
156 This also makes the semantics of Xrays for DOM objects clear: they are
157 the same as the DOM specification, since that is defined using the
158 `WebIDL <http://www.w3.org/TR/WebIDL/>`__, and the WebIDL also defines
159 the C++ representation.
161 .. _Xrays_for_JavaScript_objects:
163 Xrays for JavaScript objects
164 ----------------------------
166 Until recently, built-in JavaScript objects that are not part of the
167 DOM, such as
168 ``Date``, ``Error``, and ``Object``, did not get Xray vision when
169 accessed by more-privileged code.
171 Most of the time this is not a problem: the main concern Xrays solve is
172 with untrusted web content manipulating objects, and web content is
173 usually working with DOM objects. For example, if content code creates a
174 new ``Date`` object, it will usually be created as a property of a DOM
175 object, and then it will be filtered out by the DOM Xray:
177 .. code:: JavaScript
179    // content code
181    // redefine Date.getFullYear()
182    Date.prototype.getFullYear = function() {return 1000};
183    var date = new Date();
185 .. code:: JavaScript
187    // chrome code
189    // contentWindow is an Xray, and date is an expando on contentWindow
190    // so date is filtered out
191    gBrowser.contentWindow.date.getFullYear()
192    // -> TypeError: gBrowser.contentWindow.date is undefined
194 The chrome code will only even see ``date`` if it waives Xrays, and
195 then, because waiving is transitive, it should expect to be vulnerable
196 to redefinition:
198 .. code:: JavaScript
200    // chrome code
202    Components.utils.waiveXrays(gBrowser.contentWindow).date.getFullYear();
203    // -> 1000
205 However, there are some situations in which privileged code will access
206 JavaScript objects that are not themselves DOM objects and are not
207 properties of DOM objects. For example:
209 -  the ``detail`` property of a CustomEvent fired by content could be a JavaScript
210    Object or Date as well as a string or a primitive
211 -  the return value of ``evalInSandbox()`` and any properties attached to the
212    ``Sandbox`` object may be pure JavaScript objects
214 Also, the WebIDL specifications are starting to use JavaScript types
215 such as ``Date`` and ``Promise``: since WebIDL definition is the basis
216 of DOM Xrays, not having Xrays for these JavaScript types starts to seem
217 arbitrary.
219 So, in Gecko 31 and 32 we've added Xray support for most JavaScript
220 built-in objects.
222 Like DOM objects, most JavaScript built-in objects have an underlying
223 C++ state that is separate from their JavaScript representation, so the
224 Xray implementation can go straight to the C++ state and guarantee that
225 the object will behave as its specification defines:
227 .. code:: JavaScript
229    // chrome code
231    var sandboxScript = 'Date.prototype.getFullYear = function() {return 1000};' +
232                        'var date = new Date(); ';
234    var sandbox = Components.utils.Sandbox("https://example.org/");
235    Components.utils.evalInSandbox(sandboxScript, sandbox);
237    // Date objects are Xrayed
238    console.log(sandbox.date.getFullYear());
239    // -> 2014
241    // But you can waive Xray vision
242    console.log(Components.utils.waiveXrays(sandbox.date).getFullYear());
243    // -> 1000
245 .. note::
247    To test out examples like this, you can use the Scratchpad in
248    browser context
249    for the code snippet, and the Browser Console to see the expected
250    output.
252    Because code running in Scratchpad's browser context has chrome
253    privileges, any time you use it to run code, you need to understand
254    exactly what the code is doing. That includes the code samples in
255    this article.
257 .. _Xray_semantics_for_Object_and_Array:
259 Xray semantics for Object and Array
260 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
262 The exceptions are ``Object``
263 and ``Array``: their interesting state is in JavaScript, not C++. This
264 means that the semantics of their Xrays have to be independently
265 defined: they can't simply be defined as "the C++ representation".
267 The aim of Xray vision is to make most common operations simple and
268 safe, avoiding the need to access the underlying object except in more
269 involved cases. So the semantics defined for ``Object`` and ``Array``
270 Xrays aim to make it easy for privileged code to treat untrusted objects
271 like simple dictionaries.
273 Any value properties
274 of the object are visible in the Xray. If the object has properties
275 which are themselves objects, and these objects are same-origin with the
276 content, then their value properties are visible as well.
278 There are two main sorts of restrictions:
280 -  First, the chrome code might expect to rely on the prototype's
281    integrity, so the object's prototype is protected:
283    -  the Xray has the standard ``Object`` or ``Array`` prototype,
284       without any modifications that content may have done to that
285       prototype. The Xray always inherits from this standard prototype,
286       even if the underlying instance has a different prototype.
287    -  if a script has created a property on an object instance that
288       shadows a property on the prototype, the shadowing property is not
289       visible in the Xray
291 -  Second, we want to prevent the chrome code from running content code,
292    so functions and accessor properties
293    of the object are not visible in the Xray.
295 These rules are demonstrated in the script below, which evaluates a
296 script in a sandbox, then examines the object attached to the sandbox.
298 .. note::
300    To test out examples like this, you can use the Scratchpad in
301    browser context  for the code snippet, and the Browser Console
302    to see the expected output.
304    Because code running in Scratchpad's browser context has chrome
305    privileges, any time you use it to run code, you need to understand
306    exactly what the code is doing. That includes the code samples in
307    this article.
309 .. code:: JavaScript
311    /*
312    The sandbox script:
313    * redefines Object.prototype.toSource()
314    * creates a Person() constructor that:
315      * defines a value property "firstName" using assignment
316      * defines a value property which shadows "constructor"
317      * defines a value property "address" which is a simple object
318      * defines a function fullName()
319    * using defineProperty, defines a value property on Person "lastName"
320    * using defineProperty, defines an accessor property on Person "middleName",
321    which has some unexpected accessor behavior
322    */
324    var sandboxScript = 'Object.prototype.toSource = function() {'+
325                        '  return "not what you expected?";' +
326                        '};' +
327                        'function Person() {' +
328                        '  this.constructor = "not a constructor";' +
329                        '  this.firstName = "Joe";' +
330                        '  this.address = {"street" : "Main Street"};' +
331                        '  this.fullName = function() {' +
332                        '    return this.firstName + " " + this.lastName;'+
333                        '  };' +
334                        '};' +
335                        'var me = new Person();' +
336                        'Object.defineProperty(me, "lastName", {' +
337                        '  enumerable: true,' +
338                        '  configurable: true,' +
339                        '  writable: true,' +
340                        '  value: "Smith"' +
341                        '});' +
342                        'Object.defineProperty(me, "middleName", {' +
343                        '  enumerable: true,' +
344                        '  configurable: true,' +
345                        '  get: function() { return "wait, is this really a getter?"; }' +
346                        '});';
348    var sandbox = Components.utils.Sandbox("https://example.org/");
349    Components.utils.evalInSandbox(sandboxScript, sandbox);
351    // 1) trying to access properties in the prototype that have been redefined
352    // (non-own properties) will show the original 'native' version
353    // note that functions are not included in the output
354    console.log("1) Property redefined in the prototype:");
355    console.log(sandbox.me.toSource());
356    // -> "({firstName:"Joe", address:{street:"Main Street"}, lastName:"Smith"})"
358    // 2) trying to access properties on the object that shadow properties
359    // on the prototype will show the original 'native' version
360    console.log("2) Property that shadows the prototype:");
361    console.log(sandbox.me.constructor);
362    // -> function()
364    // 3) value properties defined by assignment to this are visible:
365    console.log("3) Value property defined by assignment to this:");
366    console.log(sandbox.me.firstName);
367    // -> "Joe"
369    // 4) value properties defined using defineProperty are visible:
370    console.log("4) Value property defined by defineProperty");
371    console.log(sandbox.me.lastName);
372    // -> "Smith"
374    // 5) accessor properties are not visible
375    console.log("5) Accessor property");
376    console.log(sandbox.me.middleName);
377    // -> undefined
379    // 6) accessing a value property of a value-property object is fine
380    console.log("6) Value property of a value-property object");
381    console.log(sandbox.me.address.street);
382    // -> "Main Street"
384    // 7) functions defined on the sandbox-defined object are not visible in the Xray
385    console.log("7) Call a function defined on the object");
386    try {
387      console.log(sandbox.me.fullName());
388    }
389    catch (e) {
390      console.error(e);
391    }
392    // -> TypeError: sandbox.me.fullName is not a function
394    // now with waived Xrays
395    console.log("Now with waived Xrays");
397    console.log("1) Property redefined in the prototype:");
398    console.log(Components.utils.waiveXrays(sandbox.me).toSource());
399    // -> "not what you expected?"
401    console.log("2) Property that shadows the prototype:");
402    console.log(Components.utils.waiveXrays(sandbox.me).constructor);
403    // -> "not a constructor"
405    console.log("3) Accessor property");
406    console.log(Components.utils.waiveXrays(sandbox.me).middleName);
407    // -> "wait, is this really a getter?"
409    console.log("4) Call a function defined on the object");
410    console.log(Components.utils.waiveXrays(sandbox.me).fullName());
411    // -> "Joe Smith"