@@ -7,12 +7,12 @@ https://ses-demo.agoric.app/demos/challenge/ to run it.
7
7
To avoid relying upon a third party for security, your production applications
8
8
should publish and reference their own copy of ` ses.umd.js ` .
9
9
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
11
11
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
13
13
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).
16
16
17
17
## Would You Like To Play A Game?
18
18
@@ -25,13 +25,13 @@ the computer.
25
25
You provide the attacker's program by pasting its source code into the box
26
26
and pressing the Execute button. The attack program is confined in an SES
27
27
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,
29
29
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).
32
32
33
33
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
35
35
defender.
36
36
37
37
This source code must evaluate to a generator function (starting with
@@ -45,12 +45,12 @@ function* guessZeros() {
45
45
}
46
46
```
47
47
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
49
49
displayed on the bottom panel. When the guess matches the code on the top
50
50
panel, the attacker wins and the game is over. The codes are ten characters
51
51
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
54
54
at any given moment). It takes at least a few milliseconds to check each one,
55
55
so brute-force guessing would take thousands of years to try all possible
56
56
combinations.
@@ -76,7 +76,7 @@ script is taking too long" dialog box after maybe 15 seconds of constant
76
76
execution, since it is effectively stuck in an infinite loop.
77
77
78
78
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,
80
80
then loop back around if it wants to make more guesses. The above program
81
81
should be rewritten like this:
82
82
@@ -94,39 +94,39 @@ function* counter() {
94
94
95
95
Despite this quirk, the attacker's program is still essentially being
96
96
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 ()` .
98
98
99
99
## Defender
100
100
101
101
The defender in this game has slightly more power than the attacker: it is
102
102
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
104
104
target code: SES programs are normally denied access to non-determinism, so
105
105
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
109
109
the text color when the guess is correct. Note that, for this demo, none of
110
110
these endowments are particularly defensive: the defender code could use them
111
111
to break out of the SES sandbox.
112
112
113
- ` ` refreshUI` ` returns a Promise that resolves after a ` ` setTimeout(0)` ` . This
113
+ ` refreshUI` returns a Promise that resolves after a ` setTimeout (0 )` . This
114
114
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 ` .
117
117
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.
120
120
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
123
123
character at a time, waiting a full 10ms between each test, and returns
124
124
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
126
126
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
128
128
approach is to just hash both sides and compare the hashes). Our vulnerable
129
- ` ` guess()` ` looks like this:
129
+ ` guess ()` looks like this:
130
130
131
131
` ` ` js
132
132
function guess (guessedCode ) {
@@ -145,8 +145,8 @@ function guess(guessedCode) {
145
145
}
146
146
` ` `
147
147
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
150
150
attacker's code with the endowments as the second argument:
151
151
152
152
` ` ` js
@@ -156,7 +156,7 @@ function attackerLog(...args) {
156
156
const attacker = SES .confine (program, { guess: guess, log: attackerLog });
157
157
` ` `
158
158
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
160
160
delay each pass until the UI had a chance to be updated, with something like
161
161
this:
162
162
@@ -179,20 +179,29 @@ this:
179
179
code right).
180
180
181
181
182
- ## Taming Date.now
182
+ ## Taming Date and Date .now
183
183
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
187
191
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.
192
197
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.
196
205
197
206
By prohibiting access to a clock, we can prevent the attacker code from
198
207
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
207
216
available to Javascript code. They all depend upon forms of non-determinism,
208
217
such as:
209
218
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
212
221
reads from that location when desired
213
222
* platform-provided UI features: initiate a CSS animation and monitor its
214
223
progress
215
- * message-passing: two separate frames exchange ` ` postMessage` ` calls and
224
+ * message-passing: two separate frames exchange ` postMessage` calls and
216
225
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
218
227
microsecond-level timing
219
228
220
229
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
226
235
times: that ordering can also be used as a clock.
227
236
228
237
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
230
240
demonstrate the success or failure of a timing-based attack.
231
241
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
235
245
in a [clever attack](https://github.com/endojs/endo/issues/191) that submitted
236
246
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
238
248
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
240
250
it, we accidentally gave the attacker code enough tools to build a clock of
241
251
their own.
242
252
0 commit comments