Bug 1722754 - Mechanically import old MDN docs. r=mccr8
Generated with: pandoc -t rst https://raw.githubusercontent.com/mdn/archived-content/main/files/en-us/mozilla/gecko/script_security/index.html > index.rst pandoc -t rst https://raw.githubusercontent.com/mdn/archived-content/b7b6516b2b522533e8ba51cb944700ace7dace4d/files/en-us/mozilla/tech/xray_vision/index.html > xray_vision.rst Per the instructions at: https://firefox-source-docs.mozilla.org/tools/moztreedocs/mdn-import.html Images refetched from archive.org. Differential Revision: https://phabricator.services.mozilla.com/D121116
После Ширина: | Высота: | Размер: 203 KiB |
После Ширина: | Высота: | Размер: 276 KiB |
После Ширина: | Высота: | Размер: 52 KiB |
После Ширина: | Высота: | Размер: 55 KiB |
После Ширина: | Высота: | Размер: 54 KiB |
После Ширина: | Высота: | Размер: 144 KiB |
После Ширина: | Высота: | Размер: 55 KiB |
После Ширина: | Высота: | Размер: 54 KiB |
|
@ -0,0 +1,340 @@
|
|||
--- title: Script security slug: Mozilla/Gecko/Script_security tags: -
|
||||
Security ---
|
||||
|
||||
.. container:: summary
|
||||
|
||||
This page provides an overview of the script security architecture in
|
||||
Gecko.
|
||||
|
||||
Like any web browser, Gecko can load JavaScript from untrusted and
|
||||
potentially hostile web pages and run it on the user's computer. The
|
||||
security model for web content is based on the `same-origin
|
||||
policy </en-US/docs/Web/Security/Same-origin_policy>`__, in which code
|
||||
gets full access to objects from its origin but highly restricted access
|
||||
to objects from a different origin. The rules for determining whether an
|
||||
object is same-origin with another, and what access is allowed
|
||||
cross-origin, are now mostly standardized across browsers.
|
||||
|
||||
Gecko has an additional problem, though: while its core is written in
|
||||
C++, the front-end code is written in JavaScript. This JavaScript code,
|
||||
which is commonly referred to as c\ *hrome code*, runs with system
|
||||
privileges. If the code is compromised, the attacker can take over the
|
||||
user's computer. Legacy `SDK extensions </en-US/Add-ons/SDK>`__ also run
|
||||
with chrome privileges.
|
||||
|
||||
Having the browser front end in JavaScript has benefits: it can be much
|
||||
quicker to develop in JavaScript than in C++, and contributors do not
|
||||
need to learn C++. However, JavaScript is a highly dynamic, malleable
|
||||
language, and without help it's difficult to write system-privileged
|
||||
code that interacts safely with untrusted web content. From the point of
|
||||
view of chrome code, the script security model in Gecko is intended to
|
||||
provide that help to make writing secure, system-privileged JavaScript a
|
||||
realistic expectation.
|
||||
|
||||
.. _Security_policy:
|
||||
|
||||
Security policy
|
||||
---------------
|
||||
|
||||
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 they 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 </en-US/docs/Web/Security/Same-origin_policy#Cross-origin_script_API_access>`__.
|
||||
For example, code served from *https://example.org/* trying to access
|
||||
objects from *https://somewhere-else.org/* will have restricted
|
||||
access.
|
||||
- **Objects in a privileged scope** are allowed complete access to
|
||||
objects in a less privileged scope, but by default they see a
|
||||
`restricted
|
||||
view </en-US/docs/Mozilla/Gecko/Script_security#Privileged_to_unprivileged_code>`__
|
||||
of such objects, designed to prevent them from being tricked by the
|
||||
untrusted code. An example of this scope is chrome-privileged
|
||||
JavaScript accessing web content.
|
||||
- **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 </en-US/docs/Mozilla/Gecko/Script_security#Unprivileged_to_privileged_code>`__.
|
||||
An example of this scope is web content accessing objects in a
|
||||
chrome-privileged scope.
|
||||
|
||||
.. _Compartments:
|
||||
|
||||
Compartments
|
||||
------------
|
||||
|
||||
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.
|
||||
|
||||
.. image:: https://mdn.mozillademos.org/files/9697/compartments.png
|
||||
|
||||
Normal content windows are globals, of course, but so are chrome
|
||||
windows, `sandboxes </en-US/docs/Components.utils.Sandbox>`__,
|
||||
`workers </en-US/docs/Web/API/Worker>`__, the
|
||||
``ContentFrameMessageManager`` in a `frame
|
||||
script </en-US/Firefox/Multiprocess_Firefox/Frame_script_environment>`__,
|
||||
and so on.
|
||||
|
||||
Gecko guarantees that JavaScript code running in a given compartment is
|
||||
only allowed to access objects in the same compartment. When code from
|
||||
compartment A tries to access an object in compartment B, Gecko gives it
|
||||
a *cross-compartment wrapper*. This is a proxy in compartment A for the
|
||||
real object, which lives in compartment B.
|
||||
|
||||
.. image:: https://mdn.mozillademos.org/files/9729/cross-compartment-wrapper.png
|
||||
|
||||
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.
|
||||
|
||||
.. _Cross-compartment_access:
|
||||
|
||||
Cross-compartment access
|
||||
------------------------
|
||||
|
||||
.. _Same-origin:
|
||||
|
||||
Same-origin
|
||||
~~~~~~~~~~~
|
||||
|
||||
As we've already seen, the most common scenario for same-origin access
|
||||
is when 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.
|
||||
|
||||
.. image:: https://mdn.mozillademos.org/files/9735/same-origin-wrapper.png
|
||||
|
||||
Transparent wrappers allow access to all the target's properties:
|
||||
functionally, it's as if the target is in the caller's compartment.
|
||||
|
||||
.. _Cross-origin:
|
||||
|
||||
Cross-origin
|
||||
~~~~~~~~~~~~
|
||||
|
||||
If the two compartments are cross-origin, the caller gets a
|
||||
*cross-origin wrapper*.
|
||||
|
||||
.. image:: https://mdn.mozillademos.org/files/9731/cross-origin-wrapper.png
|
||||
|
||||
This denies access to all the object's properties, except for a few
|
||||
properties of ```Window`` </en-US/docs/Web/API/Window>`__ and
|
||||
```Location`` </en-US/docs/Web/API/Location>`__ objects, as defined by
|
||||
the `same-origin
|
||||
policy </en-US/docs/Web/Security/Same-origin_policy#Cross-origin_script_API_access>`__.
|
||||
|
||||
.. _Privileged_to_unprivileged_code:
|
||||
|
||||
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 </en-US/Add-ons/SDK/Guides/Content_Scripts>`__ in
|
||||
`sandboxes </en-US/docs/Components.utils.Sandbox>`__, which are
|
||||
initialized with an `expanded
|
||||
principal </en-US/docs/Mozilla/Gecko/Script_security#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.
|
||||
|
||||
.. image:: https://mdn.mozillademos.org/files/9737/xray-wrapper.png
|
||||
|
||||
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 version of the DOM
|
||||
object. Any
|
||||
`expando <https://developer.mozilla.org/en-US/docs/Glossary/Expando>`__
|
||||
properties are not visible, and if any native DOM properties have been
|
||||
redefined, they are not visible in the Xray.
|
||||
|
||||
The privileged code is able to `waive
|
||||
Xrays </en-US/docs/Components.utils.waiveXrays>`__ if it wants
|
||||
unfiltered access to the untrusted object.
|
||||
|
||||
See `Xray vision </en-US/docs/Xray_vision>`__ for much more information
|
||||
on Xrays.
|
||||
|
||||
.. _Unprivileged_to_privileged_code:
|
||||
|
||||
Unprivileged to privileged code
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If the caller has lower privileges than the target object, then the
|
||||
caller gets an *opaque wrapper.*
|
||||
|
||||
.. image:: https://mdn.mozillademos.org/files/9733/opaque-wrapper.png
|
||||
|
||||
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
|
||||
```exportFunction()`` </en-US/docs/Components.utils.exportFunction>`__
|
||||
and ```cloneInto()`` </en-US/docs/Components.utils.cloneInto>`__
|
||||
functions, and the less privileged scope is then able to use them.
|
||||
|
||||
.. _Security_checks:
|
||||
|
||||
Security checks
|
||||
---------------
|
||||
|
||||
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?*
|
||||
|
||||
.. _Subsumes:
|
||||
|
||||
Subsumes
|
||||
~~~~~~~~
|
||||
|
||||
+-----------------------------------+-----------------------------------+
|
||||
| *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. |
|
||||
+-----------------------------------+-----------------------------------+
|
||||
|
||||
.. _Security_principals:
|
||||
|
||||
Security principals
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. container::
|
||||
|
||||
There are four types of security principal: the system principal,
|
||||
content principals, expanded principals, and the null principal.
|
||||
|
||||
.. container::
|
||||
|
||||
.. _System_principal:
|
||||
|
||||
System principal
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
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 </en-US/Firefox/Multiprocess_Firefox/Frame_script_environment>`__.
|
||||
|
||||
.. _Content_principal:
|
||||
|
||||
Content principal
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
A content principal is associated with some web content and is defined
|
||||
by the
|
||||
`origin </en-US/docs/Web/Security/Same-origin_policy#Definition_of_an_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 that include its origin, and any
|
||||
other content principals with the same origin.
|
||||
|
||||
.. _Expanded_principal:
|
||||
|
||||
Expanded principal
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
An expanded principal is specified as an array of origins:
|
||||
|
||||
.. code:: brush:
|
||||
|
||||
["http://mozilla.org", "http://moz.org"]
|
||||
|
||||
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.
|
||||
|
||||
Thus ``["http://moz.org"]`` subsumes ``"http://moz.org"`` but not vice
|
||||
versa. 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.
|
||||
|
||||
This also enables the script security model to treat compartments that
|
||||
have expanded principals more like part of the browser than like web
|
||||
content. This means, for example, that it can run when JavaScript is
|
||||
disabled for web content.
|
||||
|
||||
Expanded principals are useful when you want to give code extra
|
||||
privileges, including cross-origin access, but don't want to give the
|
||||
code full system privileges. For example, expanded principals are used
|
||||
in the `Add-on SDK <https://developer.mozilla.org/en-US/Add-ons/SDK>`__
|
||||
to give content scripts `cross-domain privileges for a predefined set of
|
||||
domains <https://developer.mozilla.org/en-US/Add-ons/SDK/Guides/Content_Scripts/Cross_Domain_Content_Scripts>`__,
|
||||
and to protect content scripts from access by untrusted web content,
|
||||
without having to give content scripts system privileges.
|
||||
|
||||
.. _Null_principal:
|
||||
|
||||
Null principal
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
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".)
|
||||
|
||||
.. _Principal_relationships:
|
||||
|
||||
Principal relationships
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The diagram below summarizes the relationships between the different
|
||||
principals. The arrow connecting principals A and B means "A subsumes
|
||||
B". (A is the start of the arrow, and B is the end.)
|
||||
|
||||
.. image:: https://mdn.mozillademos.org/files/9799/principal-relationships.png
|
||||
|
||||
.. _Computing_a_wrapper:
|
||||
|
||||
Computing a wrapper
|
||||
-------------------
|
||||
|
||||
The following diagram shows the factors that determine the kind of
|
||||
wrapper that compartment A would get when trying to access an object in
|
||||
compartment B.
|
||||
|
||||
.. image:: https://mdn.mozillademos.org/files/9801/computing-a-wrapper.png
|
|
@ -0,0 +1,431 @@
|
|||
--- title: Xray vision slug: Mozilla/Tech/Xray_vision tags: - Gecko -
|
||||
X-Ray Vision - XPCOM ---
|
||||
|
||||
.. container:: summary
|
||||
|
||||
Xray vision helps JavaScript running in a privileged security context
|
||||
safely access objects created by less privileged code, by showing the
|
||||
caller only the native version of the objects.
|
||||
|
||||
Gecko runs JavaScript from a variety of different sources and at a
|
||||
variety of different privilege levels.
|
||||
|
||||
- The JavaScript code that along with the C++ core, implements the
|
||||
browser itself is called *chrome code* and runs using system
|
||||
privileges. If chrome-privileged code is compromised, the attacker
|
||||
can take over the user's computer.
|
||||
- JavaScript loaded from normal web pages is called *content code*.
|
||||
Because this code is being loaded from arbitrary web pages, it is
|
||||
regarded as untrusted and potentially hostile, both to other websites
|
||||
and to the user.
|
||||
- As well as these two levels of privilege, chrome code can create
|
||||
`sandboxes </en-US/docs/Components.utils.Sandbox>`__. The `security
|
||||
principal </en-US/docs/Components.utils.Sandbox#Sandbox_principal>`__
|
||||
defined for the sandbox determines its privilege level. If an
|
||||
Expanded Principal is used, the sandbox is granted certain privileges
|
||||
over content code and is protected from direct access by content
|
||||
code.
|
||||
|
||||
| The security machinery in Gecko ensures that there's asymmetric access
|
||||
between code at different privilege levels: so for example, content
|
||||
code can't access objects created by chrome code, but chrome code can
|
||||
access objects created by content.
|
||||
| However, even the ability to access content objects can be a security
|
||||
risk for chrome code. JavaScript's a highly malleable language.
|
||||
Scripts running in web pages can add extra properties to DOM objects
|
||||
(also known as `expando properties </en-US/docs/Glossary/Expando>`__)
|
||||
and even redefine standard DOM objects to do something unexpected. If
|
||||
chrome code relies on such modified objects, it can be tricked into
|
||||
doing things it shouldn't.
|
||||
| For example:
|
||||
```window.confirm()`` </en-US/docs/Web/API/Window.confirm>`__ is a DOM
|
||||
API that's supposed to ask the user to confirm an action, and return a
|
||||
boolean depending on whether they clicked "OK" or "Cancel". A web page
|
||||
could redefine it to return ``true``:
|
||||
|
||||
.. code:: brush:
|
||||
|
||||
window.confirm = function() {
|
||||
return true;
|
||||
}
|
||||
|
||||
Any privileged code calling this function and expecting its result to
|
||||
represent user confirmation would be deceived. This would be very naive,
|
||||
of course, but there are more subtle ways in which accessing content
|
||||
objects from chrome can cause security problems.
|
||||
|
||||
| This is the problem that Xray vision is designed to solve. When a
|
||||
script accesses an object using Xray vision it sees only the native
|
||||
version of the object. Any expandos are invisible, and if any
|
||||
properties of the object have been redefined, it sees the original
|
||||
implementation, not the redefined version.
|
||||
| So in the example above, chrome code calling the content's
|
||||
``window.confirm()`` would get the original version of ``confirm()``,
|
||||
not the redefined version.
|
||||
|
||||
.. note::
|
||||
|
||||
It's worth emphasizing that even if content tricks chrome into
|
||||
running some unexpected code, that code does not run with chrome
|
||||
privileges. So this is not a straightforward privilege escalation
|
||||
attack, although it might lead to one if the chrome code is
|
||||
sufficiently confused.
|
||||
|
||||
.. _How_you_get_Xray_vision:
|
||||
|
||||
How you get Xray vision
|
||||
-----------------------
|
||||
|
||||
Privileged code automatically gets Xray vision whenever it accesses
|
||||
objects belonging to less-privileged code. So when chrome code accesses
|
||||
content objects, it sees them with Xray vision:
|
||||
|
||||
.. code:: brush:
|
||||
|
||||
// chrome code
|
||||
var transfer = gBrowser.contentWindow.confirm("Transfer all my money?");
|
||||
// calls the native implementation
|
||||
|
||||
.. note::
|
||||
|
||||
Note that using window.confirm() would be a terrible way to implement
|
||||
a security policy, and is only shown here to illustrate how Xray
|
||||
vision works.
|
||||
|
||||
.. _Waiving_Xray_vision:
|
||||
|
||||
Waiving Xray vision
|
||||
-------------------
|
||||
|
||||
| Xray vision is a kind of security heuristic, designed to make most
|
||||
common operations on untrusted objects simple and safe. However, there
|
||||
are some operations for which they are too restrictive: for example,
|
||||
if you need to see expandos on DOM objects. In cases like this you can
|
||||
waive Xray protection, but then you can no longer rely on any
|
||||
properties or functions being, or doing, what you expect. Any of them,
|
||||
even setters and getters, could have been redefined by untrusted code.
|
||||
| To waive Xray vision for an object you can use
|
||||
```Components.utils.waiveXrays(object)`` </en-US/docs/Components.utils.waiveXrays>`__,
|
||||
or use the object's ``wrappedJSObject`` property:
|
||||
|
||||
.. code:: brush:
|
||||
|
||||
// chrome code
|
||||
var waivedWindow = Components.utils.waiveXrays(gBrowser.contentWindow);
|
||||
var transfer = waivedWindow.confirm("Transfer all my money?");
|
||||
// calls the redefined implementation
|
||||
|
||||
.. code:: brush:
|
||||
|
||||
// chrome code
|
||||
var waivedWindow = gBrowser.contentWindow.wrappedJSObject;
|
||||
var transfer = waivedWindow.confirm("Transfer all my money?");
|
||||
// calls the redefined implementation
|
||||
|
||||
Waivers are transitive: so if you waive Xray vision for an object, then
|
||||
you automatically waive it for all the object's properties. For example,
|
||||
``window.wrappedJSObject.document`` gets you the waived version of
|
||||
``document``.
|
||||
|
||||
To undo the waiver again, call
|
||||
```Components.utils.unwaiveXrays(waivedObject)`` </en-US/docs/Components.utils.unwaiveXrays>`__:
|
||||
|
||||
.. code:: brush:
|
||||
|
||||
var unwaived = Components.utils.unwaiveXrays(waivedWindow);
|
||||
unwaived.confirm("Transfer all my money?");
|
||||
// calls the native implementation
|
||||
|
||||
.. _Xrays_for_DOM_objects:
|
||||
|
||||
Xrays for DOM objects
|
||||
---------------------
|
||||
|
||||
The primary use of Xray vision is for `DOM
|
||||
objects </en-US/docs/Web/API/Document_Object_Model>`__: that is, the
|
||||
objects that represent parts of the web page.
|
||||
|
||||
In Gecko, DOM objects have a dual representation: the canonical
|
||||
representation is in C++, and this is reflected into JavaScript for the
|
||||
benefit of JavaScript code. Any modifications to these objects, such as
|
||||
adding expandos or redefining standard properties, stays in the
|
||||
JavaScript reflection and does not affect the C++ representation.
|
||||
|
||||
The dual representation enables an elegant implementation of Xrays: the
|
||||
Xray just directly accesses the C++ representation of the original
|
||||
object, and doesn't go to the content's JavaScript reflection at all.
|
||||
Instead of filtering out modifications made by content, the Xray
|
||||
short-circuits the content completely.
|
||||
|
||||
This also makes the semantics of Xrays for DOM objects clear: they are
|
||||
the same as the DOM specification, since that is defined using the
|
||||
`WebIDL <http://www.w3.org/TR/WebIDL/>`__, and the WebIDL also defines
|
||||
the C++ representation.
|
||||
|
||||
.. _Xrays_for_JavaScript_objects:
|
||||
|
||||
Xrays for JavaScript objects
|
||||
----------------------------
|
||||
|
||||
Until recently, `built-in JavaScript objects that are not part of the
|
||||
DOM </en-US/docs/Web/JavaScript/Reference/Global_Objects>`__, such as
|
||||
``Date``, ``Error``, and ``Object``, did not get Xray vision when
|
||||
accessed by more-privileged code.
|
||||
|
||||
Most of the time this is not a problem: the main concern Xrays solve is
|
||||
with untrusted web content manipulating objects, and web content is
|
||||
usually working with DOM objects. For example, if content code creates a
|
||||
new ``Date`` object, it will usually be created as a property of a DOM
|
||||
object, and then it will be filtered out by the DOM Xray:
|
||||
|
||||
.. code:: brush:
|
||||
|
||||
// content code
|
||||
|
||||
// redefine Date.getFullYear()
|
||||
Date.prototype.getFullYear = function() {return 1000};
|
||||
var date = new Date();
|
||||
|
||||
.. code:: brush:
|
||||
|
||||
// chrome code
|
||||
|
||||
// contentWindow is an Xray, and date is an expando on contentWindow
|
||||
// so date is filtered out
|
||||
gBrowser.contentWindow.date.getFullYear()
|
||||
// -> TypeError: gBrowser.contentWindow.date is undefined
|
||||
|
||||
The chrome code will only even see ``date`` if it waives Xrays, and
|
||||
then, because waiving is transitive, it should expect to be vulnerable
|
||||
to redefinition:
|
||||
|
||||
.. code:: brush:
|
||||
|
||||
// chrome code
|
||||
|
||||
Components.utils.waiveXrays(gBrowser.contentWindow).date.getFullYear();
|
||||
// -> 1000
|
||||
|
||||
However, there are some situations in which privileged code will access
|
||||
JavaScript objects that are not themselves DOM objects and are not
|
||||
properties of DOM objects. For example:
|
||||
|
||||
- the ``detail`` property of a
|
||||
```CustomEvent`` </en-US/docs/Web/API/CustomEvent>`__ fired by
|
||||
content could be a JavaScript
|
||||
```Object`` </en-US/docs/Web/JavaScript/Reference/Global_Objects/Object>`__
|
||||
or
|
||||
```Date`` </en-US/docs/Web/JavaScript/Reference/Global_Objects/Date>`__
|
||||
as well as a string or a primitive
|
||||
- the return value of
|
||||
```evalInSandbox()`` </en-US/docs/Components.utils.evalInSandbox>`__
|
||||
and any properties attached to the
|
||||
```Sandbox`` </en-US/docs/Components.utils.Sandbox>`__ object may be
|
||||
pure JavaScript objects
|
||||
|
||||
Also, the WebIDL specifications are starting to use JavaScript types
|
||||
such as ``Date`` and ``Promise``: since WebIDL definition is the basis
|
||||
of DOM Xrays, not having Xrays for these JavaScript types starts to seem
|
||||
arbitrary.
|
||||
|
||||
So, in Gecko 31 and 32 we've added Xray support for most JavaScript
|
||||
built-in objects.
|
||||
|
||||
Like DOM objects, most JavaScript built-in objects have an underlying
|
||||
C++ state that is separate from their JavaScript representation, so the
|
||||
Xray implementation can go straight to the C++ state and guarantee that
|
||||
the object will behave as its specification defines:
|
||||
|
||||
.. code:: brush:
|
||||
|
||||
// chrome code
|
||||
|
||||
var sandboxScript = 'Date.prototype.getFullYear = function() {return 1000};' +
|
||||
'var date = new Date(); ';
|
||||
|
||||
var sandbox = Components.utils.Sandbox("https://example.org/");
|
||||
Components.utils.evalInSandbox(sandboxScript, sandbox);
|
||||
|
||||
// Date objects are Xrayed
|
||||
console.log(sandbox.date.getFullYear());
|
||||
// -> 2014
|
||||
|
||||
// But you can waive Xray vision
|
||||
console.log(Components.utils.waiveXrays(sandbox.date).getFullYear());
|
||||
// -> 1000
|
||||
|
||||
.. note::
|
||||
|
||||
To test out examples like this, you can use the `Scratchpad in
|
||||
browser
|
||||
context </en-US/docs/Tools/Scratchpad#Running_Scratchpad_in_the_browser_context>`__
|
||||
for the code snippet, and the `Browser
|
||||
Console </en-US/docs/Tools/Browser_Console>`__ to see the expected
|
||||
output.
|
||||
|
||||
Because code running in Scratchpad's browser context has chrome
|
||||
privileges, any time you use it to run code, you need to understand
|
||||
exactly what the code is doing. That includes the code samples in
|
||||
this article.
|
||||
|
||||
.. _Xray_semantics_for_Object_and_Array:
|
||||
|
||||
Xray semantics for Object and Array
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The exceptions are
|
||||
```Object`` </en-US/docs/Web/JavaScript/Reference/Global_Objects/Object>`__
|
||||
and ``Array``: their interesting state is in JavaScript, not C++. This
|
||||
means that the semantics of their Xrays have to be independently
|
||||
defined: they can't simply be defined as "the C++ representation".
|
||||
|
||||
The aim of Xray vision is to make most common operations simple and
|
||||
safe, avoiding the need to access the underlying object except in more
|
||||
involved cases. So the semantics defined for ``Object`` and ``Array``
|
||||
Xrays aim to make it easy for privileged code to treat untrusted objects
|
||||
like simple dictionaries.
|
||||
|
||||
Any `value
|
||||
properties </en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty>`__
|
||||
of the object are visible in the Xray. If the object has properties
|
||||
which are themselves objects, and these objects are same-origin with the
|
||||
content, then their value properties are visible as well.
|
||||
|
||||
There are two main sorts of restrictions:
|
||||
|
||||
- First, the chrome code might expect to rely on the prototype's
|
||||
integrity, so the object's prototype is protected:
|
||||
|
||||
- the Xray has the standard ``Object`` or ``Array`` prototype,
|
||||
without any modifications that content may have done to that
|
||||
prototype. The Xray always inherits from this standard prototype,
|
||||
even if the underlying instance has a different prototype.
|
||||
- if a script has created a property on an object instance that
|
||||
shadows a property on the prototype, the shadowing property is not
|
||||
visible in the Xray
|
||||
|
||||
- Second, we want to prevent the chrome code from running content code,
|
||||
so functions and `accessor
|
||||
properties </en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty>`__
|
||||
of the object are not visible in the Xray.
|
||||
|
||||
These rules are demonstrated in the script below, which evaluates a
|
||||
script in a sandbox, then examines the object attached to the sandbox.
|
||||
|
||||
.. note::
|
||||
|
||||
To test out examples like this, you can use the `Scratchpad in
|
||||
browser
|
||||
context </en-US/docs/Tools/Scratchpad#Running_Scratchpad_in_the_browser_context>`__
|
||||
for the code snippet, and the `Browser
|
||||
Console </en-US/docs/Tools/Browser_Console>`__ to see the expected
|
||||
output.
|
||||
|
||||
Because code running in Scratchpad's browser context has chrome
|
||||
privileges, any time you use it to run code, you need to understand
|
||||
exactly what the code is doing. That includes the code samples in
|
||||
this article.
|
||||
|
||||
.. code:: brush:
|
||||
|
||||
/*
|
||||
The sandbox script:
|
||||
* redefines Object.prototype.toSource()
|
||||
* creates a Person() constructor that:
|
||||
* defines a value property "firstName" using assignment
|
||||
* defines a value property which shadows "constructor"
|
||||
* defines a value property "address" which is a simple object
|
||||
* defines a function fullName()
|
||||
* using defineProperty, defines a value property on Person "lastName"
|
||||
* using defineProperty, defines an accessor property on Person "middleName",
|
||||
which has some unexpected accessor behavior
|
||||
*/
|
||||
|
||||
var sandboxScript = 'Object.prototype.toSource = function() {'+
|
||||
' return "not what you expected?";' +
|
||||
'};' +
|
||||
'function Person() {' +
|
||||
' this.constructor = "not a constructor";' +
|
||||
' this.firstName = "Joe";' +
|
||||
' this.address = {"street" : "Main Street"};' +
|
||||
' this.fullName = function() {' +
|
||||
' return this.firstName + " " + this.lastName;'+
|
||||
' };' +
|
||||
'};' +
|
||||
'var me = new Person();' +
|
||||
'Object.defineProperty(me, "lastName", {' +
|
||||
' enumerable: true,' +
|
||||
' configurable: true,' +
|
||||
' writable: true,' +
|
||||
' value: "Smith"' +
|
||||
'});' +
|
||||
'Object.defineProperty(me, "middleName", {' +
|
||||
' enumerable: true,' +
|
||||
' configurable: true,' +
|
||||
' get: function() { return "wait, is this really a getter?"; }' +
|
||||
'});';
|
||||
|
||||
var sandbox = Components.utils.Sandbox("https://example.org/");
|
||||
Components.utils.evalInSandbox(sandboxScript, sandbox);
|
||||
|
||||
// 1) trying to access properties in the prototype that have been redefined
|
||||
// (non-own properties) will show the original 'native' version
|
||||
// note that functions are not included in the output
|
||||
console.log("1) Property redefined in the prototype:");
|
||||
console.log(sandbox.me.toSource());
|
||||
// -> "({firstName:"Joe", address:{street:"Main Street"}, lastName:"Smith"})"
|
||||
|
||||
// 2) trying to access properties on the object that shadow properties
|
||||
// on the prototype will show the original 'native' version
|
||||
console.log("2) Property that shadows the prototype:");
|
||||
console.log(sandbox.me.constructor);
|
||||
// -> function()
|
||||
|
||||
// 3) value properties defined by assignment to this are visible:
|
||||
console.log("3) Value property defined by assignment to this:");
|
||||
console.log(sandbox.me.firstName);
|
||||
// -> "Joe"
|
||||
|
||||
// 4) value properties defined using defineProperty are visible:
|
||||
console.log("4) Value property defined by defineProperty");
|
||||
console.log(sandbox.me.lastName);
|
||||
// -> "Smith"
|
||||
|
||||
// 5) accessor properties are not visible
|
||||
console.log("5) Accessor property");
|
||||
console.log(sandbox.me.middleName);
|
||||
// -> undefined
|
||||
|
||||
// 6) accessing a value property of a value-property object is fine
|
||||
console.log("6) Value property of a value-property object");
|
||||
console.log(sandbox.me.address.street);
|
||||
// -> "Main Street"
|
||||
|
||||
// 7) functions defined on the sandbox-defined object are not visible in the Xray
|
||||
console.log("7) Call a function defined on the object");
|
||||
try {
|
||||
console.log(sandbox.me.fullName());
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
// -> TypeError: sandbox.me.fullName is not a function
|
||||
|
||||
// now with waived Xrays
|
||||
console.log("Now with waived Xrays");
|
||||
|
||||
console.log("1) Property redefined in the prototype:");
|
||||
console.log(Components.utils.waiveXrays(sandbox.me).toSource());
|
||||
// -> "not what you expected?"
|
||||
|
||||
console.log("2) Property that shadows the prototype:");
|
||||
console.log(Components.utils.waiveXrays(sandbox.me).constructor);
|
||||
// -> "not a constructor"
|
||||
|
||||
console.log("3) Accessor property");
|
||||
console.log(Components.utils.waiveXrays(sandbox.me).middleName);
|
||||
// -> "wait, is this really a getter?"
|
||||
|
||||
console.log("4) Call a function defined on the object");
|
||||
console.log(Components.utils.waiveXrays(sandbox.me).fullName());
|
||||
// -> "Joe Smith"
|