Skip to content

Commit 9b17577

Browse files
authored
Merge pull request #1718 from endojs/markm-910-align-w-xs-prop-censor
fix(ses): align with XS property censorship agreement
2 parents 12349e5 + e4be709 commit 9b17577

14 files changed

+232
-118
lines changed

packages/ses/NEWS.md

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
User-visible changes in SES:
22

3+
# Next release
4+
5+
- In the SES-shim implementation of HardenedJS, all constructed compartments
6+
get the same safe `Date`
7+
constructor, that does not provide the ability to measure duration. It used
8+
to do this by having `Date.now()` return `NaN`, and to have calls on the
9+
constructor that would normally have returned an indication of the current date,
10+
instead return the corresponding invalid date indication. Now, all of these
11+
throw a `TypeError` whose message begins with `'secure mode'`. This aligns with
12+
the XS implementation of HardenedJS.
13+
- Similarly, In the SES-shim implementation of HardenedJS,
14+
all constructed compartments get the same safe `Math` namespace
15+
object that does not provide a working `random()` function. It used to do that
16+
by omitting the `random` property from the safe `Math` namespace object. Now,
17+
the safe shared `Math` namespace object has a `Math.random()` function that
18+
throws a `TypeError whose message begins with `'secure mode'`. This again
19+
aligns with the XS implementation of HardenedJS.
20+
321
# v0.18.6 (2023-08-07)
422

523
- Censors the pattern `{...import(specifier)`}.
@@ -44,7 +62,7 @@ User-visible changes in SES:
4462
- Fixes a bug for SES initialization in a no-unsafe-eval
4563
Content-Security-Policy.
4664
- Fixes a bug where reexport of multiple named exports of the same name was
47-
causing them to be overridden by the last value. Now named exports are
65+
causing them to be overridden by the last value. Now named exports are
4866
handled in the same manner as `export *`.
4967
- Allows Compatment `importHook` implementations to return aliases: module
5068
descriptors that refer to a module by its specifier in the same or a

packages/ses/README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,8 @@ c1.globalThis.JSON === c2.globalThis.JSON; // true
194194
```
195195

196196
The global scope of every compartment includes a shallow, specialized copy of
197-
the JavaScript intrinsics, omitting `Date.now` and `Math.random`.
197+
the JavaScript intrinsics, disabling `Math.random`, `Date.now` and the
198+
behaviors of the `Date` constructor which would reveal the current time.
198199
Comaprtments leave these out since they can be used as covert communication
199200
channels between programs.
200201
However, a compartment may be expressly given access to these objects
@@ -722,7 +723,7 @@ have found the object capability model that `ses` provides to be sound.
722723
Hardened JavaScript is also within the scope of the [Agoric bug bounty
723724
program](https://hackerone.com/agoric), which rewards researchers for surfacing valid
724725
bugs in our code. We welcome the opportunity to cooperate with researchers,
725-
whose efforts will undoubtedly yield stronger, more resilient code.
726+
whose efforts will undoubtedly yield stronger, more resilient code.
726727

727728
## Bug Disclosure
728729

@@ -768,7 +769,7 @@ provide some place to include text like
768769
> option.
769770
> Specifically, [link to source in the project] does not work correctly in such
770771
> an environment.
771-
>
772+
>
772773
> Please consider increasing support by replacing assignments to object
773774
> properties inherited from intrinsics with use of `Object.defineProperties`
774775
> (thereby working around the JavaScript "override mistake"), and if applicable
@@ -791,4 +792,3 @@ intrinsics, including `ses`.
791792
[SES proposal]: https://github.com/tc39/proposal-ses
792793
[SES Strategy Group]: https://groups.google.com/g/ses-strategy
793794
[SES Strategy Recordings]: https://www.youtube.com/playlist?list=PLzDw4TTug5O1jzKodRDp3qec8zl88oxGd
794-

packages/ses/demos/challenge/README.md

+61-51
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ https://ses-demo.agoric.app/demos/challenge/ to run it.
77
To avoid relying upon a third party for security, your production applications
88
should publish and reference their own copy of `ses.umd.js`.
99

10-
For local testing, after making a change, run ``yarn build`` to build the
10+
For local testing, after making a change, run `yarn build` to build the
1111
generated files. Then, ensure that the demo is pointing towards your generated
12-
file (i.e.``<script src="../../dist/ses.umd.js"></script> ``). Next, run a web
12+
file (i.e.`<script src="../../dist/ses.umd.js"></script> `). Next, run a web
1313
server and serve the entire git tree. (the demo accesses the generated
14-
``ROOT/dist/ses.umd.js`` file, so serving just this ``demos/challenge/``
15-
directory is not enough).
14+
`ROOT/dist/ses.umd.js` file, so serving just this `demos/challenge/`
15+
directory is not enough).
1616

1717
## Would You Like To Play A Game?
1818

@@ -25,13 +25,13 @@ the computer.
2525
You provide the attacker's program by pasting its source code into the box
2626
and pressing the Execute button. The attack program is confined in an SES
2727
environment (shared with the defender), which means it is limited to
28-
``strict`` mode and has access to the usual ECMAScript intrinsics (Object,
28+
`strict` mode and has access to the usual ECMAScript intrinsics (Object,
2929
String, Array, Math, Map, Date (but see below), and so on). But it does not
30-
have access to platform objects (``window``, ``document``, or ``XHR`` on a
31-
web browser, or ``require`` on Node.js).
30+
have access to platform objects (`window`, `document`, or `XHR` on a
31+
web browser, or `require` on Node.js).
3232

3333
In addition to the intrinsics, the attacker's program gets access to two
34-
endowments: ``guess(code)`` and ``log(message)``. These are provided by the
34+
endowments: `guess(code)` and `log(message)`. These are provided by the
3535
defender.
3636

3737
This source code must evaluate to a generator function (starting with
@@ -45,12 +45,12 @@ function* guessZeros() {
4545
}
4646
```
4747

48-
The attacker uses ``guess()`` to try and guess the launch code. This guess is
48+
The attacker uses `guess()` to try and guess the launch code. This guess is
4949
displayed on the bottom panel. When the guess matches the code on the top
5050
panel, the attacker wins and the game is over. The codes are ten characters
5151
long, and each character is a capital letter from A to Z, or a digit from 0
52-
to 9. There are ``36**10`` possibilities (about three quadrillion, or about
53-
``2**51``, which also happens to be roughly how many ants are alive on Earth
52+
to 9. There are `36**10` possibilities (about three quadrillion, or about
53+
`2**51`, which also happens to be roughly how many ants are alive on Earth
5454
at any given moment). It takes at least a few milliseconds to check each one,
5555
so brute-force guessing would take thousands of years to try all possible
5656
combinations.
@@ -76,7 +76,7 @@ script is taking too long" dialog box after maybe 15 seconds of constant
7676
execution, since it is effectively stuck in an infinite loop.
7777

7878
By using a generator, we can update the UI once per iteration. Thus the
79-
attack function should call ``guess()`` once, then yield from the generator,
79+
attack function should call `guess()` once, then yield from the generator,
8080
then loop back around if it wants to make more guesses. The above program
8181
should be rewritten like this:
8282

@@ -94,39 +94,39 @@ function* counter() {
9494
9595
Despite this quirk, the attacker's program is still essentially being
9696
evaluated as purely transformational code: the way we load the attacker isn't
97-
quite as important as the way we allow it to call ``guess()``.
97+
quite as important as the way we allow it to call `guess()`.
9898
9999
## Defender
100100
101101
The defender in this game has slightly more power than the attacker: it is
102102
SES-confined as well, but it gets additional endowments from the setup code.
103-
One of these is a form of ``getRandomValues`` so it can select a random
103+
One of these is a form of `getRandomValues` so it can select a random
104104
target code: SES programs are normally denied access to non-determinism, so
105105
it would have no way of choosing a different code on each pageload. A few
106-
more endowments provide control over the DOM: ``setMacguffinText`` sets the
107-
target code in the top box, ``setAttackerGuess`` sets the guessed code in the
108-
bottom box, ``setLaunch`` changes the CSS class of the bottom box to change
106+
more endowments provide control over the DOM: `setMacguffinText` sets the
107+
target code in the top box, `setAttackerGuess` sets the guessed code in the
108+
bottom box, `setLaunch` changes the CSS class of the bottom box to change
109109
the text color when the guess is correct. Note that, for this demo, none of
110110
these endowments are particularly defensive: the defender code could use them
111111
to break out of the SES sandbox.
112112
113-
``refreshUI`` returns a Promise that resolves after a ``setTimeout(0)``. This
113+
`refreshUI` returns a Promise that resolves after a `setTimeout(0)`. This
114114
yields control back to the browser's UI event queue, giving it a chance to
115-
repaint the screen with the updated codes. ``log`` simply delivers its
116-
arguments to the browser's usual ``console.log``.
115+
repaint the screen with the updated codes. `log` simply delivers its
116+
arguments to the browser's usual `console.log`.
117117
118-
``delayMS`` performs a busy-wait for the given number of milliseconds by
119-
polling ``Date.now`` until it reaches some target value.
118+
`delayMS` performs a busy-wait for the given number of milliseconds by
119+
polling `Date.now` until it reaches some target value.
120120
121-
We use ``delayMS()`` to introduce an egregious timing-channel vulnerability
122-
into the defender's ``guess()`` function: it checks the attacker's guess one
121+
We use `delayMS()` to introduce an egregious timing-channel vulnerability
122+
into the defender's `guess()` function: it checks the attacker's guess one
123123
character at a time, waiting a full 10ms between each test, and returns
124124
immediately upon the first incorrect failure. If the attacker can measure how
125-
long ``guess()`` takes to run, they can mount a classic timing-oracle attack
125+
long `guess()` takes to run, they can mount a classic timing-oracle attack
126126
which runs in linear (rather than exponential) time. A safer form of
127-
``guess()`` would do a constant-time comparison (for which the most practical
127+
`guess()` would do a constant-time comparison (for which the most practical
128128
approach is to just hash both sides and compare the hashes). Our vulnerable
129-
``guess()`` looks like this:
129+
`guess()` looks like this:
130130
131131
```js
132132
function guess(guessedCode) {
@@ -145,8 +145,8 @@ function guess(guessedCode) {
145145
}
146146
```
147147
148-
The defender creates the ``guess()`` function, then provides it (and ``log``)
149-
as endowments to the attacker. It invokes ``SES.confine`` to evaluate the
148+
The defender creates the `guess()` function, then provides it (and `log`)
149+
as endowments to the attacker. It invokes `SES.confine` to evaluate the
150150
attacker's code with the endowments as the second argument:
151151
152152
```js
@@ -156,7 +156,7 @@ function attackerLog(...args) {
156156
const attacker = SES.confine(program, { guess: guess, log: attackerLog });
157157
```
158158
159-
The defender then invokes the attacker in a loop, using ``refreshUI()`` to
159+
The defender then invokes the attacker in a loop, using `refreshUI()` to
160160
delay each pass until the UI had a chance to be updated, with something like
161161
this:
162162
@@ -179,20 +179,29 @@ this:
179179
code right).
180180
181181
182-
## Taming Date.now
182+
## Taming Date and Date.now
183183
184-
The SES environment normally replaces ``Date.now()`` with a function that
185-
only returns ``NaN``. But ``Date.now()`` can be re-enabled by setting a
186-
configuration option named ``dateNowMode`` to ``allow``.
184+
The original standard `Date` constructor provides three ways to obtain the
185+
current time:
186+
* `new Date()` -- calling it as a constructor (with `new`) but
187+
with no arguments, returns a date instance representing the current time.
188+
* `Date(...)` -- calling it as a function (without `new`) no matter what
189+
the arguments may be, returns a string representing the current time.
190+
* `Date.now()` -- returns the number of milliseconds since the Unix Epoch
187191
188-
(Note that this API is still in flux, and we might change it in the future.
189-
One interesting option might be to set ``Date.now`` to return a constant,
190-
pre-selected value, enabling more code to run mostly-correctly, while still
191-
limiting its use as an attack vector)
192+
Because this original `Date` constructor provides the magical I/O power,
193+
let's call it the "powerful" `Date` constructor. SES leaves this powerful `Date`
194+
constructor on the original global object, i.e., the global object of the
195+
start compartment. All compartments other than the start compartment must be
196+
explicitly created, so those are the "constructed" compartments.
192197
193-
SES replaces the ``new Date()`` constructor with a tamed version that acts as
194-
if you wrote ``new Date(NaN)``. It does not currently have an option to
195-
change this behavior, but that will probably change too.
198+
The challenge code runs in an SES constructed Compartment.
199+
Within a constructed Compartment, SES normally replaces
200+
the powerful `Date` constructor with the safe that one that throws a
201+
`TypeError` in response to the three cases above. But the code in the start
202+
compartment can explicitly provide the powerful `Date` as an endowment to
203+
a compartment it constructs, enabling code run in that compartment to sense
204+
the current time.
196205
197206
By prohibiting access to a clock, we can prevent the attacker code from
198207
sensing timing-based covert channel. Fully-deterministic execution cannot
@@ -207,14 +216,14 @@ Gruss, and Mangard) enumerates a variety of surprising clocks that might be
207216
available to Javascript code. They all depend upon forms of non-determinism,
208217
such as:
209218
210-
* shared-state mutability: a ``WebWorker`` writes sequential integers to a
211-
shared ``ArrayBuffer`` as fast as it can, while a second thread simply
219+
* shared-state mutability: a `WebWorker` writes sequential integers to a
220+
shared `ArrayBuffer` as fast as it can, while a second thread simply
212221
reads from that location when desired
213222
* platform-provided UI features: initiate a CSS animation and monitor its
214223
progress
215-
* message-passing: two separate frames exchange ``postMessage`` calls and
224+
* message-passing: two separate frames exchange `postMessage` calls and
216225
compare their arrival order with ones sent to themselves
217-
* explicit platform features: the ``performance.now()`` call offers
226+
* explicit platform features: the `performance.now()` call offers
218227
microsecond-level timing
219228
220229
SES omits all platform features, which removes all the timers listed in this
@@ -226,17 +235,18 @@ interleaved in some nondeterminisic fashion that depends upon the arrival
226235
times: that ordering can also be used as a clock.
227236
228237
The demo page has two flavors: by changing the URL slightly, the attacker can
229-
be allowed or denied access to ``Date.now()``. This makes it easy to
238+
be allowed or denied access to the powerful `Date` constructor.
239+
This makes it easy to
230240
demonstrate the success or failure of a timing-based attack.
231241
232-
The first version of this demo implemented ``delayMS()`` with a Promise that
233-
resolved at some point in the future (via a ``setTimeout()`` callback). In
234-
that version, ``guess()`` returned a Promise. Richard Gibson exploited this
242+
The first version of this demo implemented `delayMS()` with a Promise that
243+
resolved at some point in the future (via a `setTimeout()` callback). In
244+
that version, `guess()` returned a Promise. Richard Gibson exploited this
235245
in a [clever attack](https://github.com/endojs/endo/issues/191) that submitted
236246
multiple guesses in parallel and sensed the order of their resolution: it
237-
didn't reveal exactly how long ``guess()`` took, but knowing which guess took
247+
didn't reveal exactly how long `guess()` took, but knowing which guess took
238248
the longest was enough to mount the attack. We thought we were only using
239-
``setTimeout()`` for internal purposes, but by exposing a function that used
249+
`setTimeout()` for internal purposes, but by exposing a function that used
240250
it, we accidentally gave the attacker code enough tools to build a clock of
241251
their own.
242252

packages/ses/demos/challenge/index.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ <h1>Hardened JavaScript Escape Room</h1>
3838
Compartment (the one that ran <code>lockdown()</code>), so this attacker
3939
doesn’t get a clock, and cannot read from the covert channel. Load this page
4040
<a href="?dateNow=enabled">with ?dateNow=enabled</a> to demonstrate the
41-
attack with <code>Date.now</code> enabled, or <a href="?dateNow=NaN">with
42-
?dateNow=NaN</a> to properly confine the attacker.</p>
41+
attack with <code>Date.now</code> enabled, or <a href="?dateNow=disabled">with
42+
?dateNow=disabled</a> to properly confine the attacker.</p>
4343
<p>The defender running in the start Compartment gets access to powerful JS
4444
globals. This includes sources of non-determinism like
4545
<code>window.setTimeout</code> and

packages/ses/docs/draft-standalone-spec.md

+12-4
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,18 @@ the default configuration of SES are
2626
* Omit annex B (except those our whitelist allows)
2727
* In particular, omit the `RegExp` static properties that provide a global
2828
communications channel.
29-
* Omit `Math.random()`
30-
* Omit ambient access to current date/time:
31-
* `Date.now()` returns `NaN`
32-
* `new Date()` return equivalent of `new Date(NaN)`
29+
* On the `Math` namespace object shared by constructed compartments:
30+
- `Math.random()` throws a `TypeError` rather than provide a random number,
31+
which would be a source of non-determinism.
32+
* On the `Date` constructor shared by constructed compartments:
33+
- `Date.now()` throws a `TypeError` rather than returning the millisecods
34+
representing the current time.
35+
- `new Date()`, calling it as a constructor (with `new`) with no arguments,
36+
throws a `TypeError` rather than returning a date instance
37+
representing the current time.
38+
- `Date(...)`, calling it as a function (without `new`) no matter what
39+
the arguments, throws a `TypeError` rather than a string presenting
40+
the current time.
3341
* By default, omit `Intl`, the internationalization APIs
3442
* If some of `Intl` is included, it must suppress ambient authority and
3543
non-determinism.

packages/ses/docs/guide.md

+13-6
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,16 @@ defines a subset of the globals defined by the baseline JavaScript language spec
160160
- `BigInt`
161161
- `Intl`
162162
- `Math` all features except
163-
- `Math.random()` is disabled (calling it throws an error) as an obvious source of
164-
non-determinism.
163+
- `Math.random()` throws a `TypeError` rather than provide a random number, which would be a source of non-determinism.
165164
- `Date` all features except
166-
- `Date.now()` returns `NaN`
167-
- `new Date(nonNumber)` or `Date(anything)` return a `Date` that stringifies to `"Invalid Date"`
165+
- `Date.now()` throws a `TypeError` rather than returning the millisecods
166+
representing the current time.
167+
- `new Date()`, calling it as a constructor (with `new`) with no arguments,
168+
throws a `TypeError` rather than returning a date instance
169+
representing the current time.
170+
- `Date(...)`, calling it as a function (without `new`) no matter what
171+
the arguments, throws a `TypeError` rather than a string presenting
172+
the current time.
168173

169174
Much of the `Intl` package, and some other objects' locale-specific aspects (e.g. `Number.prototype.toLocaleString`)
170175
have results that depend upon which locale is configured. This varies from one process to another.
@@ -327,8 +332,10 @@ c1.globalThis === c2.globalThis; // false
327332
c1.globalThis.JSON === c2.globalThis.JSON; // true
328333
```
329334
Every compartment's global scope includes a shallow, specialized copy of the JavaScript
330-
intrinsics. These omit `Date.now()` and `Math.random()`
331-
since they can be covert inter-program communication channels.
335+
intrinsics. These disable `Math.random()`, `Date.now()`, and the behaviors of
336+
the `Date` constructor which would provide the current time,
337+
since all these sources of non-determinism can enable covert inter-program
338+
communication channels.
332339

333340
However, a compartment may be expressly given access to these objects through
334341
the compartment constructor's first argument or by assigning them to the

0 commit comments

Comments
 (0)